From 802855f7e6f2f00a107a12196b458b267f586b8d Mon Sep 17 00:00:00 2001 From: Codex-9e8r6t Date: Fri, 11 Sep 2026 14:09:23 -0700 Subject: [PATCH] Preserve exact navigation prerelease GUI inputs --- .gitattributes | 17 + 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 | 2730 +- .../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/api.html | 2692 +- docs/api.rst | 1564 +- docs/language.html | 5688 ++-- docs/openapi.json | 25674 ++++++++-------- .../rest-coverage/coverage-probe.json | 114 +- 16 files changed, 23564 insertions(+), 23547 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3658687c..5788a86a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -71,3 +71,20 @@ harness/history/2026-09-11-state-before-gitea-migration/** -text whitespace=cr-a harness/experiments/contextual-prerelease-20260911/native/**/*.arcadia whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol harness/experiments/contextual-prerelease-20260911/producer/package*.log whitespace=-blank-at-eof,cr-at-eol harness/experiments/contextual-prerelease-20260911/producer/source-push.log -whitespace + +# Exact frozen navigation GUI input bytes; historical server source is separate. +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.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/ordered.rs -text +docs/api.html -text +docs/api.rst -text +docs/language.html -text +docs/openapi.json -text +harness/fixtures/rest-coverage/coverage-probe.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 3cb4cbce..a308e1f5 100644 --- a/crates/oa-desktop/src/guide.rs +++ b/crates/oa-desktop/src/guide.rs @@ -1,803 +1,803 @@ -//! 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.", +//! 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 definition references lexical navigation F12 back", - 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.", - ], - ), + 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( "Follow definitions and lexical references", "Place the caret on a declaration alias or an @reference. Choose Definition (F12) to locate its declaration, or References (Shift+F12) to list lexical uses. Choose a row when a definition is ambiguous, draft or detached. Lexical uses exclude comments and strings; they do not establish typed relationships or resolve an ambiguous alias to one declaration. Back (Alt+Left) returns to the previous source location; if that draft changed, it restores the current cursor instead of an obsolete selection. These shortcuts work while the source editor has focus; the Text toolbar also offers the actions.", @@ -808,569 +808,569 @@ fn article(topic: Topic) -> Article { ), 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)); - } -} + "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/api.html b/docs/api.html index 80433d9b..2bb7b9e0 100644 --- a/docs/api.html +++ b/docs/api.html @@ -1,1346 +1,1346 @@ - - - - - - -Arcadia Studio model REST API - - - - -
-

Arcadia Studio model REST API

- -

This is the HTTP contract for oa-server. The normative engineering model -and source language belong to language.rst. openapi.json describes the -JSON records and closed command vocabulary. The server uses the same Rust -Project, Command, parser/printer and validation implementation as the -desktop.

-
-

Identity, revision and writes

-

All model routes begin with /api/v1. A hosted project's immutable service -resource_id is different from its editable model.id. Each stored file -contains {resource_id, revision, model, source_project}. source_project -is the accepted entry filename and map of authored source files; compiling it -must reproduce model. The revision starts at 1, increments once per changed -accepted model/source pair, and does not change for an exact no-op. Comment-only -edits and file/entry changes count as changes even when the model is unchanged.

-

Existing-project writes require the exact quoted ETag returned by a project -read, for example:

-
If-Match: "p-6f7162cc-094c-4f30-9b70-3f6f355f0c7f-r1"
-

Missing preconditions return 428; stale preconditions return 412. Weak ETags, -wildcards and lists are not accepted on writes. GET of the envelope, model and -source/source-project supports If-None-Match and returns an empty 304 when unchanged. -Use ETags as opaque strings; revisions are unsigned integer counters in JSON.

-

Every accepted changed write is persisted before the response. A worker captures -a revision-qualified snapshot, then parses and validates its candidate outside -the catalog lock. Commit rechecks the exact ETag and atomically installs the -envelope under the lock. Another accepted edit during preparation therefore -returns 412 even when the original snapshot passed its initial revision check. -Malformed or rejected inputs preserve the accepted model. Incomplete-model -warnings permit work and are returned in issues.

-

There is no retained revision history, idempotency receipt, tombstone or -server undo. DELETE permanently removes the hosted envelope. Source/JSON -interchange exports contain the current model only; source-project export also -preserves the authored file layout and comments. They preserve persistent -aliases, diagrams, artifact references and OA/SA trace data. Artifact revision -strings refer to external artifacts, not past server snapshots.

-

A lost connection or 504 can occur after an admitted write has begun. That -write may still complete. Read current state before choosing a new action. -Do not automatically retry POST create/import or regenerate random IDs and -assume the original request failed.

-
-
-

Routes

-

Health and schema routes are public. All model routes require the configured -bearer token, if any. Non-loopback binding requires a configured token.

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Endpoints

Method

Path

Behavior

GET

/health, /health/live, /health/ready

Responding process with successfully opened store.

GET

/openapi.json, /api/v1/openapi.json

OpenAPI 3.1 contract.

POST

/api/v1/projects

Create from {"name":"..."}; 201 + Location/ETag.

GET

/api/v1/projects

Summaries; limit, after, name substring q.

GET

/api/v1/projects/{id}

Complete current stored envelope.

PATCH

/api/v1/projects/{id}

Conditional rename using {"name":"..."}.

DELETE

/api/v1/projects/{id}

Conditional permanent removal; 204.

POST

/api/v1/projects/import

Raw model JSON or Arcadia source; new service ID.

POST

/api/v1/projects/import-source-project

Complete source bundle as JSON; stabilize missing IDs and create a resource.

GET / PUT

/api/v1/projects/{id}/model

Export or conditionally replace native model JSON.

GET / PUT

/api/v1/projects/{id}/source

Export or conditionally replace canonical UTF-8 source.

GET / PUT

/api/v1/projects/{id}/source-project

Export exact authored files or conditionally replace the complete bundle.

GET

/api/v1/projects/{id}/elements

Filter by kind, layer, alias, name q.

GET

/api/v1/projects/{id}/relations

Filter by kind, source, target, incident_to.

GET

/api/v1/projects/{id}/artifact-links

Artifact links; optional owner object filter.

GET

/api/v1/projects/{id}/traces

OA/SA traces; optional endpoint object filter.

POST

/api/v1/projects/{id}/query

Bounded directed neighborhood, no mutation.

GET / POST

/api/v1/projects/{id}/validation

Validate current model or a submitted candidate.

POST

/api/v1/projects/{id}/transactions

Conditional typed command batch, optional dry run.

POST

/api/v1/projects/{id}/transitions/preview

Preview a core transition against If-Match; return plan/base_revision.

POST

/api/v1/projects/{id}/transitions/apply

Conditionally apply that client-held plan and base_revision.

POST

/api/v1/projects/{id}/semantic-transitions/query

Read selected traces' typed facts, own drift and linked impacts.

POST

/api/v1/projects/{id}/semantic-transitions/preview

Prepare typed semantic decisions and source writeback without writing.

POST

/api/v1/projects/{id}/semantic-transitions/apply

Apply that prepared candidate under its original shared revision/ETag.

-

Content-Type: application/json is required for JSON requests. -text/plain; charset=utf-8 is used for Arcadia source. The service does not -guess formats from a filename. Unknown DTO fields, invalid UUIDs/enums, duplicate -model map keys and unsupported schemas are rejected. Maximum body size is -8 MiB. Accepted serialized model JSON and serialized source-bundle JSON each -have an 8 MiB cap, including IDs inserted during compilation. The stored -envelope allows both bounded representations plus small framing overhead. -Compressed or encoded request bodies and browser Origin requests are -not supported.

-

Model admission permits at most 5,000 elements plus relations, 20,000 total -persistent records (including the project, views, placements, routes and manual -port anchors), and 50,000 ordered step references. Source declaration counts and decoded JSON record -counts are checked before structural validation. Legacy import counts the -occurrences and placements migration will generate before validating or -migrating that model. Exceeding these budgets returns 422 model_limit; -candidate validation reports the same diagnostic in its 200 response. -Parsing, validation and query processing use snapshot workers, so this work -does not hold the catalog lock or serialize unrelated model preparation.

-

Import accepts schema-1 JSON through the shared explicit migration adapter, -as well as schema-2, schema-3, schema-4, schema-5, schema-6 and current schema-7 JSON. Source import uses the shared language parser -and its supported migrations. Export uses schema 7 / arcadia 7. Native JSON -replacement accepts supported older schemas through the same explicit migration. -Only documented query parameters are accepted. In particular, appending -?revision=... to model/source export is an error, not historical retrieval.

-
-
-

Authored source projects

-

A source bundle contains entry and a map of files. For example:

-
{
-  "entry": "rescue.arcadia",
-  "files": {
-    "rescue.arcadia": "arcadia 7; project rescue \"Rescue\" { include \"oa/work.arcadia\"; }\n",
-    "oa/work.arcadia": "// Engineering rationale\noa_activity assess \"Assess assistance\" {}\n"
-  }
-}
-

The entry is a root filename. All keys are canonical, portable, slash-separated -relative paths ending in .arcadia. Includes resolve only against these -submitted strings. They never read the service filesystem, URLs, artifact -targets or another hosted resource. All submitted files must be reachable; -missing files, cycles, duplicate keys, case collisions and escaping paths are -rejected. The bundle permits at most 256 files, 32 include edges along a path, -and 1,024 include edges in total. Model and source-byte budgets apply across -the complete bundle, not separately per file. Current bundle syntax is source7; source3/4/5/6 bundles migrate explicitly while -preserving their files/includes/comments. V1/V2 single-file migration remains -available through interchange import.

-

Compilation inserts UUIDs into headers that omit them. Import returns 201 with -Location/ETag; GET source-project retrieves the stabilized accepted bundle. -PUT returns revision, changed, located issues and that accepted -source_project. Preserve the returned IDs before subsequent editing. -With explicit IDs, untouched text, comments, line endings, entry name and module -ownership are preserved exactly. Re-submitting the same accepted bundle is a -no-op. Removing a file means submitting a complete valid bundle without that -file and its includes; no files are created or deleted on the service host.

-

All typed command, transition, rename and model-interchange writes update this -same accepted source project. Existing declarations and reference comments -follow command identity correspondence, including UUID swaps. Full-model -replacement uses common UUIDs as its correspondence boundary. It preserves -the accepted source layout; canonical interchange formatting/comments are not -adopted as a replacement authored workspace. Use source-project PUT for -authoring file layout and comments. A failed source writeback rejects the whole -write and never falls back to discarding authored files.

-

Old model-only envelopes are migrated to a deterministic source bundle in -memory when opened. Reading or an exact no-op does not rewrite their files or -advance revisions. Their next changed write persists both representations. -Startup rejects already-coupled envelopes whose source/model disagree or whose -source lacks stable IDs. The 256 MiB store budget charges complete accepted -envelopes, including the generated source of old envelopes before their next -save; an over-capacity old store is reported rather than silently pruned.

-
-
-

Queries and pagination

-

List filters combine using AND. Enum spelling is case-sensitive and comes -from the shared schema: examples include kind=Activity and layer=Oa. -Aliases match exactly; q is a Unicode-lowercase substring match on the -display name. Empty or invalid enum values are request errors.

-

Lists are ordered by UUID. limit defaults to 100 and accepts 1 through -1000. after is an exclusive last UUID. next_after is null at the end. -Project-scoped lists return a revision. Include that revision on following -pages to detect a changed model; 409 snapshot_changed means restart the -query. Supplying a past revision does not retrieve history. The project catalog -is a live list and does not have a multi-page snapshot guarantee.

-

A neighborhood request is:

-
{
-  "roots": ["1b37b9d7-9a95-4a51-8707-010878a82419"],
-  "direction": "both",
-  "relation_kinds": ["Allocation", "Interaction"],
-  "max_depth": 2,
-  "max_objects": 200,
-  "revision": 3
-}
-

Roots may identify elements or relations. The service traverses relation -source/target adjacency in the selected direction (incoming/outgoing/both), -preserves distinct parallel relations, and returns elements, relations, -revision and truncated. Empty kind filters permit all relation kinds. -Every returned relation includes its endpoint closure, including the endpoints -of a referenced relation. Closure records count against the object bound but -do not add traversal depth. Model ownership, process occurrence order and -non-endpoint references remain available in the returned records and complete -model; they are not silently treated as additional graph edges.

-

Use 1–100 roots, depth 0–4, and 1–1000 total returned objects. The cap must fit -the roots and their endpoint closure. A root closure that cannot fit is an -error; further omitted edges set truncated: true. A missing root is 422. -Deterministic UUID iteration makes repeated queries of one snapshot stable.

-
-
-

Transactions

-

The commands array contains the shared externally tagged Serde command -representation. It is not RFC 6902 JSON Patch. For example:

-
{
-  "commands": [
-    {"Rename": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Assess assistance"]},
-    {"Describe": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Review the request."]}
-  ],
-  "dry_run": true
-}
-

One-field variants use {"Delete":"uuid"}; tuple variants use arrays; -named-field variants use objects. For a new typed OA activity:

-
{
-  "CreateElement": {
-    "id": "1b37b9d7-9a95-4a51-8707-010878a82419",
-    "alias": "assess",
-    "name": "Assess assistance",
-    "description": "Review the request.",
-    "parent": null,
-    "body": {"Oa": "Activity"}
-  }
-}
-

CreateArtifact/EditArtifact and CreateTrace/EditTrace use the same -transaction boundary as engineering edits. Artifact URI, revision and fragment -strings are opaque references. No URI is fetched or verified by the service. -Model references and alias uniqueness are checked by the core.

-

At most 200 commands are allowed, counting nested Batch wrappers. Explicit -creation identities allow later commands in the batch to reference earlier -creations. The shared execute_batch applies all operations to one candidate -and validates the final state. A rejected operation or final structural error -rejects the whole request. Reference rewrites/deletion cleanup follow the same -core logic as desktop editing. Command preparation retains original-to-candidate -identity correspondence through UUID edits, swaps, deletion and reuse. Source -writeback uses that correspondence to preserve declaration ownership and comments.

-

Dry run still requires If-Match. Its response includes revision, -dry_run, changed, issues and the proposed model and -source_project. It validates source writeback as well as semantics and neither writes -nor reserves IDs. A committed response omits the proposed model and source -project. Commit must check the revision again. A no-op is successful with -changed: false and the original ETag.

-
-
-

Diagram port positions

-

NodePlacement.port_anchors is an optional list of -{"port":"<port UUID>","side":"Top","offset":0.25} records. Side is -Left, Right, Top, or Bottom; offset is a finite fraction from 0 through 1, -measured from top on vertical edges and from left on horizontal edges. -The port must be a direct child of the placement's semantic element and occur -at most once in its anchor list. Placement is presentation state; it changes -neither semantic ownership nor port direction. Other diagrams are independent.

-

The existing transaction endpoint accepts:

-
{"commands":[{"SetPortAnchor":{
-  "diagram":"<diagram UUID>","node":"<placement UUID>",
-  "anchor":{"port":"<port UUID>","side":"Top","offset":0.25}
-}}]}
-

SetPortAnchor upserts by port UUID. RemovePortAnchor takes -{diagram,node,port} and restores automatic positioning. Repeating a reset -is a no-op only while the referenced port still belongs to that placement's -element; missing or stale owners are rejected. Remove old anchors before -reparenting in the same atomic batch. Both commands support ordinary dry run, -ETag checks and source-project writeback. Rejected batches preserve the model, -authored files and revision. JSON/source imports may omit port_anchors; -it defaults to [] and empty lists are omitted on export. The normative -diagram semantics and source spelling are in language.rst section 5.

-
-
-

Contextual process and chain topology

-

Operational processes and system functional chains can combine work -contributions, exchange contributions, explicit controls and references to -reusable sequences. Each contribution is an ordinary Occurrence element -owned directly by the process/chain, with its stable ID in that owner's -steps list. OccurrenceBody includes, for example:

-
{"Action":{"action":"<work UUID>","lifeline":null}}
-{"Control":{"kind":"Choice"}}
-{"SequenceReference":{"sequence":"<process or chain UUID>"}}
-

ControlKind is Choice, Merge, Fork, Join or Loop. -RelationBody.SequenceLink connects contribution IDs in one owner and -contains source, target and a string guard. Conditions belong to -choice/loop paths; an explicit else guard can describe the other path. -The readable order of the steps list remains persistent, while links -express the explicit alternative, parallel and loop topology. The core checks -owner context, work/exchange endpoint agreement and recursive sequence reuse; -engineering diagnostics identify incomplete branch cardinality or paths. -Old exchange-only sequences without explicit links retain their linear meaning.

-

These records use the same create/edit transaction commands and source/model -replacement routes as other engineering records. For example, -GET /api/v1/projects/{id}/relations?kind=SequenceLink queries the links. -The source representation uses sequence @alias references. The service -does not execute conditions or simulate a process.

-
- -
-

Typed scenarios and message values

-

Scenario bodies store an explicit viewpoint. Occurrence Exchange bodies -store message_kind, optional correlated-call reply_to and contextual -values. Message kinds are Asynchronous, Call, Reply, Create, Delete, or -{"Timer":{"expression":"after 5 s"}}. Each value record contains a -carried DataField UUID in field, enclosing field UUID list path (default -[]) and textual expression. Identity is (path, field), allowing repeated uses -of a Record. Operation Call/Reply direction checks use the direct parameter; -Reference storage stops nested payload traversal. Expressions do not change the -shared exchange item's type or evaluate server-side.

-

ScenarioUse contains a referenced scenario and one-to-one lifeline -bindings records with referenced and local UUIDs. The core validates -viewpoint/layer compatibility, call/reply order and reversal, lifetime order, -carried fields, binding identity and recursive reuse through the same candidate -boundary used by the desktop. The normative details are in language.rst.

-

Existing schema-2 files may omit the new fields: missing viewpoint maps to -MixedLegacy and missing message fields to Asynchronous, null and []. Source -exports make those compatibility defaults explicit. This preserves original V2 -models while the release schema is being integrated; it does not silently -reinterpret old mixed lifelines as a strict new viewpoint.

-
-
-

Data and interface contracts

-

Schema4 uses typed bounded DataTypeDefinition and DataValue constructors, field -storage/defaults/directions, and single specialization. Exact JSON shapes are in -openapi.json and semantic rules and runnable source in language.rst section 4.5. -Interface membership is an identified InterfaceItemAllocation relation with -independent order/transmission/acquisition. DataAssociation relates Record types -with named, navigable, multiplicity-bearing ends; it does not add carried fields.

-

Use ordinary relation queries, transactions and artifact links, for example -GET /api/v1/projects/{id}/relations?kind=InterfaceItemAllocation. The service -and desktop use the same inheritance/path/default validation. Invalid bounds, -defaults, directions, cycles or contract-breaking deletions reject atomically, -preserving the accepted source/model, revision and stored bytes. Correct a related -contract in the same transaction when removing a used interface allocation.

-

Explicit schema1/2/3 migration preserves prior identities and typed ports. Reports -add semantic defaults, generated interface allocations and unresolved message -values. Old source3 bundles retain files/includes/comments during upgrade; all -current exports use schema7/source7. Reading an old envelope does not write it or -advance its ETag. Canonical exports omit transient migration reports.

-
-
-

Validation

-

GET returns current-model diagnostics. POST accepts either:

-
{"format":"json", "model": {"...":"complete native Project record"}}
-

or:

-
{"format":"arcadia", "source":"complete source document"}
-

or:

-
{"format":"source-project", "source_project":{"entry":"rescue.arcadia", "files":{"rescue.arcadia":"arcadia 7; project rescue \"Rescue\" {}"}}}
-

A well-formed validation request returns 200 with valid and issues even -if its candidate is invalid, including a model record that fails typed decoding. -Malformed request JSON or an invalid format/payload envelope is a 400. -Source diagnostics include one-based line and column according to the -shared language parser. Candidate validation does not replace current state. -Source-project validation locations refer to the submitted bytes before UUID -insertion. Successful source-project PUT diagnostics refer to the returned -accepted bundle. Missing IDs are not saved or adopted by validation.

-
-
-

Typed state machines and events

-

OA and SA Event records use the same SharedBody::Event and EventBody -contract as source and graphical editing. Signal carries same-layer item -references; Time specifies After/At and an expression; Change specifies a -condition expression. State vertices include Choice, Junction, Fork, Join, -ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate, as well as -State, Mode, Initial and Final. Query these through the ordinary kind -filters. No timer or executable state-machine service is implied.

-

StateTransition keeps trigger, guard and effect prose and adds -transition_kind (External/Local/Internal), triggers (Event or work UUIDs) -and effects (work UUIDs). The reference lists use UUIDs in JSON and aliases -in source. Missing new properties in staged schema-2 input hydrate as -External/[]/[] without changing prior text. These defaults are compatibility -behavior pending the distinct release schema, not a versionless expansion of -every future input contract.

-

Use one ordinary transaction to create an event and edit its referring -transition. Reidentification updates references, and deletion prunes signal -items and effect references. Removing the last typed trigger with no prior -trigger prose leaves a descriptive replacement reminder; it does not silently -introduce a completion transition. Invalid typed references reject the whole -candidate and preserve revision/ETag. All writes retain the same conditional -commit and disk persistence contract as other semantic records.

-
-
-

State diagram representation

-

Diagram.projection keeps the existing string values Overview, -Decomposition, Allocation, Traceability, FunctionalDataflow and -Capabilities. A dedicated state view instead uses the required payload:

-
{"StateMachine":{"machine":"11111111-1111-4111-8111-111111111111"}}
-

The UUID MUST identify an existing StateMachine in the diagram's layer. -Only that machine, its Regions and its state vertices may be placed, including -nested members. Empty and partial views are valid. No machine, Region, vertex -or placement is synthesized by accepting or reading a diagram. Source uses -projection { StateMachine { machine @modes; }; }; with the same reference.

-

Use the ordinary CreateDiagram or EditDiagram transaction commands, -JSON model import/replacement, or source/source-project writes. A bare -"StateMachine", missing or null machine, malformed UUID, or additional -payload field is malformed JSON model data (400). A missing, wrong-kind or -cross-layer machine produces the semantic issue state_diagram_context; -an incompatible placement produces state_diagram_member. These reject -writes with 422. Transaction rejections use the command_rejected problem -code and include the first core failure in detail; candidate validation -provides the structured issues collection. -Source syntax errors use the source rejection response (422) with locations. -The validation endpoint returns 200 with valid:false for a well-formed -invalid candidate, as described above.

-

When changing a diagram's machine or selecting StateMachine for an existing -diagram, remove or relocate incompatible placements in the same transaction. -Other projections continue to preserve hidden placements. Rejected writes -retain the accepted model, source bundle, revision and ETag. Reading a state -view has no semantic or persistence side effects.

-
-
-

Source-6 communication and saved views

-

The same import, replacement and command endpoints admit OA -CommunicationMean { source, target, oriented, items } and root-level -ExchangeCategory { family, exchanges } records. Communication means join -distinct OA entities/actors. Categories group same-layer exchanges as Work -or Participant without owning them. Existing legacy OA boundary exchanges -retain their identities and contracts; migration does not silently replace them.

-

Diagram.view_options is an optional closed object with defaults: empty -hidden_edges, All for relation_kinds, both category selections, -coverage and work_contexts, and Name for exchange_labels. -{"Only":[]} hides all relation and trace lines; {"Any":[]} preserves -an explicit empty category/context selection. Category members union within -each family, and independent filters intersect. Traceability permits -references from both layers; ordinary projections require the diagram's layer. -These presentation choices preserve semantic records, routes, placements and -the automatic port positions derived from unfiltered eligible exchanges.

-

Schema/source versions 1–5 reject the new constructors, category review -facts and explicit view_options field, including an empty object. Their -frozen readers migrate accepted content to schema/source 7 with default view -options. The OpenAPI document describes current records; it is not a permissive -union of historical input formats. Source-7 integration and release acceptance -are tracked separately in the release coverage audit.

-
-
-

Explicit interface-operation identity

-

OccurrenceBody.Exchange.operation is optional. Absent/null retains ordinary -channel semantics and is omitted in canonical output. {"allocation":null} -(or an empty object) retains explicit unresolved operation intent. A selected -{"allocation":"<InterfaceItemAllocation UUID>"} identifies that exact -allocation, including for parameterless operations sharing one channel. These -records are closed; unknown fields are rejected. Earlier schema/source versions -reject the operation field even when explicitly null and reject ContextualLink -and their new semantic facets. Migration never guesses an operation from names, -message values or a channel's item set.

-

Selection is valid only for SA BoundaryInterface Call/Asynchronous messages. -Core proves actual Standard component-port Requires/Provides roles, compatible -interfaces exposing the same allocation, actual channel direction and allowed -transmission protocol. Reply stores no operation and derives it from its exact -Call; a separate return channel still needs its own valid carried-item/endpoint -contract, including a void return. Selected message values use the exact operation -signature. Unresolved intent preserves live expressions and checks their paths -with an incomplete-signature warning; it does not substitute the channel union.

-

Allocation deletion clears selection to unresolved only within an otherwise -valid whole candidate. Removing the sole allocation can refuse atomically while -a channel still carries its now-unadmitted item; repair every affected channel -explicitly in the same batch. A surviving same-item allocation may keep a channel -valid, but is never silently selected. Reselection validates retained values -without pruning them. The service uses the same source/history boundary as -graphical editing. See language.rst4.3.1 for the complete contract.

-
-
-

Cross-layer transitions

-

Preview accepts the shared TransitionRequest record, including direction -(OaToSa or SaToOa), sources, dependency scope, mappings, selected fields, -conflict resolutions and optional destination diagram. All fields are explicit -in OpenAPI. At most 100 sources, 200 mappings, 200 communication endpoint -decisions, 200 operation omissions and 400 field resolutions are -accepted. A conflict-free plan must fit the same 200-command transaction limit -and model/source-capacity limits. A conflict-free preview also checks source -writeback, so its commands can update the same accepted authored workspace.

-

SA→OA abstraction of explicit operation intent requires operation_omissions -(omitted means empty). Each closed record contains the selected invocation's -source UUID and a nonempty rationale string. Missing, duplicate, -irrelevant or wrong-direction omissions reject. Reply derives its Call's -abstraction and has no second writable operation choice. Source operation -identity/values remain intact; target facts are mapped and validated. Preview -and Apply enforce the same 200 omission limit for ordinary and semantic transitions.

-

OA CommunicationMean maps to an SA BoundaryExchange through an explicit -communication_endpoints array (omitted means empty). Each decision identifies -the source communication means and both source_port and target_port. -Each port choice is either {"Reuse":{"port":"port-uuid"}} or -{"Create":{"id":"new-port-uuid","alias":"radio_port","name":"Radio port", "description":"","kind":"Standard","orientation":"Unset"}}. Created or -reused SA component ports must belong to the explicitly mapped participants. -These per-exchange choices do not replace participant correspondence with port -IDs. Missing or incompatible choices remain preview conflicts. Interface and -functional-port allocations use their existing explicit workflows; an engineer -may omit an unresolved allocation from this transition. Reverse transition maps -boundary ports through their owners to OA participants.

-

ExchangeCategory transitions preserve their family and map only explicitly -corresponding member exchanges. Missing member mappings are conflicts, not new -fabricated exchanges. Typed review exposes Category.Family and the -CategoryExchanges member collection; historical review facts remain exact.

-

The preview response is {"base_revision":1,"plan":{...}}. It contains -planned changes, conflicts, warnings and exact commands/IDs. It does not write -the model or persist a server-side plan. Apply accepts that entire response and -requires the original ETag in If-Match. It checks both base_revision and the -core's snapshot fingerprint before executing the shared transition validator. -Stale revisions fail with 412 and unresolved conflicts with 422. Successful -apply increments the revision once, preserving the planned identities.

-

A supplied plan is a client-owned command proposal. It is not cryptographically -authenticated as an untouched server preview; the same authenticated client -can submit ordinary typed commands. The core fingerprint is a change detector, -not a security digest. There is no plan token, expiration store or receipt.

-
-
-

Typed semantic transition review

-

The sibling semantic-transitions routes use the typed review semantics in language.rst sections 5.1–5.5. -The original prose transition routes keep -their existing contract. All new routes use JSON and the existing authentication, -capacity, worker and error boundaries.

-

Query accepts {"traces":["trace-uuid"],"references":{},"revision":1}. -references is an optional map of canonical OA UUIDs to SA UUIDs, including -historical deleted identities. It never guesses correspondences from names. -revision is optional; a mismatch returns 409. Select 1–100 unique existing -trace IDs; an empty or duplicate selection returns 400 and a missing trace -returns 404. The response includes the current revision and items containing -trace, oa, sa, typed facets, drift and linked_impacts. -Own and linked drift and their unreviewed flags remain distinct. Query reads one -immutable snapshot, does not refresh baselines, and does not fetch artifact URIs.

-

Preview accepts SemanticTransitionRequest:

-
{
-  "transition": {
-    "direction": "OaToSa",
-    "sources": ["40000000-0000-4000-8000-000000000002"],
-    "scope": "SelectedOnly",
-    "mappings": [{
-      "source": "40000000-0000-4000-8000-000000000002",
-      "target": "40000000-0000-4000-8000-000000000003",
-      "target_kind": null
-    }],
-    "fields": [], "resolutions": [], "destination_diagram": null
-  },
-  "traces": {}, "references": {},
-  "reviews": [{
-    "source": "40000000-0000-4000-8000-000000000002",
-    "resolutions": [
-      {"facet":{"Header":"Name"}, "decision":"TakeSource"},
-      {"facet":{"Data":"Unit"}, "decision":"KeepTarget"}
-    ],
-    "reset": []
-  }],
-  "candidates": [], "accepted_impacts": []
-}
-

The example maps an existing OA data type to its traced SA counterpart, takes -only its name and acknowledges intentionally different units. Other facets stay -unreviewed or retain their earlier drift. Select an explicit trace through the -traces map (source UUID to trace UUID) when multiple trace kinds connect the -pair. reset deliberately invalidates exact old selectors after changed -correspondence. The optional collections default to empty. Semantic header -decisions use typed facets; leave legacy transition.fields and -transition.resolutions empty.

-

Facet selectors are closed enums, for example {"Data":"IntegerMinimum"} -or {"Member":{"collection":"SignalItems","keys":{"oa":"oa-uuid","sa":"sa-uuid"}}}. -Annotation keys contain the entire path and field; enum literal keys are exact -strings. KeepTarget, TakeSource and Candidate operate on independently -selected facts. Candidate records use existing Element/Relation/ArtifactLink -types, must preserve identity/alias, and must equal precisely the result of -the selected facets. Constructor review covers its coupled introduced/removed -fields. Membership review does not silently acknowledge order; target-only -members remain independent. OpenAPI defines every selector and typed payload.

-

Preview requires If-Match and returns {"base_revision":1,"plan":{...}}. -The plan contains the original typed request, mapping plan, review facts, -acknowledged/retired selectors, linked impacts, exact prepared impacts, conflicts, -commands, and base/prepared fingerprints. A conflict-free preview also prepares -the complete authored source bundle and checks its model equivalence and capacity. -No preview changes the model, source, revision or stored bytes.

-

If ordinary preparation induces additional cleanup, the plan reports exact typed -before/after impacts and a conflict. Review those impacts, place the accepted -records in accepted_impacts, and request another preview. Acceptance cannot -override a contradictory selected facet. Missing mappings, ambiguous order, -duplicate decisions, invalid candidates or unresolved impacts cannot be applied. -This is the shared core planner and command preparation, with no server-specific -semantic mutation engine.

-

Apply accepts the entire preview envelope under the same If-Match. It checks -base_revision, the model fingerprint, unresolved conflicts, the prepared -candidate fingerprint, ordinary model validation and coupled source preparation, -then atomically stores one accepted model/source revision. The final store check -also catches a concurrent edit during preparation. Source-only edits invalidate -the preview through the shared ETag even when the model fingerprint is unchanged. -Stale ETag/revision returns 412; semantic rejection returns 422 with code -semantic_transition_rejected. Exact no-op candidates preserve revision. -Read and preview again after a successful changed apply before reviewing further.

-

A supplied plan remains a client-owned candidate. Fingerprints detect stale or -inconsistent proposals; they are not tamper-proof signatures, authentication -receipts or proof of original review authorship. Apply preserves preview-generated -identities and does not rerun the planner with fresh random IDs. Authenticated -clients already have ordinary model/source editing privileges. The server does -not persist plans, tokens or review sessions.

-

Requests allow 100 sources, trace choices and reviews; 200 mappings, candidates -and accepted impacts; 5,000 canonical reference mappings; and 5,000 facet -resolutions plus resets across all reviews. The legacy 400 prose-resolution -limit, 200 communication endpoint decisions, 200 operation omissions and -200-command transaction limit still apply. Query has the same 5,000 -reference-map limit. Core bounded linked traversal errors remain observable as -semantic rejection; no truncated result silently claims complete review.

-

Schema/source 5 adds TraceBaseline.semantic. Absence or null is Unreviewed; -migrating older prose baselines does not fabricate semantic history. The persisted -baseline contains only closed typed FacetReview facts, not whole candidate -records or a generic JSON value tree. Historical UUIDs remain historical, including -after deletion, and source uses explicit uuid tokens for them.

-
-
-

Error contract

-

All errors have Content-Type: application/problem+json. Example:

-
{
-  "type":"urn:arcadia:problem:revision_conflict",
-  "title":"Precondition Failed",
-  "status":412,
-  "code":"revision_conflict",
-  "detail":"Project changed. Reload and review the current model before retrying.",
-  "current_revision":3
-}
-

issues contain severity, code and message, and may include field, -object_id, line, column, source path, half-open UTF-8 byte span -(start, end), and related source locations (path/span). Lines and -columns are one-based; columns count Unicode scalars. Use -code for client decisions, not the English message. X-Request-ID is a -response correlation UUID. This is not an idempotency key or receipt address.

-
    -
  • 400: malformed JSON/envelope/query/UUID or unsupported ETag form.

  • -
  • 401: missing or incorrect configured bearer token; includes WWW-Authenticate.

  • -
  • 403: browser Origin is unsupported, or unauthenticated Host is not loopback.

  • -
  • 404: missing project or route.

  • -
  • 405: unsupported HTTP method.

  • -
  • 409: requested query revision changed or revision counter exhausted.

  • -
  • 412: stale write precondition.

  • -
  • 413: body, serialized model, stabilized source bundle or envelope exceeds its size limit.

  • -
  • 415: unsupported Content-Type or Content-Encoding.

  • -
  • 422: rejected source/model/command, model count limit, or missing graph root.

  • -
  • 428: missing If-Match.

  • -
  • 503: work slots full or filesystem/storage unavailable; Retry-After is present.

  • -
  • 504: request deadline; an admitted write outcome may be unknown.

  • -
  • 507: fixed store cap of 128 models or 256 MiB serialized envelopes reached.

  • -
  • 500: unexpected server/worker failure.

  • -
-

Deployment and persistence limitations are documented in server.md.

-
-
- - + + + + + + +Arcadia Studio model REST API + + + + +
+

Arcadia Studio model REST API

+ +

This is the HTTP contract for oa-server. The normative engineering model +and source language belong to language.rst. openapi.json describes the +JSON records and closed command vocabulary. The server uses the same Rust +Project, Command, parser/printer and validation implementation as the +desktop.

+
+

Identity, revision and writes

+

All model routes begin with /api/v1. A hosted project's immutable service +resource_id is different from its editable model.id. Each stored file +contains {resource_id, revision, model, source_project}. source_project +is the accepted entry filename and map of authored source files; compiling it +must reproduce model. The revision starts at 1, increments once per changed +accepted model/source pair, and does not change for an exact no-op. Comment-only +edits and file/entry changes count as changes even when the model is unchanged.

+

Existing-project writes require the exact quoted ETag returned by a project +read, for example:

+
If-Match: "p-6f7162cc-094c-4f30-9b70-3f6f355f0c7f-r1"
+

Missing preconditions return 428; stale preconditions return 412. Weak ETags, +wildcards and lists are not accepted on writes. GET of the envelope, model and +source/source-project supports If-None-Match and returns an empty 304 when unchanged. +Use ETags as opaque strings; revisions are unsigned integer counters in JSON.

+

Every accepted changed write is persisted before the response. A worker captures +a revision-qualified snapshot, then parses and validates its candidate outside +the catalog lock. Commit rechecks the exact ETag and atomically installs the +envelope under the lock. Another accepted edit during preparation therefore +returns 412 even when the original snapshot passed its initial revision check. +Malformed or rejected inputs preserve the accepted model. Incomplete-model +warnings permit work and are returned in issues.

+

There is no retained revision history, idempotency receipt, tombstone or +server undo. DELETE permanently removes the hosted envelope. Source/JSON +interchange exports contain the current model only; source-project export also +preserves the authored file layout and comments. They preserve persistent +aliases, diagrams, artifact references and OA/SA trace data. Artifact revision +strings refer to external artifacts, not past server snapshots.

+

A lost connection or 504 can occur after an admitted write has begun. That +write may still complete. Read current state before choosing a new action. +Do not automatically retry POST create/import or regenerate random IDs and +assume the original request failed.

+
+
+

Routes

+

Health and schema routes are public. All model routes require the configured +bearer token, if any. Non-loopback binding requires a configured token.

+ + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Endpoints

Method

Path

Behavior

GET

/health, /health/live, /health/ready

Responding process with successfully opened store.

GET

/openapi.json, /api/v1/openapi.json

OpenAPI 3.1 contract.

POST

/api/v1/projects

Create from {"name":"..."}; 201 + Location/ETag.

GET

/api/v1/projects

Summaries; limit, after, name substring q.

GET

/api/v1/projects/{id}

Complete current stored envelope.

PATCH

/api/v1/projects/{id}

Conditional rename using {"name":"..."}.

DELETE

/api/v1/projects/{id}

Conditional permanent removal; 204.

POST

/api/v1/projects/import

Raw model JSON or Arcadia source; new service ID.

POST

/api/v1/projects/import-source-project

Complete source bundle as JSON; stabilize missing IDs and create a resource.

GET / PUT

/api/v1/projects/{id}/model

Export or conditionally replace native model JSON.

GET / PUT

/api/v1/projects/{id}/source

Export or conditionally replace canonical UTF-8 source.

GET / PUT

/api/v1/projects/{id}/source-project

Export exact authored files or conditionally replace the complete bundle.

GET

/api/v1/projects/{id}/elements

Filter by kind, layer, alias, name q.

GET

/api/v1/projects/{id}/relations

Filter by kind, source, target, incident_to.

GET

/api/v1/projects/{id}/artifact-links

Artifact links; optional owner object filter.

GET

/api/v1/projects/{id}/traces

OA/SA traces; optional endpoint object filter.

POST

/api/v1/projects/{id}/query

Bounded directed neighborhood, no mutation.

GET / POST

/api/v1/projects/{id}/validation

Validate current model or a submitted candidate.

POST

/api/v1/projects/{id}/transactions

Conditional typed command batch, optional dry run.

POST

/api/v1/projects/{id}/transitions/preview

Preview a core transition against If-Match; return plan/base_revision.

POST

/api/v1/projects/{id}/transitions/apply

Conditionally apply that client-held plan and base_revision.

POST

/api/v1/projects/{id}/semantic-transitions/query

Read selected traces' typed facts, own drift and linked impacts.

POST

/api/v1/projects/{id}/semantic-transitions/preview

Prepare typed semantic decisions and source writeback without writing.

POST

/api/v1/projects/{id}/semantic-transitions/apply

Apply that prepared candidate under its original shared revision/ETag.

+

Content-Type: application/json is required for JSON requests. +text/plain; charset=utf-8 is used for Arcadia source. The service does not +guess formats from a filename. Unknown DTO fields, invalid UUIDs/enums, duplicate +model map keys and unsupported schemas are rejected. Maximum body size is +8 MiB. Accepted serialized model JSON and serialized source-bundle JSON each +have an 8 MiB cap, including IDs inserted during compilation. The stored +envelope allows both bounded representations plus small framing overhead. +Compressed or encoded request bodies and browser Origin requests are +not supported.

+

Model admission permits at most 5,000 elements plus relations, 20,000 total +persistent records (including the project, views, placements, routes and manual +port anchors), and 50,000 ordered step references. Source declaration counts and decoded JSON record +counts are checked before structural validation. Legacy import counts the +occurrences and placements migration will generate before validating or +migrating that model. Exceeding these budgets returns 422 model_limit; +candidate validation reports the same diagnostic in its 200 response. +Parsing, validation and query processing use snapshot workers, so this work +does not hold the catalog lock or serialize unrelated model preparation.

+

Import accepts schema-1 JSON through the shared explicit migration adapter, +as well as schema-2, schema-3, schema-4, schema-5, schema-6 and current schema-7 JSON. Source import uses the shared language parser +and its supported migrations. Export uses schema 7 / arcadia 7. Native JSON +replacement accepts supported older schemas through the same explicit migration. +Only documented query parameters are accepted. In particular, appending +?revision=... to model/source export is an error, not historical retrieval.

+
+
+

Authored source projects

+

A source bundle contains entry and a map of files. For example:

+
{
+  "entry": "rescue.arcadia",
+  "files": {
+    "rescue.arcadia": "arcadia 7; project rescue \"Rescue\" { include \"oa/work.arcadia\"; }\n",
+    "oa/work.arcadia": "// Engineering rationale\noa_activity assess \"Assess assistance\" {}\n"
+  }
+}
+

The entry is a root filename. All keys are canonical, portable, slash-separated +relative paths ending in .arcadia. Includes resolve only against these +submitted strings. They never read the service filesystem, URLs, artifact +targets or another hosted resource. All submitted files must be reachable; +missing files, cycles, duplicate keys, case collisions and escaping paths are +rejected. The bundle permits at most 256 files, 32 include edges along a path, +and 1,024 include edges in total. Model and source-byte budgets apply across +the complete bundle, not separately per file. Current bundle syntax is source7; source3/4/5/6 bundles migrate explicitly while +preserving their files/includes/comments. V1/V2 single-file migration remains +available through interchange import.

+

Compilation inserts UUIDs into headers that omit them. Import returns 201 with +Location/ETag; GET source-project retrieves the stabilized accepted bundle. +PUT returns revision, changed, located issues and that accepted +source_project. Preserve the returned IDs before subsequent editing. +With explicit IDs, untouched text, comments, line endings, entry name and module +ownership are preserved exactly. Re-submitting the same accepted bundle is a +no-op. Removing a file means submitting a complete valid bundle without that +file and its includes; no files are created or deleted on the service host.

+

All typed command, transition, rename and model-interchange writes update this +same accepted source project. Existing declarations and reference comments +follow command identity correspondence, including UUID swaps. Full-model +replacement uses common UUIDs as its correspondence boundary. It preserves +the accepted source layout; canonical interchange formatting/comments are not +adopted as a replacement authored workspace. Use source-project PUT for +authoring file layout and comments. A failed source writeback rejects the whole +write and never falls back to discarding authored files.

+

Old model-only envelopes are migrated to a deterministic source bundle in +memory when opened. Reading or an exact no-op does not rewrite their files or +advance revisions. Their next changed write persists both representations. +Startup rejects already-coupled envelopes whose source/model disagree or whose +source lacks stable IDs. The 256 MiB store budget charges complete accepted +envelopes, including the generated source of old envelopes before their next +save; an over-capacity old store is reported rather than silently pruned.

+
+
+

Queries and pagination

+

List filters combine using AND. Enum spelling is case-sensitive and comes +from the shared schema: examples include kind=Activity and layer=Oa. +Aliases match exactly; q is a Unicode-lowercase substring match on the +display name. Empty or invalid enum values are request errors.

+

Lists are ordered by UUID. limit defaults to 100 and accepts 1 through +1000. after is an exclusive last UUID. next_after is null at the end. +Project-scoped lists return a revision. Include that revision on following +pages to detect a changed model; 409 snapshot_changed means restart the +query. Supplying a past revision does not retrieve history. The project catalog +is a live list and does not have a multi-page snapshot guarantee.

+

A neighborhood request is:

+
{
+  "roots": ["1b37b9d7-9a95-4a51-8707-010878a82419"],
+  "direction": "both",
+  "relation_kinds": ["Allocation", "Interaction"],
+  "max_depth": 2,
+  "max_objects": 200,
+  "revision": 3
+}
+

Roots may identify elements or relations. The service traverses relation +source/target adjacency in the selected direction (incoming/outgoing/both), +preserves distinct parallel relations, and returns elements, relations, +revision and truncated. Empty kind filters permit all relation kinds. +Every returned relation includes its endpoint closure, including the endpoints +of a referenced relation. Closure records count against the object bound but +do not add traversal depth. Model ownership, process occurrence order and +non-endpoint references remain available in the returned records and complete +model; they are not silently treated as additional graph edges.

+

Use 1–100 roots, depth 0–4, and 1–1000 total returned objects. The cap must fit +the roots and their endpoint closure. A root closure that cannot fit is an +error; further omitted edges set truncated: true. A missing root is 422. +Deterministic UUID iteration makes repeated queries of one snapshot stable.

+
+
+

Transactions

+

The commands array contains the shared externally tagged Serde command +representation. It is not RFC 6902 JSON Patch. For example:

+
{
+  "commands": [
+    {"Rename": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Assess assistance"]},
+    {"Describe": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Review the request."]}
+  ],
+  "dry_run": true
+}
+

One-field variants use {"Delete":"uuid"}; tuple variants use arrays; +named-field variants use objects. For a new typed OA activity:

+
{
+  "CreateElement": {
+    "id": "1b37b9d7-9a95-4a51-8707-010878a82419",
+    "alias": "assess",
+    "name": "Assess assistance",
+    "description": "Review the request.",
+    "parent": null,
+    "body": {"Oa": "Activity"}
+  }
+}
+

CreateArtifact/EditArtifact and CreateTrace/EditTrace use the same +transaction boundary as engineering edits. Artifact URI, revision and fragment +strings are opaque references. No URI is fetched or verified by the service. +Model references and alias uniqueness are checked by the core.

+

At most 200 commands are allowed, counting nested Batch wrappers. Explicit +creation identities allow later commands in the batch to reference earlier +creations. The shared execute_batch applies all operations to one candidate +and validates the final state. A rejected operation or final structural error +rejects the whole request. Reference rewrites/deletion cleanup follow the same +core logic as desktop editing. Command preparation retains original-to-candidate +identity correspondence through UUID edits, swaps, deletion and reuse. Source +writeback uses that correspondence to preserve declaration ownership and comments.

+

Dry run still requires If-Match. Its response includes revision, +dry_run, changed, issues and the proposed model and +source_project. It validates source writeback as well as semantics and neither writes +nor reserves IDs. A committed response omits the proposed model and source +project. Commit must check the revision again. A no-op is successful with +changed: false and the original ETag.

+
+
+

Diagram port positions

+

NodePlacement.port_anchors is an optional list of +{"port":"<port UUID>","side":"Top","offset":0.25} records. Side is +Left, Right, Top, or Bottom; offset is a finite fraction from 0 through 1, +measured from top on vertical edges and from left on horizontal edges. +The port must be a direct child of the placement's semantic element and occur +at most once in its anchor list. Placement is presentation state; it changes +neither semantic ownership nor port direction. Other diagrams are independent.

+

The existing transaction endpoint accepts:

+
{"commands":[{"SetPortAnchor":{
+  "diagram":"<diagram UUID>","node":"<placement UUID>",
+  "anchor":{"port":"<port UUID>","side":"Top","offset":0.25}
+}}]}
+

SetPortAnchor upserts by port UUID. RemovePortAnchor takes +{diagram,node,port} and restores automatic positioning. Repeating a reset +is a no-op only while the referenced port still belongs to that placement's +element; missing or stale owners are rejected. Remove old anchors before +reparenting in the same atomic batch. Both commands support ordinary dry run, +ETag checks and source-project writeback. Rejected batches preserve the model, +authored files and revision. JSON/source imports may omit port_anchors; +it defaults to [] and empty lists are omitted on export. The normative +diagram semantics and source spelling are in language.rst section 5.

+
+
+

Contextual process and chain topology

+

Operational processes and system functional chains can combine work +contributions, exchange contributions, explicit controls and references to +reusable sequences. Each contribution is an ordinary Occurrence element +owned directly by the process/chain, with its stable ID in that owner's +steps list. OccurrenceBody includes, for example:

+
{"Action":{"action":"<work UUID>","lifeline":null}}
+{"Control":{"kind":"Choice"}}
+{"SequenceReference":{"sequence":"<process or chain UUID>"}}
+

ControlKind is Choice, Merge, Fork, Join or Loop. +RelationBody.SequenceLink connects contribution IDs in one owner and +contains source, target and a string guard. Conditions belong to +choice/loop paths; an explicit else guard can describe the other path. +The readable order of the steps list remains persistent, while links +express the explicit alternative, parallel and loop topology. The core checks +owner context, work/exchange endpoint agreement and recursive sequence reuse; +engineering diagnostics identify incomplete branch cardinality or paths. +Old exchange-only sequences without explicit links retain their linear meaning.

+

These records use the same create/edit transaction commands and source/model +replacement routes as other engineering records. For example, +GET /api/v1/projects/{id}/relations?kind=SequenceLink queries the links. +The source representation uses sequence @alias references. The service +does not execute conditions or simulate a process.

+
+ +
+

Typed scenarios and message values

+

Scenario bodies store an explicit viewpoint. Occurrence Exchange bodies +store message_kind, optional correlated-call reply_to and contextual +values. Message kinds are Asynchronous, Call, Reply, Create, Delete, or +{"Timer":{"expression":"after 5 s"}}. Each value record contains a +carried DataField UUID in field, enclosing field UUID list path (default +[]) and textual expression. Identity is (path, field), allowing repeated uses +of a Record. Operation Call/Reply direction checks use the direct parameter; +Reference storage stops nested payload traversal. Expressions do not change the +shared exchange item's type or evaluate server-side.

+

ScenarioUse contains a referenced scenario and one-to-one lifeline +bindings records with referenced and local UUIDs. The core validates +viewpoint/layer compatibility, call/reply order and reversal, lifetime order, +carried fields, binding identity and recursive reuse through the same candidate +boundary used by the desktop. The normative details are in language.rst.

+

Existing schema-2 files may omit the new fields: missing viewpoint maps to +MixedLegacy and missing message fields to Asynchronous, null and []. Source +exports make those compatibility defaults explicit. This preserves original V2 +models while the release schema is being integrated; it does not silently +reinterpret old mixed lifelines as a strict new viewpoint.

+
+
+

Data and interface contracts

+

Schema4 uses typed bounded DataTypeDefinition and DataValue constructors, field +storage/defaults/directions, and single specialization. Exact JSON shapes are in +openapi.json and semantic rules and runnable source in language.rst section 4.5. +Interface membership is an identified InterfaceItemAllocation relation with +independent order/transmission/acquisition. DataAssociation relates Record types +with named, navigable, multiplicity-bearing ends; it does not add carried fields.

+

Use ordinary relation queries, transactions and artifact links, for example +GET /api/v1/projects/{id}/relations?kind=InterfaceItemAllocation. The service +and desktop use the same inheritance/path/default validation. Invalid bounds, +defaults, directions, cycles or contract-breaking deletions reject atomically, +preserving the accepted source/model, revision and stored bytes. Correct a related +contract in the same transaction when removing a used interface allocation.

+

Explicit schema1/2/3 migration preserves prior identities and typed ports. Reports +add semantic defaults, generated interface allocations and unresolved message +values. Old source3 bundles retain files/includes/comments during upgrade; all +current exports use schema7/source7. Reading an old envelope does not write it or +advance its ETag. Canonical exports omit transient migration reports.

+
+
+

Validation

+

GET returns current-model diagnostics. POST accepts either:

+
{"format":"json", "model": {"...":"complete native Project record"}}
+

or:

+
{"format":"arcadia", "source":"complete source document"}
+

or:

+
{"format":"source-project", "source_project":{"entry":"rescue.arcadia", "files":{"rescue.arcadia":"arcadia 7; project rescue \"Rescue\" {}"}}}
+

A well-formed validation request returns 200 with valid and issues even +if its candidate is invalid, including a model record that fails typed decoding. +Malformed request JSON or an invalid format/payload envelope is a 400. +Source diagnostics include one-based line and column according to the +shared language parser. Candidate validation does not replace current state. +Source-project validation locations refer to the submitted bytes before UUID +insertion. Successful source-project PUT diagnostics refer to the returned +accepted bundle. Missing IDs are not saved or adopted by validation.

+
+
+

Typed state machines and events

+

OA and SA Event records use the same SharedBody::Event and EventBody +contract as source and graphical editing. Signal carries same-layer item +references; Time specifies After/At and an expression; Change specifies a +condition expression. State vertices include Choice, Junction, Fork, Join, +ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate, as well as +State, Mode, Initial and Final. Query these through the ordinary kind +filters. No timer or executable state-machine service is implied.

+

StateTransition keeps trigger, guard and effect prose and adds +transition_kind (External/Local/Internal), triggers (Event or work UUIDs) +and effects (work UUIDs). The reference lists use UUIDs in JSON and aliases +in source. Missing new properties in staged schema-2 input hydrate as +External/[]/[] without changing prior text. These defaults are compatibility +behavior pending the distinct release schema, not a versionless expansion of +every future input contract.

+

Use one ordinary transaction to create an event and edit its referring +transition. Reidentification updates references, and deletion prunes signal +items and effect references. Removing the last typed trigger with no prior +trigger prose leaves a descriptive replacement reminder; it does not silently +introduce a completion transition. Invalid typed references reject the whole +candidate and preserve revision/ETag. All writes retain the same conditional +commit and disk persistence contract as other semantic records.

+
+
+

State diagram representation

+

Diagram.projection keeps the existing string values Overview, +Decomposition, Allocation, Traceability, FunctionalDataflow and +Capabilities. A dedicated state view instead uses the required payload:

+
{"StateMachine":{"machine":"11111111-1111-4111-8111-111111111111"}}
+

The UUID MUST identify an existing StateMachine in the diagram's layer. +Only that machine, its Regions and its state vertices may be placed, including +nested members. Empty and partial views are valid. No machine, Region, vertex +or placement is synthesized by accepting or reading a diagram. Source uses +projection { StateMachine { machine @modes; }; }; with the same reference.

+

Use the ordinary CreateDiagram or EditDiagram transaction commands, +JSON model import/replacement, or source/source-project writes. A bare +"StateMachine", missing or null machine, malformed UUID, or additional +payload field is malformed JSON model data (400). A missing, wrong-kind or +cross-layer machine produces the semantic issue state_diagram_context; +an incompatible placement produces state_diagram_member. These reject +writes with 422. Transaction rejections use the command_rejected problem +code and include the first core failure in detail; candidate validation +provides the structured issues collection. +Source syntax errors use the source rejection response (422) with locations. +The validation endpoint returns 200 with valid:false for a well-formed +invalid candidate, as described above.

+

When changing a diagram's machine or selecting StateMachine for an existing +diagram, remove or relocate incompatible placements in the same transaction. +Other projections continue to preserve hidden placements. Rejected writes +retain the accepted model, source bundle, revision and ETag. Reading a state +view has no semantic or persistence side effects.

+
+
+

Source-6 communication and saved views

+

The same import, replacement and command endpoints admit OA +CommunicationMean { source, target, oriented, items } and root-level +ExchangeCategory { family, exchanges } records. Communication means join +distinct OA entities/actors. Categories group same-layer exchanges as Work +or Participant without owning them. Existing legacy OA boundary exchanges +retain their identities and contracts; migration does not silently replace them.

+

Diagram.view_options is an optional closed object with defaults: empty +hidden_edges, All for relation_kinds, both category selections, +coverage and work_contexts, and Name for exchange_labels. +{"Only":[]} hides all relation and trace lines; {"Any":[]} preserves +an explicit empty category/context selection. Category members union within +each family, and independent filters intersect. Traceability permits +references from both layers; ordinary projections require the diagram's layer. +These presentation choices preserve semantic records, routes, placements and +the automatic port positions derived from unfiltered eligible exchanges.

+

Schema/source versions 1–5 reject the new constructors, category review +facts and explicit view_options field, including an empty object. Their +frozen readers migrate accepted content to schema/source 7 with default view +options. The OpenAPI document describes current records; it is not a permissive +union of historical input formats. Source-7 integration and release acceptance +are tracked separately in the release coverage audit.

+
+
+

Explicit interface-operation identity

+

OccurrenceBody.Exchange.operation is optional. Absent/null retains ordinary +channel semantics and is omitted in canonical output. {"allocation":null} +(or an empty object) retains explicit unresolved operation intent. A selected +{"allocation":"<InterfaceItemAllocation UUID>"} identifies that exact +allocation, including for parameterless operations sharing one channel. These +records are closed; unknown fields are rejected. Earlier schema/source versions +reject the operation field even when explicitly null and reject ContextualLink +and their new semantic facets. Migration never guesses an operation from names, +message values or a channel's item set.

+

Selection is valid only for SA BoundaryInterface Call/Asynchronous messages. +Core proves actual Standard component-port Requires/Provides roles, compatible +interfaces exposing the same allocation, actual channel direction and allowed +transmission protocol. Reply stores no operation and derives it from its exact +Call; a separate return channel still needs its own valid carried-item/endpoint +contract, including a void return. Selected message values use the exact operation +signature. Unresolved intent preserves live expressions and checks their paths +with an incomplete-signature warning; it does not substitute the channel union.

+

Allocation deletion clears selection to unresolved only within an otherwise +valid whole candidate. Removing the sole allocation can refuse atomically while +a channel still carries its now-unadmitted item; repair every affected channel +explicitly in the same batch. A surviving same-item allocation may keep a channel +valid, but is never silently selected. Reselection validates retained values +without pruning them. The service uses the same source/history boundary as +graphical editing. See language.rst4.3.1 for the complete contract.

+
+
+

Cross-layer transitions

+

Preview accepts the shared TransitionRequest record, including direction +(OaToSa or SaToOa), sources, dependency scope, mappings, selected fields, +conflict resolutions and optional destination diagram. All fields are explicit +in OpenAPI. At most 100 sources, 200 mappings, 200 communication endpoint +decisions, 200 operation omissions and 400 field resolutions are +accepted. A conflict-free plan must fit the same 200-command transaction limit +and model/source-capacity limits. A conflict-free preview also checks source +writeback, so its commands can update the same accepted authored workspace.

+

SA→OA abstraction of explicit operation intent requires operation_omissions +(omitted means empty). Each closed record contains the selected invocation's +source UUID and a nonempty rationale string. Missing, duplicate, +irrelevant or wrong-direction omissions reject. Reply derives its Call's +abstraction and has no second writable operation choice. Source operation +identity/values remain intact; target facts are mapped and validated. Preview +and Apply enforce the same 200 omission limit for ordinary and semantic transitions.

+

OA CommunicationMean maps to an SA BoundaryExchange through an explicit +communication_endpoints array (omitted means empty). Each decision identifies +the source communication means and both source_port and target_port. +Each port choice is either {"Reuse":{"port":"port-uuid"}} or +{"Create":{"id":"new-port-uuid","alias":"radio_port","name":"Radio port", "description":"","kind":"Standard","orientation":"Unset"}}. Created or +reused SA component ports must belong to the explicitly mapped participants. +These per-exchange choices do not replace participant correspondence with port +IDs. Missing or incompatible choices remain preview conflicts. Interface and +functional-port allocations use their existing explicit workflows; an engineer +may omit an unresolved allocation from this transition. Reverse transition maps +boundary ports through their owners to OA participants.

+

ExchangeCategory transitions preserve their family and map only explicitly +corresponding member exchanges. Missing member mappings are conflicts, not new +fabricated exchanges. Typed review exposes Category.Family and the +CategoryExchanges member collection; historical review facts remain exact.

+

The preview response is {"base_revision":1,"plan":{...}}. It contains +planned changes, conflicts, warnings and exact commands/IDs. It does not write +the model or persist a server-side plan. Apply accepts that entire response and +requires the original ETag in If-Match. It checks both base_revision and the +core's snapshot fingerprint before executing the shared transition validator. +Stale revisions fail with 412 and unresolved conflicts with 422. Successful +apply increments the revision once, preserving the planned identities.

+

A supplied plan is a client-owned command proposal. It is not cryptographically +authenticated as an untouched server preview; the same authenticated client +can submit ordinary typed commands. The core fingerprint is a change detector, +not a security digest. There is no plan token, expiration store or receipt.

+
+
+

Typed semantic transition review

+

The sibling semantic-transitions routes use the typed review semantics in language.rst sections 5.1–5.5. +The original prose transition routes keep +their existing contract. All new routes use JSON and the existing authentication, +capacity, worker and error boundaries.

+

Query accepts {"traces":["trace-uuid"],"references":{},"revision":1}. +references is an optional map of canonical OA UUIDs to SA UUIDs, including +historical deleted identities. It never guesses correspondences from names. +revision is optional; a mismatch returns 409. Select 1–100 unique existing +trace IDs; an empty or duplicate selection returns 400 and a missing trace +returns 404. The response includes the current revision and items containing +trace, oa, sa, typed facets, drift and linked_impacts. +Own and linked drift and their unreviewed flags remain distinct. Query reads one +immutable snapshot, does not refresh baselines, and does not fetch artifact URIs.

+

Preview accepts SemanticTransitionRequest:

+
{
+  "transition": {
+    "direction": "OaToSa",
+    "sources": ["40000000-0000-4000-8000-000000000002"],
+    "scope": "SelectedOnly",
+    "mappings": [{
+      "source": "40000000-0000-4000-8000-000000000002",
+      "target": "40000000-0000-4000-8000-000000000003",
+      "target_kind": null
+    }],
+    "fields": [], "resolutions": [], "destination_diagram": null
+  },
+  "traces": {}, "references": {},
+  "reviews": [{
+    "source": "40000000-0000-4000-8000-000000000002",
+    "resolutions": [
+      {"facet":{"Header":"Name"}, "decision":"TakeSource"},
+      {"facet":{"Data":"Unit"}, "decision":"KeepTarget"}
+    ],
+    "reset": []
+  }],
+  "candidates": [], "accepted_impacts": []
+}
+

The example maps an existing OA data type to its traced SA counterpart, takes +only its name and acknowledges intentionally different units. Other facets stay +unreviewed or retain their earlier drift. Select an explicit trace through the +traces map (source UUID to trace UUID) when multiple trace kinds connect the +pair. reset deliberately invalidates exact old selectors after changed +correspondence. The optional collections default to empty. Semantic header +decisions use typed facets; leave legacy transition.fields and +transition.resolutions empty.

+

Facet selectors are closed enums, for example {"Data":"IntegerMinimum"} +or {"Member":{"collection":"SignalItems","keys":{"oa":"oa-uuid","sa":"sa-uuid"}}}. +Annotation keys contain the entire path and field; enum literal keys are exact +strings. KeepTarget, TakeSource and Candidate operate on independently +selected facts. Candidate records use existing Element/Relation/ArtifactLink +types, must preserve identity/alias, and must equal precisely the result of +the selected facets. Constructor review covers its coupled introduced/removed +fields. Membership review does not silently acknowledge order; target-only +members remain independent. OpenAPI defines every selector and typed payload.

+

Preview requires If-Match and returns {"base_revision":1,"plan":{...}}. +The plan contains the original typed request, mapping plan, review facts, +acknowledged/retired selectors, linked impacts, exact prepared impacts, conflicts, +commands, and base/prepared fingerprints. A conflict-free preview also prepares +the complete authored source bundle and checks its model equivalence and capacity. +No preview changes the model, source, revision or stored bytes.

+

If ordinary preparation induces additional cleanup, the plan reports exact typed +before/after impacts and a conflict. Review those impacts, place the accepted +records in accepted_impacts, and request another preview. Acceptance cannot +override a contradictory selected facet. Missing mappings, ambiguous order, +duplicate decisions, invalid candidates or unresolved impacts cannot be applied. +This is the shared core planner and command preparation, with no server-specific +semantic mutation engine.

+

Apply accepts the entire preview envelope under the same If-Match. It checks +base_revision, the model fingerprint, unresolved conflicts, the prepared +candidate fingerprint, ordinary model validation and coupled source preparation, +then atomically stores one accepted model/source revision. The final store check +also catches a concurrent edit during preparation. Source-only edits invalidate +the preview through the shared ETag even when the model fingerprint is unchanged. +Stale ETag/revision returns 412; semantic rejection returns 422 with code +semantic_transition_rejected. Exact no-op candidates preserve revision. +Read and preview again after a successful changed apply before reviewing further.

+

A supplied plan remains a client-owned candidate. Fingerprints detect stale or +inconsistent proposals; they are not tamper-proof signatures, authentication +receipts or proof of original review authorship. Apply preserves preview-generated +identities and does not rerun the planner with fresh random IDs. Authenticated +clients already have ordinary model/source editing privileges. The server does +not persist plans, tokens or review sessions.

+

Requests allow 100 sources, trace choices and reviews; 200 mappings, candidates +and accepted impacts; 5,000 canonical reference mappings; and 5,000 facet +resolutions plus resets across all reviews. The legacy 400 prose-resolution +limit, 200 communication endpoint decisions, 200 operation omissions and +200-command transaction limit still apply. Query has the same 5,000 +reference-map limit. Core bounded linked traversal errors remain observable as +semantic rejection; no truncated result silently claims complete review.

+

Schema/source 5 adds TraceBaseline.semantic. Absence or null is Unreviewed; +migrating older prose baselines does not fabricate semantic history. The persisted +baseline contains only closed typed FacetReview facts, not whole candidate +records or a generic JSON value tree. Historical UUIDs remain historical, including +after deletion, and source uses explicit uuid tokens for them.

+
+
+

Error contract

+

All errors have Content-Type: application/problem+json. Example:

+
{
+  "type":"urn:arcadia:problem:revision_conflict",
+  "title":"Precondition Failed",
+  "status":412,
+  "code":"revision_conflict",
+  "detail":"Project changed. Reload and review the current model before retrying.",
+  "current_revision":3
+}
+

issues contain severity, code and message, and may include field, +object_id, line, column, source path, half-open UTF-8 byte span +(start, end), and related source locations (path/span). Lines and +columns are one-based; columns count Unicode scalars. Use +code for client decisions, not the English message. X-Request-ID is a +response correlation UUID. This is not an idempotency key or receipt address.

+
    +
  • 400: malformed JSON/envelope/query/UUID or unsupported ETag form.

  • +
  • 401: missing or incorrect configured bearer token; includes WWW-Authenticate.

  • +
  • 403: browser Origin is unsupported, or unauthenticated Host is not loopback.

  • +
  • 404: missing project or route.

  • +
  • 405: unsupported HTTP method.

  • +
  • 409: requested query revision changed or revision counter exhausted.

  • +
  • 412: stale write precondition.

  • +
  • 413: body, serialized model, stabilized source bundle or envelope exceeds its size limit.

  • +
  • 415: unsupported Content-Type or Content-Encoding.

  • +
  • 422: rejected source/model/command, model count limit, or missing graph root.

  • +
  • 428: missing If-Match.

  • +
  • 503: work slots full or filesystem/storage unavailable; Retry-After is present.

  • +
  • 504: request deadline; an admitted write outcome may be unknown.

  • +
  • 507: fixed store cap of 128 models or 256 MiB serialized envelopes reached.

  • +
  • 500: unexpected server/worker failure.

  • +
+

Deployment and persistence limitations are documented in server.md.

+
+
+ + diff --git a/docs/api.rst b/docs/api.rst index ef49fd7e..4aaf6cf7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,782 +1,782 @@ -Arcadia Studio model REST API -============================= - -This is the HTTP contract for ``oa-server``. The normative engineering model -and source language belong to ``language.rst``. ``openapi.json`` describes the -JSON records and closed command vocabulary. The server uses the same Rust -``Project``, ``Command``, parser/printer and validation implementation as the -desktop. - -Identity, revision and writes ------------------------------ - -All model routes begin with ``/api/v1``. A hosted project's immutable service -``resource_id`` is different from its editable ``model.id``. Each stored file -contains ``{resource_id, revision, model, source_project}``. ``source_project`` -is the accepted entry filename and map of authored source files; compiling it -must reproduce ``model``. The revision starts at 1, increments once per changed -accepted model/source pair, and does not change for an exact no-op. Comment-only -edits and file/entry changes count as changes even when the model is unchanged. - -Existing-project writes require the exact quoted ETag returned by a project -read, for example:: - - If-Match: "p-6f7162cc-094c-4f30-9b70-3f6f355f0c7f-r1" - -Missing preconditions return 428; stale preconditions return 412. Weak ETags, -wildcards and lists are not accepted on writes. GET of the envelope, model and -source/source-project supports ``If-None-Match`` and returns an empty 304 when unchanged. -Use ETags as opaque strings; revisions are unsigned integer counters in JSON. - -Every accepted changed write is persisted before the response. A worker captures -a revision-qualified snapshot, then parses and validates its candidate outside -the catalog lock. Commit rechecks the exact ETag and atomically installs the -envelope under the lock. Another accepted edit during preparation therefore -returns 412 even when the original snapshot passed its initial revision check. -Malformed or rejected inputs preserve the accepted model. Incomplete-model -warnings permit work and are returned in ``issues``. - -There is no retained revision history, idempotency receipt, tombstone or -server undo. DELETE permanently removes the hosted envelope. Source/JSON -interchange exports contain the current model only; source-project export also -preserves the authored file layout and comments. They preserve persistent -aliases, diagrams, artifact references and OA/SA trace data. Artifact revision -strings refer to external artifacts, not past server snapshots. - -A lost connection or 504 can occur after an admitted write has begun. That -write may still complete. Read current state before choosing a new action. -Do not automatically retry POST create/import or regenerate random IDs and -assume the original request failed. - -Routes ------- - -Health and schema routes are public. All model routes require the configured -bearer token, if any. Non-loopback binding requires a configured token. - -.. list-table:: Endpoints - :header-rows: 1 - :widths: 12 41 47 - - * - Method - - Path - - Behavior - * - GET - - ``/health``, ``/health/live``, ``/health/ready`` - - Responding process with successfully opened store. - * - GET - - ``/openapi.json``, ``/api/v1/openapi.json`` - - OpenAPI 3.1 contract. - * - POST - - ``/api/v1/projects`` - - Create from ``{"name":"..."}``; 201 + Location/ETag. - * - GET - - ``/api/v1/projects`` - - Summaries; ``limit``, ``after``, name substring ``q``. - * - GET - - ``/api/v1/projects/{id}`` - - Complete current stored envelope. - * - PATCH - - ``/api/v1/projects/{id}`` - - Conditional rename using ``{"name":"..."}``. - * - DELETE - - ``/api/v1/projects/{id}`` - - Conditional permanent removal; 204. - * - POST - - ``/api/v1/projects/import`` - - Raw model JSON or Arcadia source; new service ID. - * - POST - - ``/api/v1/projects/import-source-project`` - - Complete source bundle as JSON; stabilize missing IDs and create a resource. - * - GET / PUT - - ``/api/v1/projects/{id}/model`` - - Export or conditionally replace native model JSON. - * - GET / PUT - - ``/api/v1/projects/{id}/source`` - - Export or conditionally replace canonical UTF-8 source. - * - GET / PUT - - ``/api/v1/projects/{id}/source-project`` - - Export exact authored files or conditionally replace the complete bundle. - * - GET - - ``/api/v1/projects/{id}/elements`` - - Filter by ``kind``, ``layer``, ``alias``, name ``q``. - * - GET - - ``/api/v1/projects/{id}/relations`` - - Filter by ``kind``, ``source``, ``target``, ``incident_to``. - * - GET - - ``/api/v1/projects/{id}/artifact-links`` - - Artifact links; optional owner ``object`` filter. - * - GET - - ``/api/v1/projects/{id}/traces`` - - OA/SA traces; optional endpoint ``object`` filter. - * - POST - - ``/api/v1/projects/{id}/query`` - - Bounded directed neighborhood, no mutation. - * - GET / POST - - ``/api/v1/projects/{id}/validation`` - - Validate current model or a submitted candidate. - * - POST - - ``/api/v1/projects/{id}/transactions`` - - Conditional typed command batch, optional dry run. - * - POST - - ``/api/v1/projects/{id}/transitions/preview`` - - Preview a core transition against If-Match; return plan/base_revision. - * - POST - - ``/api/v1/projects/{id}/transitions/apply`` - - Conditionally apply that client-held plan and base_revision. - * - POST - - ``/api/v1/projects/{id}/semantic-transitions/query`` - - Read selected traces' typed facts, own drift and linked impacts. - * - POST - - ``/api/v1/projects/{id}/semantic-transitions/preview`` - - Prepare typed semantic decisions and source writeback without writing. - * - POST - - ``/api/v1/projects/{id}/semantic-transitions/apply`` - - Apply that prepared candidate under its original shared revision/ETag. - -``Content-Type: application/json`` is required for JSON requests. -``text/plain; charset=utf-8`` is used for Arcadia source. The service does not -guess formats from a filename. Unknown DTO fields, invalid UUIDs/enums, duplicate -model map keys and unsupported schemas are rejected. Maximum body size is -8 MiB. Accepted serialized model JSON and serialized source-bundle JSON each -have an 8 MiB cap, including IDs inserted during compilation. The stored -envelope allows both bounded representations plus small framing overhead. -Compressed or encoded request bodies and browser Origin requests are -not supported. - -Model admission permits at most 5,000 elements plus relations, 20,000 total -persistent records (including the project, views, placements, routes and manual -port anchors), and 50,000 ordered step references. Source declaration counts and decoded JSON record -counts are checked before structural validation. Legacy import counts the -occurrences and placements migration will generate before validating or -migrating that model. Exceeding these budgets returns ``422 model_limit``; -candidate validation reports the same diagnostic in its 200 response. -Parsing, validation and query processing use snapshot workers, so this work -does not hold the catalog lock or serialize unrelated model preparation. - -Import accepts schema-1 JSON through the shared explicit migration adapter, -as well as schema-2, schema-3, schema-4, schema-5, schema-6 and current schema-7 JSON. Source import uses the shared language parser -and its supported migrations. Export uses schema 7 / arcadia 7. Native JSON -replacement accepts supported older schemas through the same explicit migration. -Only documented query parameters are accepted. In particular, appending -``?revision=...`` to model/source export is an error, not historical retrieval. - -Authored source projects ------------------------- - -A source bundle contains ``entry`` and a map of ``files``. For example:: - - { - "entry": "rescue.arcadia", - "files": { - "rescue.arcadia": "arcadia 7; project rescue \"Rescue\" { include \"oa/work.arcadia\"; }\n", - "oa/work.arcadia": "// Engineering rationale\noa_activity assess \"Assess assistance\" {}\n" - } - } - -The entry is a root filename. All keys are canonical, portable, slash-separated -relative paths ending in ``.arcadia``. Includes resolve only against these -submitted strings. They never read the service filesystem, URLs, artifact -targets or another hosted resource. All submitted files must be reachable; -missing files, cycles, duplicate keys, case collisions and escaping paths are -rejected. The bundle permits at most 256 files, 32 include edges along a path, -and 1,024 include edges in total. Model and source-byte budgets apply across -the complete bundle, not separately per file. Current bundle syntax is source7; source3/4/5/6 bundles migrate explicitly while -preserving their files/includes/comments. V1/V2 single-file migration remains -available through interchange import. - -Compilation inserts UUIDs into headers that omit them. Import returns 201 with -Location/ETag; GET source-project retrieves the stabilized accepted bundle. -PUT returns ``revision``, ``changed``, located ``issues`` and that accepted -``source_project``. Preserve the returned IDs before subsequent editing. -With explicit IDs, untouched text, comments, line endings, entry name and module -ownership are preserved exactly. Re-submitting the same accepted bundle is a -no-op. Removing a file means submitting a complete valid bundle without that -file and its includes; no files are created or deleted on the service host. - -All typed command, transition, rename and model-interchange writes update this -same accepted source project. Existing declarations and reference comments -follow command identity correspondence, including UUID swaps. Full-model -replacement uses common UUIDs as its correspondence boundary. It preserves -the accepted source layout; canonical interchange formatting/comments are not -adopted as a replacement authored workspace. Use source-project PUT for -authoring file layout and comments. A failed source writeback rejects the whole -write and never falls back to discarding authored files. - -Old model-only envelopes are migrated to a deterministic source bundle in -memory when opened. Reading or an exact no-op does not rewrite their files or -advance revisions. Their next changed write persists both representations. -Startup rejects already-coupled envelopes whose source/model disagree or whose -source lacks stable IDs. The 256 MiB store budget charges complete accepted -envelopes, including the generated source of old envelopes before their next -save; an over-capacity old store is reported rather than silently pruned. - -Queries and pagination ----------------------- - -List filters combine using AND. Enum spelling is case-sensitive and comes -from the shared schema: examples include ``kind=Activity`` and ``layer=Oa``. -Aliases match exactly; ``q`` is a Unicode-lowercase substring match on the -display name. Empty or invalid enum values are request errors. - -Lists are ordered by UUID. ``limit`` defaults to 100 and accepts 1 through -1000. ``after`` is an exclusive last UUID. ``next_after`` is null at the end. -Project-scoped lists return a revision. Include that revision on following -pages to detect a changed model; 409 ``snapshot_changed`` means restart the -query. Supplying a past revision does not retrieve history. The project catalog -is a live list and does not have a multi-page snapshot guarantee. - -A neighborhood request is:: - - { - "roots": ["1b37b9d7-9a95-4a51-8707-010878a82419"], - "direction": "both", - "relation_kinds": ["Allocation", "Interaction"], - "max_depth": 2, - "max_objects": 200, - "revision": 3 - } - -Roots may identify elements or relations. The service traverses relation -source/target adjacency in the selected direction (incoming/outgoing/both), -preserves distinct parallel relations, and returns ``elements``, ``relations``, -``revision`` and ``truncated``. Empty kind filters permit all relation kinds. -Every returned relation includes its endpoint closure, including the endpoints -of a referenced relation. Closure records count against the object bound but -do not add traversal depth. Model ownership, process occurrence order and -non-endpoint references remain available in the returned records and complete -model; they are not silently treated as additional graph edges. - -Use 1–100 roots, depth 0–4, and 1–1000 total returned objects. The cap must fit -the roots and their endpoint closure. A root closure that cannot fit is an -error; further omitted edges set ``truncated: true``. A missing root is 422. -Deterministic UUID iteration makes repeated queries of one snapshot stable. - -Transactions ------------- - -The ``commands`` array contains the shared externally tagged Serde command -representation. It is not RFC 6902 JSON Patch. For example:: - - { - "commands": [ - {"Rename": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Assess assistance"]}, - {"Describe": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Review the request."]} - ], - "dry_run": true - } - -One-field variants use ``{"Delete":"uuid"}``; tuple variants use arrays; -named-field variants use objects. For a new typed OA activity:: - - { - "CreateElement": { - "id": "1b37b9d7-9a95-4a51-8707-010878a82419", - "alias": "assess", - "name": "Assess assistance", - "description": "Review the request.", - "parent": null, - "body": {"Oa": "Activity"} - } - } - -``CreateArtifact``/``EditArtifact`` and ``CreateTrace``/``EditTrace`` use the same -transaction boundary as engineering edits. Artifact URI, revision and fragment -strings are opaque references. No URI is fetched or verified by the service. -Model references and alias uniqueness are checked by the core. - -At most 200 commands are allowed, counting nested Batch wrappers. Explicit -creation identities allow later commands in the batch to reference earlier -creations. The shared ``execute_batch`` applies all operations to one candidate -and validates the final state. A rejected operation or final structural error -rejects the whole request. Reference rewrites/deletion cleanup follow the same -core logic as desktop editing. Command preparation retains original-to-candidate -identity correspondence through UUID edits, swaps, deletion and reuse. Source -writeback uses that correspondence to preserve declaration ownership and comments. - -Dry run still requires ``If-Match``. Its response includes ``revision``, -``dry_run``, ``changed``, ``issues`` and the proposed ``model`` and -``source_project``. It validates source writeback as well as semantics and neither writes -nor reserves IDs. A committed response omits the proposed model and source -project. Commit must check the revision again. A no-op is successful with -``changed: false`` and the original ETag. - -Diagram port positions ----------------------- - -``NodePlacement.port_anchors`` is an optional list of -``{"port":"","side":"Top","offset":0.25}`` records. Side is -Left, Right, Top, or Bottom; offset is a finite fraction from 0 through 1, -measured from top on vertical edges and from left on horizontal edges. -The port must be a direct child of the placement's semantic element and occur -at most once in its anchor list. Placement is presentation state; it changes -neither semantic ownership nor port direction. Other diagrams are independent. - -The existing transaction endpoint accepts:: - - {"commands":[{"SetPortAnchor":{ - "diagram":"","node":"", - "anchor":{"port":"","side":"Top","offset":0.25} - }}]} - -``SetPortAnchor`` upserts by port UUID. ``RemovePortAnchor`` takes -``{diagram,node,port}`` and restores automatic positioning. Repeating a reset -is a no-op only while the referenced port still belongs to that placement's -element; missing or stale owners are rejected. Remove old anchors before -reparenting in the same atomic batch. Both commands support ordinary dry run, -ETag checks and source-project writeback. Rejected batches preserve the model, -authored files and revision. JSON/source imports may omit ``port_anchors``; -it defaults to ``[]`` and empty lists are omitted on export. The normative -diagram semantics and source spelling are in ``language.rst`` section 5. - -Contextual process and chain topology -------------------------------------- - -Operational processes and system functional chains can combine work -contributions, exchange contributions, explicit controls and references to -reusable sequences. Each contribution is an ordinary ``Occurrence`` element -owned directly by the process/chain, with its stable ID in that owner's -``steps`` list. ``OccurrenceBody`` includes, for example:: - - {"Action":{"action":"","lifeline":null}} - {"Control":{"kind":"Choice"}} - {"SequenceReference":{"sequence":""}} - -``ControlKind`` is ``Choice``, ``Merge``, ``Fork``, ``Join`` or ``Loop``. -``RelationBody.SequenceLink`` connects contribution IDs in one owner and -contains ``source``, ``target`` and a string ``guard``. Conditions belong to -choice/loop paths; an explicit ``else`` guard can describe the other path. -The readable order of the ``steps`` list remains persistent, while links -express the explicit alternative, parallel and loop topology. The core checks -owner context, work/exchange endpoint agreement and recursive sequence reuse; -engineering diagnostics identify incomplete branch cardinality or paths. -Old exchange-only sequences without explicit links retain their linear meaning. - -These records use the same create/edit transaction commands and source/model -replacement routes as other engineering records. For example, -``GET /api/v1/projects/{id}/relations?kind=SequenceLink`` queries the links. -The source representation uses ``sequence @alias`` references. The service -does not execute conditions or simulate a process. - -Caller-qualified process and chain links ----------------------------------------- - -Schema 7 adds the closed ``RelationBody.ContextualLink`` payload:: - - { - "ContextualLink": { - "owner": "", - "source": {"references": [], "occurrence": ""}, - "target": {"references": [""], - "occurrence": ""}, - "purpose": {"Precedence": {"guard": "check accepted"}} - } - } - -``references`` is an ordered path of reference-occurrence IDs, never sequence -definition IDs; at most 64entries per endpoint. The explicit owner determines -context and source placement. ``purpose`` is exactly Precedence with string -guard, Exchange with work-exchange UUID, or CommonWork with work UUID. Only -Precedence contributes ordering; the other purposes preserve assembly incidence. -Both paths empty with Precedence must use the existing SequenceLink constructor. -Query these records with ``relations?kind=ContextualLink``. Ordinary create/edit, -source replacement and JSON replacement share the same owner/path/purpose checks. - -R1.B and R2.B remain separate caller contributions even if B has one definition -UUID. Deleting a typed path/leaf/owner/purpose dependency removes the whole link; -retargeting an invalid surviving link requires explicit repair. No path shortening, -implicit work duplication or fallback to a whole reference is permitted. Authored -path entries count toward the 50,000 ordered-reference budget. Resolver cache state -does not change validity. Contextual analysis has separate 50,000 node/100,000 edge -limits and explicitly reports incomplete expansion. See language.rst4.2.1 for -caller-qualified diagnostics and synthetic-boundary rules. - -Typed scenarios and message values ----------------------------------- - -Scenario bodies store an explicit ``viewpoint``. Occurrence Exchange bodies -store ``message_kind``, optional correlated-call ``reply_to`` and contextual -``values``. Message kinds are Asynchronous, Call, Reply, Create, Delete, or -``{"Timer":{"expression":"after 5 s"}}``. Each value record contains a -carried DataField UUID in ``field``, enclosing field UUID list ``path`` (default -[]) and textual ``expression``. Identity is (path, field), allowing repeated uses -of a Record. Operation Call/Reply direction checks use the direct parameter; -Reference storage stops nested payload traversal. Expressions do not change the -shared exchange item's type or evaluate server-side. - -``ScenarioUse`` contains a referenced ``scenario`` and one-to-one lifeline -``bindings`` records with ``referenced`` and ``local`` UUIDs. The core validates -viewpoint/layer compatibility, call/reply order and reversal, lifetime order, -carried fields, binding identity and recursive reuse through the same candidate -boundary used by the desktop. The normative details are in ``language.rst``. - -Existing schema-2 files may omit the new fields: missing viewpoint maps to -MixedLegacy and missing message fields to Asynchronous, null and []. Source -exports make those compatibility defaults explicit. This preserves original V2 -models while the release schema is being integrated; it does not silently -reinterpret old mixed lifelines as a strict new viewpoint. - -Data and interface contracts ----------------------------- - -Schema4 uses typed bounded DataTypeDefinition and DataValue constructors, field -storage/defaults/directions, and single specialization. Exact JSON shapes are in -``openapi.json`` and semantic rules and runnable source in ``language.rst`` section 4.5. -Interface membership is an identified InterfaceItemAllocation relation with -independent order/transmission/acquisition. DataAssociation relates Record types -with named, navigable, multiplicity-bearing ends; it does not add carried fields. - -Use ordinary relation queries, transactions and artifact links, for example -``GET /api/v1/projects/{id}/relations?kind=InterfaceItemAllocation``. The service -and desktop use the same inheritance/path/default validation. Invalid bounds, -defaults, directions, cycles or contract-breaking deletions reject atomically, -preserving the accepted source/model, revision and stored bytes. Correct a related -contract in the same transaction when removing a used interface allocation. - -Explicit schema1/2/3 migration preserves prior identities and typed ports. Reports -add semantic defaults, generated interface allocations and unresolved message -values. Old source3 bundles retain files/includes/comments during upgrade; all -current exports use schema7/source7. Reading an old envelope does not write it or -advance its ETag. Canonical exports omit transient migration reports. - -Validation ----------- - -GET returns current-model diagnostics. POST accepts either:: - - {"format":"json", "model": {"...":"complete native Project record"}} - -or:: - - {"format":"arcadia", "source":"complete source document"} - -or:: - - {"format":"source-project", "source_project":{"entry":"rescue.arcadia", "files":{"rescue.arcadia":"arcadia 7; project rescue \"Rescue\" {}"}}} - -A well-formed validation request returns 200 with ``valid`` and ``issues`` even -if its candidate is invalid, including a model record that fails typed decoding. -Malformed request JSON or an invalid format/payload envelope is a 400. -Source diagnostics include one-based line and column according to the -shared language parser. Candidate validation does not replace current state. -Source-project validation locations refer to the submitted bytes before UUID -insertion. Successful source-project PUT diagnostics refer to the returned -accepted bundle. Missing IDs are not saved or adopted by validation. - -Typed state machines and events -------------------------------- - -OA and SA Event records use the same ``SharedBody::Event`` and ``EventBody`` -contract as source and graphical editing. Signal carries same-layer item -references; Time specifies After/At and an expression; Change specifies a -condition expression. State vertices include Choice, Junction, Fork, Join, -ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate, as well as -State, Mode, Initial and Final. Query these through the ordinary ``kind`` -filters. No timer or executable state-machine service is implied. - -StateTransition keeps ``trigger``, ``guard`` and ``effect`` prose and adds -``transition_kind`` (External/Local/Internal), ``triggers`` (Event or work UUIDs) -and ``effects`` (work UUIDs). The reference lists use UUIDs in JSON and aliases -in source. Missing new properties in staged schema-2 input hydrate as -External/[]/[] without changing prior text. These defaults are compatibility -behavior pending the distinct release schema, not a versionless expansion of -every future input contract. - -Use one ordinary transaction to create an event and edit its referring -transition. Reidentification updates references, and deletion prunes signal -items and effect references. Removing the last typed trigger with no prior -trigger prose leaves a descriptive replacement reminder; it does not silently -introduce a completion transition. Invalid typed references reject the whole -candidate and preserve revision/ETag. All writes retain the same conditional -commit and disk persistence contract as other semantic records. - -State diagram representation ----------------------------- - -``Diagram.projection`` keeps the existing string values ``Overview``, -``Decomposition``, ``Allocation``, ``Traceability``, ``FunctionalDataflow`` and -``Capabilities``. A dedicated state view instead uses the required payload:: - - {"StateMachine":{"machine":"11111111-1111-4111-8111-111111111111"}} - -The UUID MUST identify an existing StateMachine in the diagram's layer. -Only that machine, its Regions and its state vertices may be placed, including -nested members. Empty and partial views are valid. No machine, Region, vertex -or placement is synthesized by accepting or reading a diagram. Source uses -``projection { StateMachine { machine @modes; }; };`` with the same reference. - -Use the ordinary ``CreateDiagram`` or ``EditDiagram`` transaction commands, -JSON model import/replacement, or source/source-project writes. A bare -``"StateMachine"``, missing or null ``machine``, malformed UUID, or additional -payload field is malformed JSON model data (400). A missing, wrong-kind or -cross-layer machine produces the semantic issue ``state_diagram_context``; -an incompatible placement produces ``state_diagram_member``. These reject -writes with 422. Transaction rejections use the ``command_rejected`` problem -code and include the first core failure in ``detail``; candidate validation -provides the structured ``issues`` collection. -Source syntax errors use the source rejection response (422) with locations. -The validation endpoint returns 200 with ``valid:false`` for a well-formed -invalid candidate, as described above. - -When changing a diagram's machine or selecting StateMachine for an existing -diagram, remove or relocate incompatible placements in the same transaction. -Other projections continue to preserve hidden placements. Rejected writes -retain the accepted model, source bundle, revision and ETag. Reading a state -view has no semantic or persistence side effects. - -Source-6 communication and saved views --------------------------------------- - -The same import, replacement and command endpoints admit OA -``CommunicationMean { source, target, oriented, items }`` and root-level -``ExchangeCategory { family, exchanges }`` records. Communication means join -distinct OA entities/actors. Categories group same-layer exchanges as ``Work`` -or ``Participant`` without owning them. Existing legacy OA boundary exchanges -retain their identities and contracts; migration does not silently replace them. - -``Diagram.view_options`` is an optional closed object with defaults: empty -``hidden_edges``, ``All`` for ``relation_kinds``, both category selections, -``coverage`` and ``work_contexts``, and ``Name`` for ``exchange_labels``. -``{"Only":[]}`` hides all relation and trace lines; ``{"Any":[]}`` preserves -an explicit empty category/context selection. Category members union within -each family, and independent filters intersect. ``Traceability`` permits -references from both layers; ordinary projections require the diagram's layer. -These presentation choices preserve semantic records, routes, placements and -the automatic port positions derived from unfiltered eligible exchanges. - -Schema/source versions 1–5 reject the new constructors, category review -facts and explicit ``view_options`` field, including an empty object. Their -frozen readers migrate accepted content to schema/source 7 with default view -options. The OpenAPI document describes current records; it is not a permissive -union of historical input formats. Source-7 integration and release acceptance -are tracked separately in the release coverage audit. - -Explicit interface-operation identity -------------------------------------- - -``OccurrenceBody.Exchange.operation`` is optional. Absent/null retains ordinary -channel semantics and is omitted in canonical output. ``{"allocation":null}`` -(or an empty object) retains explicit unresolved operation intent. A selected -``{"allocation":""}`` identifies that exact -allocation, including for parameterless operations sharing one channel. These -records are closed; unknown fields are rejected. Earlier schema/source versions -reject the operation field even when explicitly null and reject ContextualLink -and their new semantic facets. Migration never guesses an operation from names, -message values or a channel's item set. - -Selection is valid only for SA BoundaryInterface Call/Asynchronous messages. -Core proves actual Standard component-port Requires/Provides roles, compatible -interfaces exposing the same allocation, actual channel direction and allowed -transmission protocol. Reply stores no operation and derives it from its exact -Call; a separate return channel still needs its own valid carried-item/endpoint -contract, including a void return. Selected message values use the exact operation -signature. Unresolved intent preserves live expressions and checks their paths -with an incomplete-signature warning; it does not substitute the channel union. - -Allocation deletion clears selection to unresolved only within an otherwise -valid whole candidate. Removing the sole allocation can refuse atomically while -a channel still carries its now-unadmitted item; repair every affected channel -explicitly in the same batch. A surviving same-item allocation may keep a channel -valid, but is never silently selected. Reselection validates retained values -without pruning them. The service uses the same source/history boundary as -graphical editing. See language.rst4.3.1 for the complete contract. - -Cross-layer transitions ------------------------ - -Preview accepts the shared ``TransitionRequest`` record, including direction -(``OaToSa`` or ``SaToOa``), sources, dependency scope, mappings, selected fields, -conflict resolutions and optional destination diagram. All fields are explicit -in OpenAPI. At most 100 sources, 200 mappings, 200 communication endpoint -decisions, 200 operation omissions and 400 field resolutions are -accepted. A conflict-free plan must fit the same 200-command transaction limit -and model/source-capacity limits. A conflict-free preview also checks source -writeback, so its commands can update the same accepted authored workspace. - -SA→OA abstraction of explicit operation intent requires ``operation_omissions`` -(omitted means empty). Each closed record contains the selected invocation's -``source`` UUID and a nonempty ``rationale`` string. Missing, duplicate, -irrelevant or wrong-direction omissions reject. Reply derives its Call's -abstraction and has no second writable operation choice. Source operation -identity/values remain intact; target facts are mapped and validated. Preview -and Apply enforce the same 200 omission limit for ordinary and semantic transitions. - -OA ``CommunicationMean`` maps to an SA ``BoundaryExchange`` through an explicit -``communication_endpoints`` array (omitted means empty). Each decision identifies -the source communication means and both ``source_port`` and ``target_port``. -Each port choice is either ``{"Reuse":{"port":"port-uuid"}}`` or -``{"Create":{"id":"new-port-uuid","alias":"radio_port","name":"Radio port", -"description":"","kind":"Standard","orientation":"Unset"}}``. Created or -reused SA component ports must belong to the explicitly mapped participants. -These per-exchange choices do not replace participant correspondence with port -IDs. Missing or incompatible choices remain preview conflicts. Interface and -functional-port allocations use their existing explicit workflows; an engineer -may omit an unresolved allocation from this transition. Reverse transition maps -boundary ports through their owners to OA participants. - -``ExchangeCategory`` transitions preserve their family and map only explicitly -corresponding member exchanges. Missing member mappings are conflicts, not new -fabricated exchanges. Typed review exposes ``Category.Family`` and the -``CategoryExchanges`` member collection; historical review facts remain exact. - -The preview response is ``{"base_revision":1,"plan":{...}}``. It contains -planned changes, conflicts, warnings and exact commands/IDs. It does not write -the model or persist a server-side plan. Apply accepts that entire response and -requires the original ETag in ``If-Match``. It checks both base_revision and the -core's snapshot fingerprint before executing the shared transition validator. -Stale revisions fail with 412 and unresolved conflicts with 422. Successful -apply increments the revision once, preserving the planned identities. - -A supplied plan is a client-owned command proposal. It is not cryptographically -authenticated as an untouched server preview; the same authenticated client -can submit ordinary typed commands. The core fingerprint is a change detector, -not a security digest. There is no plan token, expiration store or receipt. - -Typed semantic transition review --------------------------------- - -The sibling ``semantic-transitions`` routes use the `typed review semantics -`_ in ``language.rst`` sections 5.1–5.5. -The original prose transition routes keep -their existing contract. All new routes use JSON and the existing authentication, -capacity, worker and error boundaries. - -Query accepts ``{"traces":["trace-uuid"],"references":{},"revision":1}``. -``references`` is an optional map of canonical OA UUIDs to SA UUIDs, including -historical deleted identities. It never guesses correspondences from names. -``revision`` is optional; a mismatch returns 409. Select 1–100 unique existing -trace IDs; an empty or duplicate selection returns 400 and a missing trace -returns 404. The response includes the current revision and items containing -``trace``, ``oa``, ``sa``, typed ``facets``, ``drift`` and ``linked_impacts``. -Own and linked drift and their unreviewed flags remain distinct. Query reads one -immutable snapshot, does not refresh baselines, and does not fetch artifact URIs. - -Preview accepts ``SemanticTransitionRequest``:: - - { - "transition": { - "direction": "OaToSa", - "sources": ["40000000-0000-4000-8000-000000000002"], - "scope": "SelectedOnly", - "mappings": [{ - "source": "40000000-0000-4000-8000-000000000002", - "target": "40000000-0000-4000-8000-000000000003", - "target_kind": null - }], - "fields": [], "resolutions": [], "destination_diagram": null - }, - "traces": {}, "references": {}, - "reviews": [{ - "source": "40000000-0000-4000-8000-000000000002", - "resolutions": [ - {"facet":{"Header":"Name"}, "decision":"TakeSource"}, - {"facet":{"Data":"Unit"}, "decision":"KeepTarget"} - ], - "reset": [] - }], - "candidates": [], "accepted_impacts": [] - } - -The example maps an existing OA data type to its traced SA counterpart, takes -only its name and acknowledges intentionally different units. Other facets stay -unreviewed or retain their earlier drift. Select an explicit trace through the -``traces`` map (source UUID to trace UUID) when multiple trace kinds connect the -pair. ``reset`` deliberately invalidates exact old selectors after changed -correspondence. The optional collections default to empty. Semantic header -decisions use typed facets; leave legacy ``transition.fields`` and -``transition.resolutions`` empty. - -Facet selectors are closed enums, for example ``{"Data":"IntegerMinimum"}`` -or ``{"Member":{"collection":"SignalItems","keys":{"oa":"oa-uuid","sa":"sa-uuid"}}}``. -Annotation keys contain the entire path and field; enum literal keys are exact -strings. ``KeepTarget``, ``TakeSource`` and ``Candidate`` operate on independently -selected facts. Candidate records use existing Element/Relation/ArtifactLink -types, must preserve identity/alias, and must equal precisely the result of -the selected facets. Constructor review covers its coupled introduced/removed -fields. Membership review does not silently acknowledge order; target-only -members remain independent. OpenAPI defines every selector and typed payload. - -Preview requires ``If-Match`` and returns ``{"base_revision":1,"plan":{...}}``. -The plan contains the original typed request, mapping plan, review facts, -acknowledged/retired selectors, linked impacts, exact prepared impacts, conflicts, -commands, and base/prepared fingerprints. A conflict-free preview also prepares -the complete authored source bundle and checks its model equivalence and capacity. -No preview changes the model, source, revision or stored bytes. - -If ordinary preparation induces additional cleanup, the plan reports exact typed -before/after impacts and a conflict. Review those impacts, place the accepted -records in ``accepted_impacts``, and request another preview. Acceptance cannot -override a contradictory selected facet. Missing mappings, ambiguous order, -duplicate decisions, invalid candidates or unresolved impacts cannot be applied. -This is the shared core planner and command preparation, with no server-specific -semantic mutation engine. - -Apply accepts the entire preview envelope under the same ``If-Match``. It checks -``base_revision``, the model fingerprint, unresolved conflicts, the prepared -candidate fingerprint, ordinary model validation and coupled source preparation, -then atomically stores one accepted model/source revision. The final store check -also catches a concurrent edit during preparation. Source-only edits invalidate -the preview through the shared ETag even when the model fingerprint is unchanged. -Stale ETag/revision returns 412; semantic rejection returns 422 with code -``semantic_transition_rejected``. Exact no-op candidates preserve revision. -Read and preview again after a successful changed apply before reviewing further. - -A supplied plan remains a client-owned candidate. Fingerprints detect stale or -inconsistent proposals; they are not tamper-proof signatures, authentication -receipts or proof of original review authorship. Apply preserves preview-generated -identities and does not rerun the planner with fresh random IDs. Authenticated -clients already have ordinary model/source editing privileges. The server does -not persist plans, tokens or review sessions. - -Requests allow 100 sources, trace choices and reviews; 200 mappings, candidates -and accepted impacts; 5,000 canonical reference mappings; and 5,000 facet -resolutions plus resets across all reviews. The legacy 400 prose-resolution -limit, 200 communication endpoint decisions, 200 operation omissions and -200-command transaction limit still apply. Query has the same 5,000 -reference-map limit. Core bounded linked traversal errors remain observable as -semantic rejection; no truncated result silently claims complete review. - -Schema/source 5 adds ``TraceBaseline.semantic``. Absence or null is Unreviewed; -migrating older prose baselines does not fabricate semantic history. The persisted -baseline contains only closed typed ``FacetReview`` facts, not whole candidate -records or a generic JSON value tree. Historical UUIDs remain historical, including -after deletion, and source uses explicit ``uuid`` tokens for them. - -Error contract --------------- - -All errors have ``Content-Type: application/problem+json``. Example:: - - { - "type":"urn:arcadia:problem:revision_conflict", - "title":"Precondition Failed", - "status":412, - "code":"revision_conflict", - "detail":"Project changed. Reload and review the current model before retrying.", - "current_revision":3 - } - -``issues`` contain severity, code and message, and may include field, -object_id, line, column, source ``path``, half-open UTF-8 byte ``span`` -(``start``, ``end``), and ``related`` source locations (path/span). Lines and -columns are one-based; columns count Unicode scalars. Use -``code`` for client decisions, not the English message. ``X-Request-ID`` is a -response correlation UUID. This is not an idempotency key or receipt address. - -* 400: malformed JSON/envelope/query/UUID or unsupported ETag form. -* 401: missing or incorrect configured bearer token; includes WWW-Authenticate. -* 403: browser Origin is unsupported, or unauthenticated Host is not loopback. -* 404: missing project or route. -* 405: unsupported HTTP method. -* 409: requested query revision changed or revision counter exhausted. -* 412: stale write precondition. -* 413: body, serialized model, stabilized source bundle or envelope exceeds its size limit. -* 415: unsupported Content-Type or Content-Encoding. -* 422: rejected source/model/command, model count limit, or missing graph root. -* 428: missing If-Match. -* 503: work slots full or filesystem/storage unavailable; Retry-After is present. -* 504: request deadline; an admitted write outcome may be unknown. -* 507: fixed store cap of 128 models or 256 MiB serialized envelopes reached. -* 500: unexpected server/worker failure. - -Deployment and persistence limitations are documented in ``server.md``. +Arcadia Studio model REST API +============================= + +This is the HTTP contract for ``oa-server``. The normative engineering model +and source language belong to ``language.rst``. ``openapi.json`` describes the +JSON records and closed command vocabulary. The server uses the same Rust +``Project``, ``Command``, parser/printer and validation implementation as the +desktop. + +Identity, revision and writes +----------------------------- + +All model routes begin with ``/api/v1``. A hosted project's immutable service +``resource_id`` is different from its editable ``model.id``. Each stored file +contains ``{resource_id, revision, model, source_project}``. ``source_project`` +is the accepted entry filename and map of authored source files; compiling it +must reproduce ``model``. The revision starts at 1, increments once per changed +accepted model/source pair, and does not change for an exact no-op. Comment-only +edits and file/entry changes count as changes even when the model is unchanged. + +Existing-project writes require the exact quoted ETag returned by a project +read, for example:: + + If-Match: "p-6f7162cc-094c-4f30-9b70-3f6f355f0c7f-r1" + +Missing preconditions return 428; stale preconditions return 412. Weak ETags, +wildcards and lists are not accepted on writes. GET of the envelope, model and +source/source-project supports ``If-None-Match`` and returns an empty 304 when unchanged. +Use ETags as opaque strings; revisions are unsigned integer counters in JSON. + +Every accepted changed write is persisted before the response. A worker captures +a revision-qualified snapshot, then parses and validates its candidate outside +the catalog lock. Commit rechecks the exact ETag and atomically installs the +envelope under the lock. Another accepted edit during preparation therefore +returns 412 even when the original snapshot passed its initial revision check. +Malformed or rejected inputs preserve the accepted model. Incomplete-model +warnings permit work and are returned in ``issues``. + +There is no retained revision history, idempotency receipt, tombstone or +server undo. DELETE permanently removes the hosted envelope. Source/JSON +interchange exports contain the current model only; source-project export also +preserves the authored file layout and comments. They preserve persistent +aliases, diagrams, artifact references and OA/SA trace data. Artifact revision +strings refer to external artifacts, not past server snapshots. + +A lost connection or 504 can occur after an admitted write has begun. That +write may still complete. Read current state before choosing a new action. +Do not automatically retry POST create/import or regenerate random IDs and +assume the original request failed. + +Routes +------ + +Health and schema routes are public. All model routes require the configured +bearer token, if any. Non-loopback binding requires a configured token. + +.. list-table:: Endpoints + :header-rows: 1 + :widths: 12 41 47 + + * - Method + - Path + - Behavior + * - GET + - ``/health``, ``/health/live``, ``/health/ready`` + - Responding process with successfully opened store. + * - GET + - ``/openapi.json``, ``/api/v1/openapi.json`` + - OpenAPI 3.1 contract. + * - POST + - ``/api/v1/projects`` + - Create from ``{"name":"..."}``; 201 + Location/ETag. + * - GET + - ``/api/v1/projects`` + - Summaries; ``limit``, ``after``, name substring ``q``. + * - GET + - ``/api/v1/projects/{id}`` + - Complete current stored envelope. + * - PATCH + - ``/api/v1/projects/{id}`` + - Conditional rename using ``{"name":"..."}``. + * - DELETE + - ``/api/v1/projects/{id}`` + - Conditional permanent removal; 204. + * - POST + - ``/api/v1/projects/import`` + - Raw model JSON or Arcadia source; new service ID. + * - POST + - ``/api/v1/projects/import-source-project`` + - Complete source bundle as JSON; stabilize missing IDs and create a resource. + * - GET / PUT + - ``/api/v1/projects/{id}/model`` + - Export or conditionally replace native model JSON. + * - GET / PUT + - ``/api/v1/projects/{id}/source`` + - Export or conditionally replace canonical UTF-8 source. + * - GET / PUT + - ``/api/v1/projects/{id}/source-project`` + - Export exact authored files or conditionally replace the complete bundle. + * - GET + - ``/api/v1/projects/{id}/elements`` + - Filter by ``kind``, ``layer``, ``alias``, name ``q``. + * - GET + - ``/api/v1/projects/{id}/relations`` + - Filter by ``kind``, ``source``, ``target``, ``incident_to``. + * - GET + - ``/api/v1/projects/{id}/artifact-links`` + - Artifact links; optional owner ``object`` filter. + * - GET + - ``/api/v1/projects/{id}/traces`` + - OA/SA traces; optional endpoint ``object`` filter. + * - POST + - ``/api/v1/projects/{id}/query`` + - Bounded directed neighborhood, no mutation. + * - GET / POST + - ``/api/v1/projects/{id}/validation`` + - Validate current model or a submitted candidate. + * - POST + - ``/api/v1/projects/{id}/transactions`` + - Conditional typed command batch, optional dry run. + * - POST + - ``/api/v1/projects/{id}/transitions/preview`` + - Preview a core transition against If-Match; return plan/base_revision. + * - POST + - ``/api/v1/projects/{id}/transitions/apply`` + - Conditionally apply that client-held plan and base_revision. + * - POST + - ``/api/v1/projects/{id}/semantic-transitions/query`` + - Read selected traces' typed facts, own drift and linked impacts. + * - POST + - ``/api/v1/projects/{id}/semantic-transitions/preview`` + - Prepare typed semantic decisions and source writeback without writing. + * - POST + - ``/api/v1/projects/{id}/semantic-transitions/apply`` + - Apply that prepared candidate under its original shared revision/ETag. + +``Content-Type: application/json`` is required for JSON requests. +``text/plain; charset=utf-8`` is used for Arcadia source. The service does not +guess formats from a filename. Unknown DTO fields, invalid UUIDs/enums, duplicate +model map keys and unsupported schemas are rejected. Maximum body size is +8 MiB. Accepted serialized model JSON and serialized source-bundle JSON each +have an 8 MiB cap, including IDs inserted during compilation. The stored +envelope allows both bounded representations plus small framing overhead. +Compressed or encoded request bodies and browser Origin requests are +not supported. + +Model admission permits at most 5,000 elements plus relations, 20,000 total +persistent records (including the project, views, placements, routes and manual +port anchors), and 50,000 ordered step references. Source declaration counts and decoded JSON record +counts are checked before structural validation. Legacy import counts the +occurrences and placements migration will generate before validating or +migrating that model. Exceeding these budgets returns ``422 model_limit``; +candidate validation reports the same diagnostic in its 200 response. +Parsing, validation and query processing use snapshot workers, so this work +does not hold the catalog lock or serialize unrelated model preparation. + +Import accepts schema-1 JSON through the shared explicit migration adapter, +as well as schema-2, schema-3, schema-4, schema-5, schema-6 and current schema-7 JSON. Source import uses the shared language parser +and its supported migrations. Export uses schema 7 / arcadia 7. Native JSON +replacement accepts supported older schemas through the same explicit migration. +Only documented query parameters are accepted. In particular, appending +``?revision=...`` to model/source export is an error, not historical retrieval. + +Authored source projects +------------------------ + +A source bundle contains ``entry`` and a map of ``files``. For example:: + + { + "entry": "rescue.arcadia", + "files": { + "rescue.arcadia": "arcadia 7; project rescue \"Rescue\" { include \"oa/work.arcadia\"; }\n", + "oa/work.arcadia": "// Engineering rationale\noa_activity assess \"Assess assistance\" {}\n" + } + } + +The entry is a root filename. All keys are canonical, portable, slash-separated +relative paths ending in ``.arcadia``. Includes resolve only against these +submitted strings. They never read the service filesystem, URLs, artifact +targets or another hosted resource. All submitted files must be reachable; +missing files, cycles, duplicate keys, case collisions and escaping paths are +rejected. The bundle permits at most 256 files, 32 include edges along a path, +and 1,024 include edges in total. Model and source-byte budgets apply across +the complete bundle, not separately per file. Current bundle syntax is source7; source3/4/5/6 bundles migrate explicitly while +preserving their files/includes/comments. V1/V2 single-file migration remains +available through interchange import. + +Compilation inserts UUIDs into headers that omit them. Import returns 201 with +Location/ETag; GET source-project retrieves the stabilized accepted bundle. +PUT returns ``revision``, ``changed``, located ``issues`` and that accepted +``source_project``. Preserve the returned IDs before subsequent editing. +With explicit IDs, untouched text, comments, line endings, entry name and module +ownership are preserved exactly. Re-submitting the same accepted bundle is a +no-op. Removing a file means submitting a complete valid bundle without that +file and its includes; no files are created or deleted on the service host. + +All typed command, transition, rename and model-interchange writes update this +same accepted source project. Existing declarations and reference comments +follow command identity correspondence, including UUID swaps. Full-model +replacement uses common UUIDs as its correspondence boundary. It preserves +the accepted source layout; canonical interchange formatting/comments are not +adopted as a replacement authored workspace. Use source-project PUT for +authoring file layout and comments. A failed source writeback rejects the whole +write and never falls back to discarding authored files. + +Old model-only envelopes are migrated to a deterministic source bundle in +memory when opened. Reading or an exact no-op does not rewrite their files or +advance revisions. Their next changed write persists both representations. +Startup rejects already-coupled envelopes whose source/model disagree or whose +source lacks stable IDs. The 256 MiB store budget charges complete accepted +envelopes, including the generated source of old envelopes before their next +save; an over-capacity old store is reported rather than silently pruned. + +Queries and pagination +---------------------- + +List filters combine using AND. Enum spelling is case-sensitive and comes +from the shared schema: examples include ``kind=Activity`` and ``layer=Oa``. +Aliases match exactly; ``q`` is a Unicode-lowercase substring match on the +display name. Empty or invalid enum values are request errors. + +Lists are ordered by UUID. ``limit`` defaults to 100 and accepts 1 through +1000. ``after`` is an exclusive last UUID. ``next_after`` is null at the end. +Project-scoped lists return a revision. Include that revision on following +pages to detect a changed model; 409 ``snapshot_changed`` means restart the +query. Supplying a past revision does not retrieve history. The project catalog +is a live list and does not have a multi-page snapshot guarantee. + +A neighborhood request is:: + + { + "roots": ["1b37b9d7-9a95-4a51-8707-010878a82419"], + "direction": "both", + "relation_kinds": ["Allocation", "Interaction"], + "max_depth": 2, + "max_objects": 200, + "revision": 3 + } + +Roots may identify elements or relations. The service traverses relation +source/target adjacency in the selected direction (incoming/outgoing/both), +preserves distinct parallel relations, and returns ``elements``, ``relations``, +``revision`` and ``truncated``. Empty kind filters permit all relation kinds. +Every returned relation includes its endpoint closure, including the endpoints +of a referenced relation. Closure records count against the object bound but +do not add traversal depth. Model ownership, process occurrence order and +non-endpoint references remain available in the returned records and complete +model; they are not silently treated as additional graph edges. + +Use 1–100 roots, depth 0–4, and 1–1000 total returned objects. The cap must fit +the roots and their endpoint closure. A root closure that cannot fit is an +error; further omitted edges set ``truncated: true``. A missing root is 422. +Deterministic UUID iteration makes repeated queries of one snapshot stable. + +Transactions +------------ + +The ``commands`` array contains the shared externally tagged Serde command +representation. It is not RFC 6902 JSON Patch. For example:: + + { + "commands": [ + {"Rename": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Assess assistance"]}, + {"Describe": ["1b37b9d7-9a95-4a51-8707-010878a82419", "Review the request."]} + ], + "dry_run": true + } + +One-field variants use ``{"Delete":"uuid"}``; tuple variants use arrays; +named-field variants use objects. For a new typed OA activity:: + + { + "CreateElement": { + "id": "1b37b9d7-9a95-4a51-8707-010878a82419", + "alias": "assess", + "name": "Assess assistance", + "description": "Review the request.", + "parent": null, + "body": {"Oa": "Activity"} + } + } + +``CreateArtifact``/``EditArtifact`` and ``CreateTrace``/``EditTrace`` use the same +transaction boundary as engineering edits. Artifact URI, revision and fragment +strings are opaque references. No URI is fetched or verified by the service. +Model references and alias uniqueness are checked by the core. + +At most 200 commands are allowed, counting nested Batch wrappers. Explicit +creation identities allow later commands in the batch to reference earlier +creations. The shared ``execute_batch`` applies all operations to one candidate +and validates the final state. A rejected operation or final structural error +rejects the whole request. Reference rewrites/deletion cleanup follow the same +core logic as desktop editing. Command preparation retains original-to-candidate +identity correspondence through UUID edits, swaps, deletion and reuse. Source +writeback uses that correspondence to preserve declaration ownership and comments. + +Dry run still requires ``If-Match``. Its response includes ``revision``, +``dry_run``, ``changed``, ``issues`` and the proposed ``model`` and +``source_project``. It validates source writeback as well as semantics and neither writes +nor reserves IDs. A committed response omits the proposed model and source +project. Commit must check the revision again. A no-op is successful with +``changed: false`` and the original ETag. + +Diagram port positions +---------------------- + +``NodePlacement.port_anchors`` is an optional list of +``{"port":"","side":"Top","offset":0.25}`` records. Side is +Left, Right, Top, or Bottom; offset is a finite fraction from 0 through 1, +measured from top on vertical edges and from left on horizontal edges. +The port must be a direct child of the placement's semantic element and occur +at most once in its anchor list. Placement is presentation state; it changes +neither semantic ownership nor port direction. Other diagrams are independent. + +The existing transaction endpoint accepts:: + + {"commands":[{"SetPortAnchor":{ + "diagram":"","node":"", + "anchor":{"port":"","side":"Top","offset":0.25} + }}]} + +``SetPortAnchor`` upserts by port UUID. ``RemovePortAnchor`` takes +``{diagram,node,port}`` and restores automatic positioning. Repeating a reset +is a no-op only while the referenced port still belongs to that placement's +element; missing or stale owners are rejected. Remove old anchors before +reparenting in the same atomic batch. Both commands support ordinary dry run, +ETag checks and source-project writeback. Rejected batches preserve the model, +authored files and revision. JSON/source imports may omit ``port_anchors``; +it defaults to ``[]`` and empty lists are omitted on export. The normative +diagram semantics and source spelling are in ``language.rst`` section 5. + +Contextual process and chain topology +------------------------------------- + +Operational processes and system functional chains can combine work +contributions, exchange contributions, explicit controls and references to +reusable sequences. Each contribution is an ordinary ``Occurrence`` element +owned directly by the process/chain, with its stable ID in that owner's +``steps`` list. ``OccurrenceBody`` includes, for example:: + + {"Action":{"action":"","lifeline":null}} + {"Control":{"kind":"Choice"}} + {"SequenceReference":{"sequence":""}} + +``ControlKind`` is ``Choice``, ``Merge``, ``Fork``, ``Join`` or ``Loop``. +``RelationBody.SequenceLink`` connects contribution IDs in one owner and +contains ``source``, ``target`` and a string ``guard``. Conditions belong to +choice/loop paths; an explicit ``else`` guard can describe the other path. +The readable order of the ``steps`` list remains persistent, while links +express the explicit alternative, parallel and loop topology. The core checks +owner context, work/exchange endpoint agreement and recursive sequence reuse; +engineering diagnostics identify incomplete branch cardinality or paths. +Old exchange-only sequences without explicit links retain their linear meaning. + +These records use the same create/edit transaction commands and source/model +replacement routes as other engineering records. For example, +``GET /api/v1/projects/{id}/relations?kind=SequenceLink`` queries the links. +The source representation uses ``sequence @alias`` references. The service +does not execute conditions or simulate a process. + +Caller-qualified process and chain links +---------------------------------------- + +Schema 7 adds the closed ``RelationBody.ContextualLink`` payload:: + + { + "ContextualLink": { + "owner": "", + "source": {"references": [], "occurrence": ""}, + "target": {"references": [""], + "occurrence": ""}, + "purpose": {"Precedence": {"guard": "check accepted"}} + } + } + +``references`` is an ordered path of reference-occurrence IDs, never sequence +definition IDs; at most 64entries per endpoint. The explicit owner determines +context and source placement. ``purpose`` is exactly Precedence with string +guard, Exchange with work-exchange UUID, or CommonWork with work UUID. Only +Precedence contributes ordering; the other purposes preserve assembly incidence. +Both paths empty with Precedence must use the existing SequenceLink constructor. +Query these records with ``relations?kind=ContextualLink``. Ordinary create/edit, +source replacement and JSON replacement share the same owner/path/purpose checks. + +R1.B and R2.B remain separate caller contributions even if B has one definition +UUID. Deleting a typed path/leaf/owner/purpose dependency removes the whole link; +retargeting an invalid surviving link requires explicit repair. No path shortening, +implicit work duplication or fallback to a whole reference is permitted. Authored +path entries count toward the 50,000 ordered-reference budget. Resolver cache state +does not change validity. Contextual analysis has separate 50,000 node/100,000 edge +limits and explicitly reports incomplete expansion. See language.rst4.2.1 for +caller-qualified diagnostics and synthetic-boundary rules. + +Typed scenarios and message values +---------------------------------- + +Scenario bodies store an explicit ``viewpoint``. Occurrence Exchange bodies +store ``message_kind``, optional correlated-call ``reply_to`` and contextual +``values``. Message kinds are Asynchronous, Call, Reply, Create, Delete, or +``{"Timer":{"expression":"after 5 s"}}``. Each value record contains a +carried DataField UUID in ``field``, enclosing field UUID list ``path`` (default +[]) and textual ``expression``. Identity is (path, field), allowing repeated uses +of a Record. Operation Call/Reply direction checks use the direct parameter; +Reference storage stops nested payload traversal. Expressions do not change the +shared exchange item's type or evaluate server-side. + +``ScenarioUse`` contains a referenced ``scenario`` and one-to-one lifeline +``bindings`` records with ``referenced`` and ``local`` UUIDs. The core validates +viewpoint/layer compatibility, call/reply order and reversal, lifetime order, +carried fields, binding identity and recursive reuse through the same candidate +boundary used by the desktop. The normative details are in ``language.rst``. + +Existing schema-2 files may omit the new fields: missing viewpoint maps to +MixedLegacy and missing message fields to Asynchronous, null and []. Source +exports make those compatibility defaults explicit. This preserves original V2 +models while the release schema is being integrated; it does not silently +reinterpret old mixed lifelines as a strict new viewpoint. + +Data and interface contracts +---------------------------- + +Schema4 uses typed bounded DataTypeDefinition and DataValue constructors, field +storage/defaults/directions, and single specialization. Exact JSON shapes are in +``openapi.json`` and semantic rules and runnable source in ``language.rst`` section 4.5. +Interface membership is an identified InterfaceItemAllocation relation with +independent order/transmission/acquisition. DataAssociation relates Record types +with named, navigable, multiplicity-bearing ends; it does not add carried fields. + +Use ordinary relation queries, transactions and artifact links, for example +``GET /api/v1/projects/{id}/relations?kind=InterfaceItemAllocation``. The service +and desktop use the same inheritance/path/default validation. Invalid bounds, +defaults, directions, cycles or contract-breaking deletions reject atomically, +preserving the accepted source/model, revision and stored bytes. Correct a related +contract in the same transaction when removing a used interface allocation. + +Explicit schema1/2/3 migration preserves prior identities and typed ports. Reports +add semantic defaults, generated interface allocations and unresolved message +values. Old source3 bundles retain files/includes/comments during upgrade; all +current exports use schema7/source7. Reading an old envelope does not write it or +advance its ETag. Canonical exports omit transient migration reports. + +Validation +---------- + +GET returns current-model diagnostics. POST accepts either:: + + {"format":"json", "model": {"...":"complete native Project record"}} + +or:: + + {"format":"arcadia", "source":"complete source document"} + +or:: + + {"format":"source-project", "source_project":{"entry":"rescue.arcadia", "files":{"rescue.arcadia":"arcadia 7; project rescue \"Rescue\" {}"}}} + +A well-formed validation request returns 200 with ``valid`` and ``issues`` even +if its candidate is invalid, including a model record that fails typed decoding. +Malformed request JSON or an invalid format/payload envelope is a 400. +Source diagnostics include one-based line and column according to the +shared language parser. Candidate validation does not replace current state. +Source-project validation locations refer to the submitted bytes before UUID +insertion. Successful source-project PUT diagnostics refer to the returned +accepted bundle. Missing IDs are not saved or adopted by validation. + +Typed state machines and events +------------------------------- + +OA and SA Event records use the same ``SharedBody::Event`` and ``EventBody`` +contract as source and graphical editing. Signal carries same-layer item +references; Time specifies After/At and an expression; Change specifies a +condition expression. State vertices include Choice, Junction, Fork, Join, +ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate, as well as +State, Mode, Initial and Final. Query these through the ordinary ``kind`` +filters. No timer or executable state-machine service is implied. + +StateTransition keeps ``trigger``, ``guard`` and ``effect`` prose and adds +``transition_kind`` (External/Local/Internal), ``triggers`` (Event or work UUIDs) +and ``effects`` (work UUIDs). The reference lists use UUIDs in JSON and aliases +in source. Missing new properties in staged schema-2 input hydrate as +External/[]/[] without changing prior text. These defaults are compatibility +behavior pending the distinct release schema, not a versionless expansion of +every future input contract. + +Use one ordinary transaction to create an event and edit its referring +transition. Reidentification updates references, and deletion prunes signal +items and effect references. Removing the last typed trigger with no prior +trigger prose leaves a descriptive replacement reminder; it does not silently +introduce a completion transition. Invalid typed references reject the whole +candidate and preserve revision/ETag. All writes retain the same conditional +commit and disk persistence contract as other semantic records. + +State diagram representation +---------------------------- + +``Diagram.projection`` keeps the existing string values ``Overview``, +``Decomposition``, ``Allocation``, ``Traceability``, ``FunctionalDataflow`` and +``Capabilities``. A dedicated state view instead uses the required payload:: + + {"StateMachine":{"machine":"11111111-1111-4111-8111-111111111111"}} + +The UUID MUST identify an existing StateMachine in the diagram's layer. +Only that machine, its Regions and its state vertices may be placed, including +nested members. Empty and partial views are valid. No machine, Region, vertex +or placement is synthesized by accepting or reading a diagram. Source uses +``projection { StateMachine { machine @modes; }; };`` with the same reference. + +Use the ordinary ``CreateDiagram`` or ``EditDiagram`` transaction commands, +JSON model import/replacement, or source/source-project writes. A bare +``"StateMachine"``, missing or null ``machine``, malformed UUID, or additional +payload field is malformed JSON model data (400). A missing, wrong-kind or +cross-layer machine produces the semantic issue ``state_diagram_context``; +an incompatible placement produces ``state_diagram_member``. These reject +writes with 422. Transaction rejections use the ``command_rejected`` problem +code and include the first core failure in ``detail``; candidate validation +provides the structured ``issues`` collection. +Source syntax errors use the source rejection response (422) with locations. +The validation endpoint returns 200 with ``valid:false`` for a well-formed +invalid candidate, as described above. + +When changing a diagram's machine or selecting StateMachine for an existing +diagram, remove or relocate incompatible placements in the same transaction. +Other projections continue to preserve hidden placements. Rejected writes +retain the accepted model, source bundle, revision and ETag. Reading a state +view has no semantic or persistence side effects. + +Source-6 communication and saved views +-------------------------------------- + +The same import, replacement and command endpoints admit OA +``CommunicationMean { source, target, oriented, items }`` and root-level +``ExchangeCategory { family, exchanges }`` records. Communication means join +distinct OA entities/actors. Categories group same-layer exchanges as ``Work`` +or ``Participant`` without owning them. Existing legacy OA boundary exchanges +retain their identities and contracts; migration does not silently replace them. + +``Diagram.view_options`` is an optional closed object with defaults: empty +``hidden_edges``, ``All`` for ``relation_kinds``, both category selections, +``coverage`` and ``work_contexts``, and ``Name`` for ``exchange_labels``. +``{"Only":[]}`` hides all relation and trace lines; ``{"Any":[]}`` preserves +an explicit empty category/context selection. Category members union within +each family, and independent filters intersect. ``Traceability`` permits +references from both layers; ordinary projections require the diagram's layer. +These presentation choices preserve semantic records, routes, placements and +the automatic port positions derived from unfiltered eligible exchanges. + +Schema/source versions 1–5 reject the new constructors, category review +facts and explicit ``view_options`` field, including an empty object. Their +frozen readers migrate accepted content to schema/source 7 with default view +options. The OpenAPI document describes current records; it is not a permissive +union of historical input formats. Source-7 integration and release acceptance +are tracked separately in the release coverage audit. + +Explicit interface-operation identity +------------------------------------- + +``OccurrenceBody.Exchange.operation`` is optional. Absent/null retains ordinary +channel semantics and is omitted in canonical output. ``{"allocation":null}`` +(or an empty object) retains explicit unresolved operation intent. A selected +``{"allocation":""}`` identifies that exact +allocation, including for parameterless operations sharing one channel. These +records are closed; unknown fields are rejected. Earlier schema/source versions +reject the operation field even when explicitly null and reject ContextualLink +and their new semantic facets. Migration never guesses an operation from names, +message values or a channel's item set. + +Selection is valid only for SA BoundaryInterface Call/Asynchronous messages. +Core proves actual Standard component-port Requires/Provides roles, compatible +interfaces exposing the same allocation, actual channel direction and allowed +transmission protocol. Reply stores no operation and derives it from its exact +Call; a separate return channel still needs its own valid carried-item/endpoint +contract, including a void return. Selected message values use the exact operation +signature. Unresolved intent preserves live expressions and checks their paths +with an incomplete-signature warning; it does not substitute the channel union. + +Allocation deletion clears selection to unresolved only within an otherwise +valid whole candidate. Removing the sole allocation can refuse atomically while +a channel still carries its now-unadmitted item; repair every affected channel +explicitly in the same batch. A surviving same-item allocation may keep a channel +valid, but is never silently selected. Reselection validates retained values +without pruning them. The service uses the same source/history boundary as +graphical editing. See language.rst4.3.1 for the complete contract. + +Cross-layer transitions +----------------------- + +Preview accepts the shared ``TransitionRequest`` record, including direction +(``OaToSa`` or ``SaToOa``), sources, dependency scope, mappings, selected fields, +conflict resolutions and optional destination diagram. All fields are explicit +in OpenAPI. At most 100 sources, 200 mappings, 200 communication endpoint +decisions, 200 operation omissions and 400 field resolutions are +accepted. A conflict-free plan must fit the same 200-command transaction limit +and model/source-capacity limits. A conflict-free preview also checks source +writeback, so its commands can update the same accepted authored workspace. + +SA→OA abstraction of explicit operation intent requires ``operation_omissions`` +(omitted means empty). Each closed record contains the selected invocation's +``source`` UUID and a nonempty ``rationale`` string. Missing, duplicate, +irrelevant or wrong-direction omissions reject. Reply derives its Call's +abstraction and has no second writable operation choice. Source operation +identity/values remain intact; target facts are mapped and validated. Preview +and Apply enforce the same 200 omission limit for ordinary and semantic transitions. + +OA ``CommunicationMean`` maps to an SA ``BoundaryExchange`` through an explicit +``communication_endpoints`` array (omitted means empty). Each decision identifies +the source communication means and both ``source_port`` and ``target_port``. +Each port choice is either ``{"Reuse":{"port":"port-uuid"}}`` or +``{"Create":{"id":"new-port-uuid","alias":"radio_port","name":"Radio port", +"description":"","kind":"Standard","orientation":"Unset"}}``. Created or +reused SA component ports must belong to the explicitly mapped participants. +These per-exchange choices do not replace participant correspondence with port +IDs. Missing or incompatible choices remain preview conflicts. Interface and +functional-port allocations use their existing explicit workflows; an engineer +may omit an unresolved allocation from this transition. Reverse transition maps +boundary ports through their owners to OA participants. + +``ExchangeCategory`` transitions preserve their family and map only explicitly +corresponding member exchanges. Missing member mappings are conflicts, not new +fabricated exchanges. Typed review exposes ``Category.Family`` and the +``CategoryExchanges`` member collection; historical review facts remain exact. + +The preview response is ``{"base_revision":1,"plan":{...}}``. It contains +planned changes, conflicts, warnings and exact commands/IDs. It does not write +the model or persist a server-side plan. Apply accepts that entire response and +requires the original ETag in ``If-Match``. It checks both base_revision and the +core's snapshot fingerprint before executing the shared transition validator. +Stale revisions fail with 412 and unresolved conflicts with 422. Successful +apply increments the revision once, preserving the planned identities. + +A supplied plan is a client-owned command proposal. It is not cryptographically +authenticated as an untouched server preview; the same authenticated client +can submit ordinary typed commands. The core fingerprint is a change detector, +not a security digest. There is no plan token, expiration store or receipt. + +Typed semantic transition review +-------------------------------- + +The sibling ``semantic-transitions`` routes use the `typed review semantics +`_ in ``language.rst`` sections 5.1–5.5. +The original prose transition routes keep +their existing contract. All new routes use JSON and the existing authentication, +capacity, worker and error boundaries. + +Query accepts ``{"traces":["trace-uuid"],"references":{},"revision":1}``. +``references`` is an optional map of canonical OA UUIDs to SA UUIDs, including +historical deleted identities. It never guesses correspondences from names. +``revision`` is optional; a mismatch returns 409. Select 1–100 unique existing +trace IDs; an empty or duplicate selection returns 400 and a missing trace +returns 404. The response includes the current revision and items containing +``trace``, ``oa``, ``sa``, typed ``facets``, ``drift`` and ``linked_impacts``. +Own and linked drift and their unreviewed flags remain distinct. Query reads one +immutable snapshot, does not refresh baselines, and does not fetch artifact URIs. + +Preview accepts ``SemanticTransitionRequest``:: + + { + "transition": { + "direction": "OaToSa", + "sources": ["40000000-0000-4000-8000-000000000002"], + "scope": "SelectedOnly", + "mappings": [{ + "source": "40000000-0000-4000-8000-000000000002", + "target": "40000000-0000-4000-8000-000000000003", + "target_kind": null + }], + "fields": [], "resolutions": [], "destination_diagram": null + }, + "traces": {}, "references": {}, + "reviews": [{ + "source": "40000000-0000-4000-8000-000000000002", + "resolutions": [ + {"facet":{"Header":"Name"}, "decision":"TakeSource"}, + {"facet":{"Data":"Unit"}, "decision":"KeepTarget"} + ], + "reset": [] + }], + "candidates": [], "accepted_impacts": [] + } + +The example maps an existing OA data type to its traced SA counterpart, takes +only its name and acknowledges intentionally different units. Other facets stay +unreviewed or retain their earlier drift. Select an explicit trace through the +``traces`` map (source UUID to trace UUID) when multiple trace kinds connect the +pair. ``reset`` deliberately invalidates exact old selectors after changed +correspondence. The optional collections default to empty. Semantic header +decisions use typed facets; leave legacy ``transition.fields`` and +``transition.resolutions`` empty. + +Facet selectors are closed enums, for example ``{"Data":"IntegerMinimum"}`` +or ``{"Member":{"collection":"SignalItems","keys":{"oa":"oa-uuid","sa":"sa-uuid"}}}``. +Annotation keys contain the entire path and field; enum literal keys are exact +strings. ``KeepTarget``, ``TakeSource`` and ``Candidate`` operate on independently +selected facts. Candidate records use existing Element/Relation/ArtifactLink +types, must preserve identity/alias, and must equal precisely the result of +the selected facets. Constructor review covers its coupled introduced/removed +fields. Membership review does not silently acknowledge order; target-only +members remain independent. OpenAPI defines every selector and typed payload. + +Preview requires ``If-Match`` and returns ``{"base_revision":1,"plan":{...}}``. +The plan contains the original typed request, mapping plan, review facts, +acknowledged/retired selectors, linked impacts, exact prepared impacts, conflicts, +commands, and base/prepared fingerprints. A conflict-free preview also prepares +the complete authored source bundle and checks its model equivalence and capacity. +No preview changes the model, source, revision or stored bytes. + +If ordinary preparation induces additional cleanup, the plan reports exact typed +before/after impacts and a conflict. Review those impacts, place the accepted +records in ``accepted_impacts``, and request another preview. Acceptance cannot +override a contradictory selected facet. Missing mappings, ambiguous order, +duplicate decisions, invalid candidates or unresolved impacts cannot be applied. +This is the shared core planner and command preparation, with no server-specific +semantic mutation engine. + +Apply accepts the entire preview envelope under the same ``If-Match``. It checks +``base_revision``, the model fingerprint, unresolved conflicts, the prepared +candidate fingerprint, ordinary model validation and coupled source preparation, +then atomically stores one accepted model/source revision. The final store check +also catches a concurrent edit during preparation. Source-only edits invalidate +the preview through the shared ETag even when the model fingerprint is unchanged. +Stale ETag/revision returns 412; semantic rejection returns 422 with code +``semantic_transition_rejected``. Exact no-op candidates preserve revision. +Read and preview again after a successful changed apply before reviewing further. + +A supplied plan remains a client-owned candidate. Fingerprints detect stale or +inconsistent proposals; they are not tamper-proof signatures, authentication +receipts or proof of original review authorship. Apply preserves preview-generated +identities and does not rerun the planner with fresh random IDs. Authenticated +clients already have ordinary model/source editing privileges. The server does +not persist plans, tokens or review sessions. + +Requests allow 100 sources, trace choices and reviews; 200 mappings, candidates +and accepted impacts; 5,000 canonical reference mappings; and 5,000 facet +resolutions plus resets across all reviews. The legacy 400 prose-resolution +limit, 200 communication endpoint decisions, 200 operation omissions and +200-command transaction limit still apply. Query has the same 5,000 +reference-map limit. Core bounded linked traversal errors remain observable as +semantic rejection; no truncated result silently claims complete review. + +Schema/source 5 adds ``TraceBaseline.semantic``. Absence or null is Unreviewed; +migrating older prose baselines does not fabricate semantic history. The persisted +baseline contains only closed typed ``FacetReview`` facts, not whole candidate +records or a generic JSON value tree. Historical UUIDs remain historical, including +after deletion, and source uses explicit ``uuid`` tokens for them. + +Error contract +-------------- + +All errors have ``Content-Type: application/problem+json``. Example:: + + { + "type":"urn:arcadia:problem:revision_conflict", + "title":"Precondition Failed", + "status":412, + "code":"revision_conflict", + "detail":"Project changed. Reload and review the current model before retrying.", + "current_revision":3 + } + +``issues`` contain severity, code and message, and may include field, +object_id, line, column, source ``path``, half-open UTF-8 byte ``span`` +(``start``, ``end``), and ``related`` source locations (path/span). Lines and +columns are one-based; columns count Unicode scalars. Use +``code`` for client decisions, not the English message. ``X-Request-ID`` is a +response correlation UUID. This is not an idempotency key or receipt address. + +* 400: malformed JSON/envelope/query/UUID or unsupported ETag form. +* 401: missing or incorrect configured bearer token; includes WWW-Authenticate. +* 403: browser Origin is unsupported, or unauthenticated Host is not loopback. +* 404: missing project or route. +* 405: unsupported HTTP method. +* 409: requested query revision changed or revision counter exhausted. +* 412: stale write precondition. +* 413: body, serialized model, stabilized source bundle or envelope exceeds its size limit. +* 415: unsupported Content-Type or Content-Encoding. +* 422: rejected source/model/command, model count limit, or missing graph root. +* 428: missing If-Match. +* 503: work slots full or filesystem/storage unavailable; Retry-After is present. +* 504: request deadline; an admitted write outcome may be unknown. +* 507: fixed store cap of 128 models or 256 MiB serialized envelopes reached. +* 500: unexpected server/worker failure. + +Deployment and persistence limitations are documented in ``server.md``. diff --git a/docs/language.html b/docs/language.html index a9d889f5..7ae47436 100644 --- a/docs/language.html +++ b/docs/language.html @@ -1,2844 +1,2844 @@ - - - - - - -Arcadia Studio OA / SA Language and Editing Contract - - - - -
-

Arcadia Studio OA / SA Language and Editing Contract

-
-
Version:
-
7 (integration in progress)
-
Status:
-
Source-7 contract under integration; native and API acceptance pending
-
Core implementation:
-

crates/oa-core

-
-
Text suffix:
-

.arcadia

-
-
-
-

1. Scope and conformance

-

This specification defines the shared Operational Analysis (OA) and System -Analysis (SA) model, its textual language, graphical editing obligations, -validation, persistence, and explicit migration from versions 1, 2, 3, 4, 5 and 6. MUST, MUST NOT, -and MAY indicate requirements, prohibitions, and permitted choices.

-

Schema 7 adds caller-qualified process/chain links and explicit interface-operation -selection on boundary scenario invocations. Schema 6 added OA communication means, -exchange categories and saved view options. Integration and acceptance are tracked -separately; this version label does not claim completed graphical/API parity. -Schema 5 added closed typed semantic review facts to trace baselines, including -historical references that may no longer exist in the live model. It retains -schema 4 bounded reusable data types, typed defaults, exchange mechanisms, -qualified values, specialization, interface allocations and associations, and -schema 3 typed ports and state views. Explicit older readers preserve meanings -and identities without inventing previously reviewed semantic values. -This contract does not by itself establish native acceptance or release completion.

-

OA describes stakeholders, operational work and needs. SA describes the -selected system boundary, external actors, functions, services and expected -behavior. SA permits at most one System of Interest; its absence is an -incomplete-model warning when other SA elements exist. The SA System MUST NOT -contain internal components. Functional decomposition is independent of that -structural boundary. Logical Architecture, Physical Architecture, deployment, -Capella file interchange, and executable behavioral simulation are outside -this specification. This independent language does not claim complete Arcadia, -Eclipse Capella, or SysML conformance.

-

An implementation conforms only when each supported field and relationship is -reachable in both graphical and textual editing, preserves identity through -save/open and undo, and uses the same structural validation. Merely displaying -a name or storing an opaque payload does not establish graphical support. -Actual verification evidence is separate from this contract.

-
-
-

2. One authoritative model

-

A Project stores schema_version (7), id, alias, name, -description, and maps named elements, relations, diagrams, -artifact_links, and traces. Each map is keyed by the UUID contained in -its record. Duplicate map keys and mismatching keys/identities are errors.

-

All objects share one UUID and alias namespace: Project, Element, Relation, -Diagram, NodePlacement, ArtifactLink, and Trace. UUIDs are durable identity; -aliases are durable, editable source labels. Display-name changes MUST NOT -regenerate existing aliases. Different objects MAY have the same display name.

-

Every Element has id, alias, name, description, parent -(optional element reference), and a typed body. Its kind and OA/SA layer -are derived from its body. A Relation has id, alias, name, -description, and a typed body; it has no independently editable kind tag -that can disagree with its payload.

-

Diagram placement does not determine semantic ownership. NodePlacement -records belong to diagrams and reference semantic elements. Multiple diagrams -can show the same element with independent placement and collapse state. -Within one diagram, each element appears at most once.

-

All stored strings MUST contain only XML 1.0 characters: U+0009, U+000A, -U+000D, U+0020–U+D7FF, U+E000–U+FFFD, or U+10000–U+10FFFF. Strings are not -trimmed or Unicode-normalized. Escaping a forbidden character does not permit -it. Coordinates and sizes are finite IEEE-754 binary32 values with absolute -value at most 1000000. Width and height MUST be positive. Signed zero and -subnormal coordinates are retained.

-
-
-

3. Element declarations and typed fields

-

Each element declaration starts with its layer and kind, for example -oa_activity or sa_function. Every element body accepts -description (default "") and parent (default none for a -document-level declaration, or its lexical element owner when nested; section -6.2). The table -lists all additional fields. ref means @alias; ref? also accepts -none. Lists preserve order. Unknown or duplicate fields are errors.

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Element body field catalog

Declarations

Additional fields

Defaults

oa_entity, oa_actor, oa_role, oa_activity, oa_capability, oa_mission

None

Empty body

sa_system, sa_actor, sa_function, sa_capability, sa_mission

None

Empty body

oa_process, sa_functional_chain

steps: occurrence refs

[]

oa_exchange_item, sa_exchange_item

kind: Information, Material, Energy; mechanism: ExchangeMechanism; supertype: item ref?

Information; Unspecified; none

oa_exchange_category, sa_exchange_category

family: Work or Participant; exchanges: same-layer exchange refs

Work; []; root-level only

oa_data_type, sa_data_type

definition: DataTypeDefinition; default: DataValue?; unit: string

String with minimum_length 0 / maximum_length none; none; ""

oa_data_field, sa_data_field

type_ref; minimum: u32; maximum: optional u32; unit: string; direction: ParameterDirection; storage: FieldStorage; default: DataValue list?

String; 1; 1; ""; Unspecified; Value; none

oa_interface, sa_interface

supertype: interface ref?

none; membership is an identified interface_item_allocation relation

oa_port, sa_port

contract: required PortContract; legacy: optional LegacyPortContract

No implicit contract; legacy defaults none. OA admits Legacy only.

oa_scenario, sa_scenario

viewpoint: ScenarioViewpoint; context: ref?; precondition, postcondition: strings; steps: occurrence refs

MixedLegacy when viewpoint is omitted, including newly authored source 7 (see section 4.3); none; ""; ""; []

oa_lifeline, sa_lifeline

represented: participant, work, or role ref?

none

oa_occurrence, sa_occurrence

body: occurrence constructor; precondition, postcondition: strings

Exchange with none references, Asynchronous message kind and no values; two empty strings

oa_state_machine, sa_state_machine, oa_region, sa_region

None

Empty body

oa_state, sa_state, oa_mode, sa_mode, oa_initial, sa_initial, oa_final, sa_final

kind: StateKind; entry, exit, do_activity: strings

Kind selected by declaration; three empty strings

oa_choice, oa_junction, oa_fork, oa_join, oa_shallow_history, oa_deep_history, oa_entry_point, oa_exit_point, oa_terminate and corresponding sa_ declarations

Same State body; StateKind is Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint or Terminate

Kind selected by declaration; three empty strings

oa_event, sa_event

body: Signal, Time or Change constructor

Signal with items []

oa_constraint, sa_constraint

expression: string; constrained: semantic record refs

""; []

-

The explicit state kind field MUST agree with its declaration keyword. -u32 is an integer from 0 through 4294967295. A DataField's maximum MUST -be at least its minimum; none means unbounded. Primitive TypeRefs are -Boolean, Integer, Real, and String. A named TypeRef uses a DataType reference. -An Enumeration contains unique string literals. For example:

-
definition { Enumeration { literals ["Clear", "Fog"]; }; };
-type_ref { Named @weather_classification; };
-

These are typed constructors with named fields, not embedded JSON documents. -Constructor field names and enum spellings are case-sensitive. A selected -constructor uses its own field contract; it does not inherit the payload of a -different constructor from the declaration default. Required fields MUST be -supplied. Nullability and omission are separate properties: ? denotes a -value that can be none; a constructor's omission contract determines whether -the field may be absent. There is no general all-constructor-fields-required -rule. Other typed constructors use their contracts in the corresponding -sections; the occurrence rules below do not establish defaults for them.

-

The following table records the source-7 OccurrenceBody contracts. Optional -references may be omitted and then mean none. Exchange also admits omitted -message_kind and values, meaning Asynchronous and [] respectively. -These omissions are accepted in newly authored source 7, not only in schema-2 -compatibility input. Earlier schema admission of constructors and fields is -still governed by its version rules.

-

Canonical formatting writes the hydrated occurrence references and default -message_kind, reply_to and values explicitly. An absent -operation remains omitted in canonical output. Lossless no-op updates and -unrelated name edits retain authored omissions; canonical formatting is a -different operation. An admitted incomplete occurrence can still produce model -checks requesting its missing work, exchange, lifeline or referenced scenario.

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Occurrence body constructors

Constructor

Required fields

Fields that may be omitted, with their resulting values

Exchange

None

exchange: exchange relation ref?, sender, receiver: lifeline refs?, reply_to: occurrence ref?, operation: OperationUse? → none; message_kind: MessageKind → Asynchronous; values: MessageValue records → []

Action

None

action: activity/function ref?, lifeline: ref? → none

Control

kind: Choice, Merge, Fork, Join, Loop

None

SequenceReference

None

sequence: process/functional-chain ref? → none

ScenarioUse

bindings: LifelineBinding records

scenario: scenario ref? → none

State

None

state: state/mode ref?, lifeline: ref? → none

Fragment

operator: Alternative, Parallel, Loop, Optional; branches: branch occurrence refs

None

Branch

guard: string; occurrences: occurrence refs

None

-

For example, body { Action { action @assess; lifeline @operator_line; }; }; -selects an Action occurrence. Processes and functional chains contain Exchange, -Action, Control and SequenceReference occurrences. Process/chain Actions are -contextual work contributions and MUST have lifeline none. A Control or -SequenceReference MUST belong directly to a Process or FunctionalChain, not a -Scenario. Scenarios contain Exchange, Action, State, Fragment, Branch and ScenarioUse -occurrences according to their enclosing ownership rules. Each -listed occurrence belongs to its enclosing sequence or branch through -parent. A Branch belongs to a Fragment; the Fragment's branches list -references the branches it owns.

-

An exchange and an occurrence of that exchange have different identities. -Repeating an exchange requires distinct occurrence records. Repeating one -occurrence identity within an ordered list is invalid. Conditions, guards, -entry/exit/do text, and constraint expressions are descriptive strings; this -version does not parse or execute them as a programming language.

-
-
-

4. Containment, allocation, and relationships

-

Containment MUST be acyclic and stay within one layer. Entities may contain -entities, actors, and roles. Actors are not structural containers. Activities -contain activities; functions contain functions. Capability grouping uses -same-layer capability parents; it does not mean inheritance or inclusion. -Operational and system missions are root semantic records; they do not own -capabilities by containment. MissionCapability expresses that association. -DataFields belong to ExchangeItems or Record DataTypes. Preserved generic ports belong to -participants or work. Lifelines belong to scenarios. State-machine ownership -uses participant → machine → region → state/mode; a state or mode can contain -a nested region. All other state vertices belong to Regions as well. Events -are root semantic records and do not acquire a machine owner when referenced.

-

DataFields, ports, lifelines, occurrences, Regions and all state vertices -require an owner. An unowned StateMachine can be saved as incomplete -work and produces a warning. The System cannot own internal components. -The present Studio profile does not decompose SA Actors.

-

All relations have a header identity, alias, display name, optional -description (default ""), and required source and target -endpoints. Ordinary endpoints are semantic references; ContextualLink endpoints -are qualified OccurrenceEndpoint records resolved from its required owner. -Their kind is the declaration keyword:

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Relation catalog

Declaration

Endpoint meaning

Additional fields / defaults

allocation

Same-layer participant → leaf activity/function

None

interface_item_allocation

Same-layer Interface → ExchangeItem

order u32 / 0; transmission TransmissionProtocol / Unspecified; acquisition AcquisitionProtocol / Unspecified

data_association

Same-layer Record DataType → Record DataType

source_role, target_role / ""; source_minimum, target_minimum / 0; source_maximum, target_maximum / none; source_navigable, target_navigable / false

interaction

OA activity → OA activity

items refs / []

functional_exchange

SA function → SA function

Required source_port, target_port refs; items / []

boundary_exchange

Different participant owners; typed component ports have matching kinds

oriented bool / true; items refs / []

communication_mean

Different OA Entity/Actor elements; no operational ports or role inference

oriented bool / false; items refs / []

port_allocation

SA Component port → SA FunctionInput/FunctionOutput port

Requires the function's actual work Allocation to that component

capability_involvement

Participant, activity/function, or OA role → capability

None

chain_involvement

Process/functional chain → capability

None

role_realization

OA entity/actor → OA role

None

role_assignment

OA role → leaf OA activity

None

mission_capability

OA mission → OA capability, or SA mission → SA capability

None

mission_involvement

OA entity/actor → OA mission, or SA System/Actor → SA mission

None

capability_include

Including capability → included capability in one layer

None

capability_extend

Extending capability → extended capability in one layer

condition string / ""

capability_generalization

Specific capability → general capability in one layer

None

sequence_link

Preceding contribution → following contribution in one process/chain

guard string / ""

contextual_link

Qualified occurrence endpoints resolved from an explicit owner Process/FunctionalChain; section 4.2.1

Required owner, source and target OccurrenceEndpoint records, and typed purpose: Precedence, Exchange or CommonWork

state_transition

State vertices in one machine, subject to section 4.4

trigger, guard, effect strings / ""; transition_kind External; triggers, effects reference lists / []

availability

Work, participant, capability, or process/chain → state/mode

None

exchange_allocation

CommunicationMean → OA Interaction, or BoundaryExchange → same-layer Interaction/FunctionalExchange

None

interface_provision, interface_requirement

Participant/port → provided/required interface

None

state_action

State/mode → activity/function

phase: Entry, Do, Exit / Do

-

Relation endpoints and carried ExchangeItems stay in the same layer. Parallel -exchanges with distinct identities are permitted. Self-relations are invalid -except for a state transition that returns to its source state or an explicit -DataAssociation between two ends of the same Record type.

-
-

Source-6 communication and exchange categories

-

An OA communication_mean joins two distinct Entity/Actor records directly. -Its stable source/target tuple does not create operational ports. An unoriented -means permits either direction; an oriented ordinary use follows source to target. -The empty carried-item set is explicit, not a wildcard. Every item carried by an -allocated Interaction must specialize or equal a declared means item, using the -same inheritance rule as existing exchange allocations.

-

exchange_allocation remains a separately identified relation. A means may -allocate several Interactions and an Interaction may have several explicit means. -Allocation compares actual activity-to-participant Allocations, without inferring -responsibility from roles or nesting. Every known performer must fit one complete -permitted endpoint pair. A known contradiction is an error; a missing actual -performer is an incomplete-model warning. Reorienting or reconnecting a means -validates all dependent allocations and scenario uses in one candidate.

-

ParticipantInteraction and compatible MixedLegacy scenarios may reference means. -For bidirectional use, explicit sender and receiver lifelines determine one -compatible ordered endpoint pair. Missing or ambiguous bindings do not guess a -direction. A Reply reusing its Call's means reverses the resolved actual Call, -including a reverse Call on an unoriented means. The existing operation-response -exception permits the correlated Reply on an oriented means; this implies no -physical transport property. OA process contributions continue to use Interaction.

-

Mapped unoriented SA BoundaryExchange messages use the same explicit lifeline -resolution. Ordinary messages also respect component-port direction; legacy -port boundaries retain their stored direction. A correlated same-channel Reply -still reverses the actual Call, including across directional Flow ports.

-

CommunicationMean and SA BoundaryExchange support Exchange traces. OA-to-SA -transition requires a separate component-port choice for each end of each means: -reuse an existing compatible port, or create a port with explicitly chosen -Standard/Flow kind and orientation. Participant correspondence never substitutes -a port UUID for its participant UUID. Interfaces and PortAllocations are not -invented; unresolved technical allocations must be reviewed or omitted from the -selection and authored explicitly. Reverse transition projects SA port owners to -reviewed OA participant counterparts, without copying typed SA ports into OA. -Source/Target review retains the exact participant/port identities in history. -Taking an OA endpoint requires the scoped technical port decision, while Keep -and a valid Candidate retain explicit technical choices.

-

oa_exchange_category and sa_exchange_category are root-level named -semantic elements with family Work (default) or Participant, and exchanges -references (default []). Work categories contain Interaction/FunctionalExchange; -Participant categories contain CommunicationMean/BoundaryExchange. Members must -be distinct and in the category's layer; categories never own or merge exchanges. -Category transition requires mapped counterpart members. Category.Family and -Member { collection CategoryExchanges; ... } are closed typed semantic facts. -Deleting an exchange removes its category membership; deleting a category does -not remove its exchanges. Reidentification preserves all live references and -typed historical reference provenance.

-

Each Diagram has optional view_options (all selections unrestricted, no hidden -edges, Name labels by default). Its closed fields are hidden_edges, -relation_kinds, work_categories, participant_categories, coverage, -work_contexts and exchange_labels. Relation kinds use All or Only with -Relation(kind)/Trace entries; Only([]) draws no lines, including traces. Category -selectors use All or Any(category IDs); Any([]) draws none of that exchange family. -Selected categories are unioned within each family; independent restrictions -intersect. Coverage is All/InBehavior/OutsideBehavior; work contexts are All or -Any(explicit process, chain or scenario IDs). Contexts follow ordered owned -contributions and explicit reuse, sharing the advisory diagnostic evidence walk. -Labels are Name, Items or NameAndItems. These settings alter presentation only; -automatic port positions use unfiltered eligible lines. Hidden routes and semantic -identities remain saved. Removing a referenced selection preserves explicit empty -filters rather than widening them to All.

-

Traceability views retain their established two-layer projection: default options -preserve eligible OA and SA lines. Their hidden-edge, category and context -references may therefore select either layer. All ordinary projections require -these references in the declared diagram layer. A filter never hides the other -layer merely because the Traceability diagram has an OA or SA layer label.

-

Versions 1–5 reject the new relation, category constructor, diagram field (even -an explicitly supplied default), and category historical facts. Reading version 5 -preserves existing records, legacy OA boundaries and reviewed facts, with an -explicit upgrade report to the current version 7. Source upgrading changes the version token -without rewriting unrelated authored trivia. No old boundary is automatically -converted into communication means. Source-6 graphical/API/native acceptance is -still tracked separately from this integration contract.

-

Direct Allocation has at most one performer per leaf. Role sharing is a -different relationship: several OA participants can realize a Role. A direct -allocation inconsistent with its role's realized performers produces an -advisory warning; it does not prevent saving the model. Shared roles do not -imply a unique performer. -Allocation of a non-leaf work element is invalid. A computed graphical summary -of its allocated leaves MUST NOT create an extra semantic allocation.

-

Typed function ports belong directly to SA Functions. FunctionOutput -declares outgoing_items; FunctionInput declares incoming_items. -The lists contain distinct same-layer ExchangeItem references. A functional -exchange MUST reference output/input ports owned by its respective functions, -and each carried item MUST fit both declarations. Empty declarations accept -no items. A carried item satisfies an identical declaration or a declaration -of one of its explicit base items. A base item does not satisfy a more -specialized declaration; names imply no compatibility. Port declarations -remain independent of incident exchanges after creation.

-

A Component contract belongs to an SA System or SystemActor and stores -kind (Standard or Flow) and orientation (Unset, In, Out, InOut). -Standard requires Unset. Flow Unset is an incomplete draft, not InOut. Typed -component connections require matching kinds: Standard/Flow mismatch is an -error. Standard connections normally use oriented false; directional Flow -connections use true. Boundary orientation is separate from endpoint kind and -orientation. Oriented Flow endpoints permit Out at source and In at target; -unoriented Flow endpoints must permit at least one communication direction.

-

Provided/required interfaces are separate identified relations. A Standard -partner satisfies a requirement by providing the identical interface; stored -source/target order does not infer service roles. Component carried items must -fit the declared interface-item union when explicit declarations exist. An -explicitly empty interface is not a wildcard. Missing declarations and -unsatisfied required interfaces produce located completeness warnings.

-

PortAllocation relates a Component port to a typed function port only when -that function has an actual Allocation to the component owner. Standard may -allocate either function direction; Flow directions must agree. A typed -ExchangeAllocation requires both corresponding PortAllocations and compatible -carried items. Unoriented boundary exchanges allow reversed correspondence. -Visual allocation nesting is never evidence of a stored allocation relation.

-

Legacy requires legacy { direction ...; interface ...; }. It preserves -generic direction/interface data and emits a located compatibility warning. -It is an explicit compatibility constructor in JSON/source; ordinary creation -pickers do not offer it. Resolution to a typed SA contract retains this record -without treating it as another active constraint. OA generic ports remain -Legacy; OA Interactions stay activity-to-activity without function ports. -Deleting a referenced legacy interface clears that optional live reference.

-

A current-source functional exchange MUST specify both port aliases; the parser does -not create endpoints. Graphical/command FE creation plans ports atomically, -using unique compatible typed-port reuse or deterministic new endpoints. -Port deletion cascades to incident FEs; FE deletion retains independent ports.

-

For example:

-
arcadia 7;
-project ports_example "Typed function contract" {
-    sa_function send "Send" {}
-    sa_function receive "Receive" {}
-    sa_exchange_item payload "Payload" {}
-    sa_interface old_service "Preserved service" {}
-    interface_item_allocation service_payload "Service payload" { source @old_service; target @payload; }
-    sa_port output "Output" {
-        parent @send;
-        contract { FunctionOutput { outgoing_items [@payload]; }; };
-    }
-    sa_port input "Input" {
-        parent @receive;
-        contract { FunctionInput { incoming_items [@payload]; }; };
-    }
-    functional_exchange message "Message" {
-        source @send; target @receive;
-        source_port @output; target_port @input; items [@payload];
-    }
-    sa_port preserved "Unresolved original" {
-        parent @send; contract Legacy;
-        legacy { direction InOut; interface @old_service; };
-    }
-}
-

An exclusive region has at most one initial state. Missing initial state is -a completeness warning. Initial states have no incoming transitions and at -most one outgoing transition; their outgoing transition has no trigger/guard. -Final states have no outgoing transitions. Mixing State and Mode entries is -permitted by this Studio profile. StateAction links provide typed work -references alongside the descriptive entry/exit/do strings.

-
-
-

4.1. Capabilities and missions

-

Include means the including capability requires the included capability's -behavior. Extend means the source capability adds conditional behavior to the -target; its condition records when that behavior applies. Generalization -means a specific source capability specializes a general target capability. -Each relationship stays within OA capabilities or within SA capabilities. -Include and Generalization graphs MUST each be acyclic, including transitive -cycles. Extend does not introduce an additional cycle restriction; the ordinary -no-self rule still applies. An empty Extend condition is an engineering warning -so an incomplete capability map can be authored and saved.

-

MissionCapability and MissionInvolvement express need and stakeholder/system -participation. They MUST NOT create a capability parent, a performer allocation, -or a cross-layer realization. These relationships remain distinct when the -same objects appear in a diagram or share names.

-
-
-

4.2. Contextual process and chain topology

-

A Process or FunctionalChain owns contribution occurrences with stable -identities. steps is their persistent membership and presentation order; -an identity MUST occur at most once in that list. A work contribution points to -an existing same-layer Activity or Function. Reusing the same work or exchange -requires separate occurrence identities and MUST NOT duplicate the referenced -engineering element. A SequenceReference points to another same-layer Process -or FunctionalChain of the owner's kind. Its target may be none while the -draft is incomplete. Direct and transitive recursive sequence reuse are errors.

-

SequenceLinks connect distinct occurrences directly owned by the same process -or chain. Their source is the predecessor and target is the successor. A link -from work to an exchange requires that work to be the referenced exchange's -source; an exchange-to-work link requires the target work to be its endpoint. -Two adjacent exchange contributions require the first exchange's target to -match the second exchange's source. A mismatch is a structural error. Other -links express contextual precedence without manufacturing a global exchange.

-

When links or work/control/reuse contributions exist, topology is explicit. -Reordering steps MUST NOT add, remove or reorder SequenceLinks. The editor -MUST retain the difference between alternative Choice/Merge and parallel -Fork/Join contributions. Choice/Fork expect one incoming and at least two -outgoing links; Merge/Join expect at least two incoming and one outgoing link. -Loop expects at least one incoming and two outgoing links for repetition and -exit. These cardinalities are completeness warnings, not barriers to building -an incomplete flow. Choice and Loop outgoing paths with empty guards warn; -an explicit else guard is permitted. Guards remain descriptive strings.

-

Validation warns about missing/ambiguous entry and exit, isolated or unreachable -contributions, contributions with no reachable exit, and splits/merges that -lack explicit controls. Topology cycles are permitted for loops; the recursive -SequenceReference prohibition is a different rule. A legacy exchange-only -sequence without explicit links retains its ordered linear meaning and -adjacent-exchange gap diagnostics. Implementations MUST NOT silently invent -topology links when loading or reordering such a sequence.

- -
-
-

4.3. Typed scenarios and reusable behavior

-

ScenarioViewpoint is one of OperationalActivity (OA activity lifelines), -ParticipantInteraction (OA entity/actor/role lifelines), Functional (SA function -lifelines), BoundaryInterface (SA System/Actor lifelines), or MixedLegacy. -The first two are OA viewpoints; the next two are SA viewpoints. Lifeline -references MUST agree with that perspective. OperationalActivity messages use -Interactions; Functional messages use FunctionalExchanges. Participant views -may use their layer's work exchanges through effective performer allocation, -or participant boundary exchanges through the relevant ports. Corresponding -lifelines and endpoints MUST agree; a viewpoint is not a label that suppresses -reference checks.

-

Omitted schema-2 viewpoints hydrate as MixedLegacy in both source and JSON. -This preserves pre-extension mixed work/participant scenes without discarding -their lifelines or silently assigning a stricter meaning. MixedLegacy emits a -review warning. Source parsing also uses MixedLegacy when viewpoint is -omitted from a newly authored source-7 scenario; it does not infer a strict -viewpoint from the layer or lifelines. Write the intended viewpoint explicitly -when starting a scenario. For example, this new OA source project starts an -activity-based scenario whose lifelines and occurrences can be added later:

-
arcadia 7;
-project response "Distress response" {
-    oa_scenario assessment "Assess the distress report" {
-        viewpoint OperationalActivity;
-    }
-}
-

An empty scenario can produce completeness warnings while remaining editable. -The desktop's Behavior New flow asks authors to choose one of the layer's -two strict viewpoints explicitly. Authors can also select a strict viewpoint -after reviewing a legacy scene; canonical source then records that choice.

-

MessageKind is Asynchronous, Call, Reply, Create, Delete, or -{ Timer { expression "after 5 s"; }; }. A process/chain exchange -contribution MUST remain Asynchronous with no reply correlation. Call/reply, -creation/deletion and timer semantics belong to scenarios. Reply's reply_to -references a Call occurrence in the same scenario. The call MUST precede the -reply on a guaranteed common sequence/enclosing branch path. Reply lifelines -reverse the call's sender/receiver; it may reuse the call's exchange in reverse -or use an explicit reverse-direction exchange. A second reply to the same call -on that guaranteed path is an error. Missing reply correlation is a draft -warning; non-Reply messages MUST NOT have reply_to.

-

Create identifies the receiver lifeline's creation. An earlier guaranteed use -of that lifeline is an error. Delete identifies its destruction; a later -guaranteed use is an error. Ordering checks respect branch ownership; they MUST -NOT flatten alternative branches into a false unconditional order. A Timer is -local to its sender lifeline, has no global exchange or values, and has either -no receiver or the same receiver as sender. Its nonempty expression describes -the delay/deadline; an empty expression warns. Conditions and expressions are -stored engineering annotations, not an executable simulation language.

-

Each MessageValue is { path [@parameter]; field @data_field; expression "value expression"; }. -Its identity is the pair (path, field), so two uses of one Record can have -different values for the same terminal field. Path contains enclosing DataField -references, excluding the terminal field; direct parameters use []. Every -hop MUST follow Value storage through a named Record's effective fields. Reference -storage stops traversal. Operation Call accepts In/InOut and Reply accepts -Out/InOut/Return, interpreted from the provider's perspective. Nested field paths -inherit their direct parameter's direction. Unspecified direction warns. -At most one annotation for a pair is allowed. Omitted path defaults to []; -an ambiguous preserved legacy nested annotation warns message_value_ambiguous_path -until an explicit path is selected. New graphical choices always identify a path. -Values never overwrite types or items. Expression text is descriptive, preserved -exactly and not evaluated; an empty expression warns.

-
-

4.3.1. Explicit interface-operation invocations

-

The optional Exchange occurrence field operation distinguishes ordinary -messages from explicit operation intent. Omitted or none retains ordinary -channel semantics and is omitted in canonical output. A present closed record -with absent/none allocation means an explicitly unresolved operation. -A present allocation @service_operation selects that exact -InterfaceItemAllocation relation, not its item UUID, a name, a port or whichever -allocation happens to carry the same parameters. Thus parameterless Reset and -ReadStatus operations on one channel remain distinct maintained identities.

-

Operation intent is permitted only on SA BoundaryInterface scenario Call or -Asynchronous occurrences. A selected allocation MUST expose an Information -Operation item through the actual concrete Standard component ports used by the -invocation: receiver Provides, sender Requires a compatible interface exposing -that same allocation, using the normal interface inheritance rules. Containment -does not inherit participant interface roles onto a port. A separately allocated -same item is not the selected allocation. The actual channel traversal determines -direction, including an unoriented channel used in reverse. Call requires -Synchronous transmission; Asynchronous requires Asynchronous transmission; -Unspecified is incomplete and warns. A selected allocation without these proofs -is an error. Explicit unresolved intent remains editable with a located warning.

-

Reply MUST store no operation selection. It derives allocation, provider and -signature from its exact correlated Call, while independently satisfying the -existing reply order, reverse-participant and channel checks. It may reuse the -Call channel in reverse or use a separate explicit return channel. The separate -channel MUST carry the selected item or an admitted subtype and satisfy its own -actual endpoint contract, including for a parameterless/void return. The Reply -receiver need not provide the operation; the provider was established by the -Call. No operation or correlation may be guessed from similar names or fields.

-

Selected Call/Asynchronous values use only that operation's In/InOut signature; -Reply values use Out/InOut/Return. All ordinary nested-path and multiplicity rules -still apply. Unresolved operation intent preserves still-live expressions and -performs independent live-reference/path checks with an incomplete-signature -warning. It MUST NOT fall back to the union of items carried by the channel. -Selecting a new allocation validates all retained values and refuses incompatible -values without silently pruning them or creating defaults.

-

Deletion cleanup clears a deleted selected allocation to explicit unresolved -intent and preserves still-live values; it does not erase the Call or Reply. -The whole candidate MUST still satisfy independent channel contracts. Deleting -the sole allocation can therefore refuse while a boundary still carries its -now-unadmitted item. An explicit repair batch must address every affected channel, -including a separate Reply channel. If a surviving allocation admits the item, -deletion can succeed, but it MUST NOT select that surviving allocation as the -operation. Item/field deletion uses the normal proven-reference cleanup rules.

-

SA→OA transition of operation intent requires one explicit -OperationOmission { source, rationale } per selected invocation. Rationale -MUST be nonempty. Missing, irrelevant, duplicate or wrong-direction decisions -are errors. Reply derives its Call's abstraction without a second operation -choice. The source model retains its operation; other facts and values are mapped -explicitly and the complete target is validated. The transition MUST NOT invent -OA technical ports, operation identities or functional refinement to avoid this -decision.

-
-
-

4.3.2. Scenario reuse

-

ScenarioUse references an existing same-layer scenario and maps its lifelines -with bindings [ { referenced @callee_line; local @caller_line; } ];. -Each binding is one-to-one and references a direct lifeline of the appropriate -scenario. Bound lifelines represent the same semantic object, or an explicitly -realized operational role. Strict viewpoints MUST agree; MixedLegacy retains -the compatibility case until reviewed. Missing bindings produce warnings. -Direct/transitive recursive scenario use is invalid; repetition uses a Loop -fragment instead. ScenarioUse owns neither its referenced scenario nor its -lifelines; deleting a use does not delete the reusable definition.

-
-
-
-

4.4. State vertices, events and transition contracts

-

StateKind comprises State, Mode, Initial, Final, Choice, Junction, Fork, Join, -ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate. Every vertex -belongs to a Region. Only State and Mode are stable states: only these may own -nested Regions, typed StateAction links, availability and scenario state -observations. Multiple sibling Regions describe orthogonal configurations. -EntryPoint and ExitPoint retain Region ownership; their boundary is that -Region's owning State/Mode or StateMachine. Their graphical symbols belong on -that boundary. This is Studio's explicit containment encoding.

-

Events are ordinary same-layer semantic elements, independent of diagram -placements. EventBody has exactly these constructors:

- - - - - - - - - - - - - - - - - - -
Event definitions

Constructor

Fields and meaning

Signal

items is a unique list of same-layer ExchangeItem references; empty is valid

Time

kind is After or At; expression is descriptive time text

Change

expression describes the Boolean condition becoming true

-

After measures relative time from activation of a transition's source state; -At describes an absolute instant. Empty Time/Change expressions are located -completeness warnings. The application does not execute expressions, fire -timers or transport signals. For example:

-
sa_event timeout "Recovery timeout" {
-    body { Time { kind After; expression "30 s"; }; };
-}
-sa_event recovered "Pressure recovered" {
-    body { Change { expression "pressure > 4 && status == \"Ready\""; }; };
-}
-

StateTransition's triggers references Events or layer-appropriate work -(OA Activity / SA Function). Referenced work need not have the machine owner's -performer: external work can cause the observed transition. Multiple triggers -are alternatives. effects references work describing effects, without -executing it. Duplicate, missing, wrong-layer or wrong-kind references are -errors. The existing trigger, guard and effect strings remain -authored prose; they MUST NOT be renamed, parsed as UUIDs or inferred by name.

-

With typed triggers, trigger adds descriptive information. With no typed -triggers and nonblank trigger prose, the transition has an unresolved -descriptive trigger and emits a warning. Both empty denotes completion from -a stable state or transient routing from a pseudostate. Deleting the final -typed trigger when prose is empty MUST leave an explicit descriptive reminder; -it MUST NOT silently create a completion transition. Guards/effect prose remain -unchanged. Signal payload references and typed effect references are pruned -when their targets are removed.

-

TransitionKind is External, Local or Internal. External leaves/re-enters the -affected configuration; a stable self-transition includes exit and entry -behavior. Internal requires the same stable source and target, preserves nested -configuration, and does not imply entry/exit. Local requires a composite stable -source and proper descendant target, preserving the source activation; an -EntryPoint also routes locally into its boundary. Local self/outside targets -are invalid. Other pseudostates route externally. Endpoints MUST share a layer -and StateMachine; crossing nested Regions within that machine is not generally -forbidden. A pseudostate self-transition is invalid.

-

A simple state's completion means entry/do behavior is complete. A composite -completes when its active Regions complete. Final completes its containing -Region; Terminate ends the containing machine's activation. These are modeling -meanings, not evidence of a running state-machine engine.

-

Missing connections/guards and unfinished initial/history paths produce -warnings, so an engineer can insert vertices before wiring them. Contradictory -upper cardinalities, forbidden directions/kinds and invalid references reject.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Vertex connection constraints

Vertex

Structural restrictions

Incomplete-model warnings

Initial

At most one per Region; no incoming; at most one outgoing; no outgoing trigger or guard

Missing initial/default entry or outgoing

Final / Terminate

No outgoing or nested Region

No incoming

Choice

At most one incoming; outgoing transient alternatives

Missing incoming, fewer than two outgoing, missing guard or else

Junction

Transient routing, no implicit concurrency

Missing side or ambiguous unguarded alternatives

Fork

At most one incoming; outgoing stable targets in distinct orthogonal Regions

Missing incoming or fewer than two outgoing

Join

At most one outgoing; incoming stable sources in distinct orthogonal Regions

Fewer than two incoming or missing outgoing

ShallowHistory

At most one per Region; at most one fallback to a direct stable child of that Region; no fallback trigger/guard

Missing fallback/default entry

DeepHistory

At most one per Region; at most one fallback to a stable descendant of that Region; no fallback trigger/guard

Missing fallback/default entry

EntryPoint

Incoming from outside boundary, outgoing Local into it; at most one outgoing per destination Region

Missing outgoing or incomplete affected-Region entry

ExitPoint

Incoming from inside boundary; at most one outgoing External to outside

Missing input/output; top-level exit is an exit endpoint, not implicit termination

-

Fork outputs and Join inputs MUST NOT carry guards or typed/descriptive -triggers. Their distinct orthogonal Regions are the first Regions beneath the -nearest common composite or machine, not merely each endpoint's immediate -parent. Choice/Junction routing expresses conditional alternatives. Retained -pseudostate entry/exit/do prose produces a warning and remains editable; it -does not become a typed state behavior. StateAction remains the maintained -reference for entry/do/exit work on stable states.

-

Other pseudostate outgoing segments MUST NOT introduce event/work triggers -or trigger prose. A Choice has at most one literal else guard; the tool -does not prove arbitrary guard expressions disjoint or exhaustive. Choice -expresses a dynamic decision after incoming effects; Junction expresses a -compound path selected by guards before traversing it. ShallowHistory remembers -the immediate state and uses default entry for deeper configuration; -DeepHistory remembers nested configuration. Fallback applies when no resumable -history exists. History is per Region, including top-level Regions in this -Studio profile; no running history state is persisted or synthesized.

-
-
-

4.5. Data contracts, specialization and interface allocation

-

ExchangeItem kind describes nature: Information, Material or Energy. -mechanism is Unspecified, Flow, Event, Operation or SharedData. Unknown -mechanisms remain explicit Unspecified drafts with a warning. Material/Energy -with Event/Operation/SharedData warns for engineering review; it is not silently -reclassified. Empty Event notifications and void Operations are valid. An empty -carried/allocated Flow, SharedData or Unspecified item warns.

-

An Operation's directly owned DataFields are parameters. direction is -Unspecified, In, Out, InOut or Return, from the provider's perspective. At most -one effective parameter is Return. Unspecified parameters warn; non-operation -fields MUST use Unspecified. Nested record fields inherit direction from their -direct parameter when used in message paths, rather than storing context on a -shared type. storage is Value or Reference. Reference requires a named Record, -has no explicit default, and does not convey ownership or deletion of the type.

-

DataTypeDefinition has exactly the following constructors:

- - - - - - - - - - - - - - - - - - - - - - - - - - - -
Reusable definitions

Constructor

Required fields

Boolean

None

Integer

minimum, maximum: optional signed 64-bit integers

Real

minimum, maximum: optional finite IEEE-754 binary64 values

String

minimum_length: u32; maximum_length: optional u32

Enumeration

literals: unique nonempty, case-sensitive strings

Record

supertype: same-layer Record ref?

-

Bounds are inclusive and ordered; an absent bound means unbounded within the -represented numeric domain. Integer comparisons MUST NOT convert to binary64. -NaN, infinities, overflow and reversed bounds reject. String length counts Unicode -scalar values, not UTF-8 bytes or grapheme clusters. Enum defaults match exact -literals. Empty enum definitions remain incomplete drafts with a warning.

-

DataValue is a tagged scalar: { Boolean true; }, { Integer 42; }, -{ Real 1.5; }, { String "value"; } or { Enumeration "Ready"; }. -There is no implicit numeric or string coercion. A type default is one compatible -scalar within its bounds. Record type defaults and record/object instance literals -are unsupported. A field default is absent (none) or a list of compatible -scalars with length inside its multiplicity. [] explicitly supplies an empty -collection and requires minimum 0, including Value Record fields; a nonempty -Record literal rejects. Reference fields MUST use none. A one-valued scalar -field uses a one-value list. A named type default supplies an individual element's -default, not a collection or an implicit instance of a mandatory field.

-

Numeric types may carry unit text. Empty field units inherit the named type unit; -an explicit different unit warns when that type has a unit. Units on nonnumeric -fields/types warn and remain preserved. Unit strings never convert values or -establish dimensional compatibility. Expressions are not executed.

-

Record, ExchangeItem and Interface each permit one same-family, same-layer -supertype. Specialization is acyclic and separate from containment. Effective -fields are base fields followed by local fields, preserving original field UUIDs -and owners. Local names MUST NOT shadow inherited names. Item specialization -preserves base nature and any specified mechanism; an Unspecified base mechanism -may be refined. Deleting a derived record never deletes its base. Mandatory -Value-field cycles warn record_no_finite_value; optional or Reference cycles -are valid without that warning. Traversal is cycle guarded and bounded; a payload -path budget failure is an explicit diagnostic, not silent truncation.

-

InterfaceItemAllocation is an ordinary identified relation from Interface to -ExchangeItem. It stores order and independent transmission/acquisition -protocols. Transmission is Unspecified, Synchronous, Asynchronous, Unicast, -Multicast or Broadcast; acquisition is Unspecified, Read or Accept. No protocol -combination is inferred from mechanism. Effective allocations are base-first, -then local ordered by (order, alias, UUID). A local item already allocated locally -or inherited is an error. Removing an allocation keeps its item and other -allocations; if live boundary compatibility would break, the transaction MUST -repair the affected contract explicitly or reject atomically. Artifacts can -reference the allocation identity independently of the global item.

-

An exchange carrying a specialized item satisfies a declaration of its base, -never the reverse. Standard provided/required compatibility uses explicit -interface ancestry. Identity/ancestry, not matching names, defines conformance. -The current typed function/component port contracts remain unchanged.

-

DataAssociation connects Record types without adding fields or transitively -carrying payload. Each named end has role, multiplicity and navigability. Source -multiplicity counts source instances per target instance and vice versa; it is -not a count of model elements. An end's navigable flag permits navigation toward -that end. Self-associations are valid; if both ends are navigable they require -distinct nonempty roles. Empty roles and duplicate role contracts warn, whereas -invalid ranges reject. Composition/shared aggregation, multiple inheritance, -redefinition, instance literals and arbitrary-precision decimal arithmetic are -outside this bounded contract.

-

This complete data contract example is also exercised through the REST API:

-
arcadia 7;
-project engineering_data "Engineering data" {
-    sa_data_type voltage "Voltage" {
-        definition { Real { minimum 0.0; maximum 48.0; }; };
-        default { Real 24.0; }; unit "V";
-    }
-    sa_data_type coordinate "Coordinate" {
-        definition { Record { supertype none; }; };
-        sa_data_field latitude "Latitude" { type_ref Real; }
-    }
-    sa_data_type located "Located asset" {
-        definition { Record { supertype @coordinate; }; };
-        sa_data_field supply "Supply" { type_ref { Named @voltage; }; }
-    }
-    sa_exchange_item navigate "Navigate" {
-        mechanism Operation;
-        sa_data_field origin "Origin" { type_ref { Named @coordinate; }; direction In; }
-        sa_data_field destination "Destination" { type_ref { Named @coordinate; }; direction In; }
-        sa_data_field accepted "Accepted" { type_ref Boolean; direction Return; }
-    }
-    sa_interface navigation "Navigation" {}
-    sa_interface monitoring "Monitoring" {}
-    interface_item_allocation nav_operation "Navigation operation" {
-        source @navigation; target @navigate; transmission Synchronous; acquisition Accept;
-    }
-    interface_item_allocation monitor_operation "Monitoring operation" {
-        source @monitoring; target @navigate; transmission Asynchronous; acquisition Read;
-    }
-    data_association location_link "Location relationship" {
-        source @coordinate; target @located;
-        source_role "location"; target_role "assets";
-        source_maximum 1;
-    }
-}
-
-
-
-

5. Diagrams, artifacts, and cross-layer traceability

-

diagram has name, identity, alias, description, layer (Oa by default), -projection (Overview by default), and routes (empty list by default). -The string projections are Overview, Decomposition, Allocation, -FunctionalDataflow, Capabilities, and Traceability. StateMachine is a payload -constructor with one required machine reference. Traceability diagrams -can show both layers; other diagrams show their chosen layer.

-

Allocation is an architecture view: it shows participants and work, nests -allocated work under its performer, and displays interactions, functional -exchanges and boundary exchanges. FunctionalDataflow shows the work hierarchy -and its exchanges. Capabilities shows participants, roles, missions and -capabilities with their involvement, exploitation, include, extend and -generalization links. Decomposition and Overview retain general containment; -Overview also exposes all relationships. These string projections hide -inapplicable placements without deleting them or changing semantic records.

-

A StateMachine projection MUST reference an existing StateMachine in the -diagram's layer. Its placements MUST refer to that machine, its Regions, or -its state vertices, including nested Regions and vertices. Events, other -machines and their members, and unrelated semantic elements are not valid -placements in this view. Empty and partial state views are valid; neither -creating nor reading a view synthesizes semantic elements or placements. -Changing an existing diagram to StateMachine, or changing its machine, MUST -remove or relocate incompatible placements in the same transaction. Context -errors use state_diagram_context; incompatible placements use -state_diagram_member. Neither case silently hides invalid members.

-

This complete source example places one state from an existing machine:

-
arcadia 7;
-project operating "Operating modes" {
-    oa_entity crew "Crew" {}
-    oa_state_machine modes "Modes" { parent @crew; }
-    oa_region main "Main region" { parent @modes; }
-    oa_state ready "Ready" { parent @main; }
-    diagram mode_view "Operating modes" {
-        layer Oa;
-        projection { StateMachine { machine @modes; }; };
-    }
-    node ready_view { diagram @mode_view; element @ready; }
-}
-

The JSON projection is -{"StateMachine":{"machine":"11111111-1111-4111-8111-111111111111"}}, -where the UUID is the referenced machine's identity. A bare "StateMachine", -a missing or null machine, an invalid UUID, or additional payload fields -is malformed. Existing projections retain their string representation, for -example "Overview" in JSON and projection Overview; in source.

-

Ports are boundary anchors of visible owners, with their existing -semantic identity. An explicitly placed port is not rendered as a separate -large node. Functional exchanges use their declared source/target ports; -boundary exchanges attach to their participant ports even if those ports have -no separate placement. Collapsing an owner hides its interior; projected links -may terminate at a visible ancestor without creating a new port or exchange.

-

node has an alias and identity but no display-name string. Required fields -are diagram and element references. Optional fields are position -(record with x/y, default 0/0), size (width/height, default 224/94), and -collapsed (false), and port_anchors (empty list). Node identity is -independent of its semantic element. -Coordinates are absolute diagram coordinates. Placement does not alter parent, -allocation, or realization. Hiding an element from a diagram must remain -distinguishable from deleting it from the model. -Changing a node's diagram preserves the placement identity, alias, coordinates, -size, collapse state, manual port anchors, and attached artifacts. The destination -must accept the element's layer, satisfy StateMachine membership when applicable, -and must not already contain another placement of that element.

-

Each port_anchors entry is a PortAnchor record with required port -reference, side (Left, Right, Top, or Bottom), and offset (finite -binary32 from 0 through 1). It has no separate identity or alias. The port MUST -be an existing Port directly owned by the placement's element. A port MAY -have one manual anchor per placement; different diagrams MAY position it -independently. List order is preserved but has no positional meaning.

-

offset measures a fraction of the complete projected owner edge, from top -on Left/Right or from left on Top/Bottom. Zero and one denote its corners. -The side is presentation data and MUST NOT change the port's contract, -orientation, ownership, allocations, or connected exchanges. Absent anchors use -automatic positioning. Empty lists are omitted in canonical JSON/source; -earlier schema-3 input without this field hydrates as [].

-
node actor_view {
-    diagram @architecture; element @vessel;
-    port_anchors [{ port @communication; side Top; offset 0.25; }];
-}
-

SetPortAnchor { diagram, node, anchor } upserts by port identity and retains -unrelated entries. RemovePortAnchor { diagram, node, port } restores -automatic positioning; it is a no-op only for an existing direct child port -of that placement. Unknown fields, duplicate port references, non-port or -foreign-owner references, and invalid offsets are structural errors. Each -anchor counts as one record against ParseLimits.max_records.

-

These commands use the same atomic history, conditional REST transactions, and -source writeback as other presentation edits. Source references resolve across -files, and anchor comments follow port identity. Reidentifying a port updates -anchor references; deleting it removes its anchors. Reparenting MUST repair -old-owner anchors in the same transaction, for example by removing them before -the Reparent command. Moving, resizing, arranging, or folding an owner retains -its authored sides and fractions. Frozen schema-1/schema-2 readers do not accept -port_anchors; their explicit migration supplies an empty list.

-

Each routes entry is an EdgeRoute record with a required edge reference -to an existing Relation or Trace, waypoints (default []), and label -(default none). Route records have no separate UUID or alias. An edge occurs -at most once in a diagram's route list; different diagrams MAY store independent -routes for the same edge. List order and explicit empty entries are retained by -source and JSON round trips, although list order has no routing meaning. Edges -need not currently be visible in their diagram.

-
routes [
-    { edge @report_exchange;
-      waypoints [{ x 420; y 260; }, { x 690; y 260; }];
-      label { fraction 0.55; offset { x 0; y -16; }; };
-    }
-];
-

Waypoints are pinned absolute diagram positions in source-to-target traversal -order; at most 128 are allowed per edge. Computed endpoints and automatic elbows -MUST NOT be persisted as authored waypoints. A label record requires both -fraction (finite binary32 from 0 through 1, measured along the computed -polyline's arc length) and offset (a relative x/y diagram displacement). -All position/displacement components obey the coordinate bounds in section 2. -An absent label uses automatic placement. Unknown fields, missing required -nested fields, duplicate route edges, non-edge references and invalid numbers -are structural errors. A pinned point that obstructs routing remains stored; -geometric routing difficulty is reported separately from structural validity. -Each EdgeRoute counts as one record against ParseLimits.max_records; pinned -points are bounded by the per-route limit and aggregate input byte limits.

-

SetEdgeRoute { diagram, route } upserts an entry by edge identity, retaining -the order and contents of unrelated entries. RemoveEdgeRoute { diagram, edge } -removes it; removing an absent entry is a no-op for an existing diagram and edge. -The latter is the Reset routing operation. Either command participates in the -same atomic transactions, validation and undo as other model edits. Deleting a -relation or trace removes its routes in every diagram; reidentifying it updates -route references without changing geometry. Folding, hiding, changing projection -or relocating placements MUST NOT erase routing metadata. Group movement -translates pins when both actual endpoint owners move by the same delta; label -offsets remain relative. Raw EditNode and source position edits are exact -record edits and do not implicitly translate pins.

-

This is an additive schema-3 field. Earlier schema-3 JSON/source that omits -routes loads as an empty list. Frozen schema-1 and schema-2 readers do not -accept this new field; migration creates empty route lists. Source projects keep -routes in their owning diagram declaration/file, resolve edge @alias across -files, and retain route-entry comments by referenced edge identity when entries -are reordered. The existing REST transaction endpoint accepts the same externally -tagged command records with UUID references and conditional ETags; no separate -routing endpoint or UI-only routing model exists.

-

artifact has name, identity, alias, description, required owner ref, -relation (References, Satisfies, Verifies, Implements, DerivedFrom; -default References), uri (default empty), revision (optional string), -and fragment (optional string). The two optional strings default to none. -URI must be nonempty, have no whitespace/backslash/control characters, and -use valid percent escapes. The owner is another existing model object. -Validation MUST NOT fetch the URI. Revision and fragment are retained verbatim.

-

trace has name, identity, alias, description, required oa and sa -endpoint references, kind (default Refinement), and baseline (default -none). Endpoints are canonical: SA realizes OA, regardless of which side -initiated creation. Trace does not change endpoint identity or containment. -Multiple distinct counterparts are permitted; duplicate endpoint/kind triples -are invalid.

- - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Trace kinds

Kind

OA → SA endpoint types

Refinement

Activity → Function

Participant

Entity/Actor → System/Actor

Capability

Operational Capability → System Capability

Mission

Operational Mission → System Mission

Exchange

Interaction → FunctionalExchange, or CommunicationMean → BoundaryExchange

Sequence

Process → FunctionalChain, or paired occurrence contributions directly owned by Process / FunctionalChain

Item

Same shared kind: ExchangeItem, DataType, DataField, Interface, or Port

Scenario

Same shared kind: Scenario, Lifeline, or Occurrence

State

Same shared kind: StateMachine, Region, any StateKind vertex, or Event

Manual

Engineer-defined relationship between OA/SA elements or relations

-

A nonempty baseline retains the required prose records oa { name "..."; description "..."; }; sa { name "..."; description "..."; }; and optionally -semantic (default none), defined below. The original prose records and typed -semantic facts are distinct review histories. Importing an older prose baseline -MUST NOT fabricate semantic acknowledgements. Neither history is a full model -snapshot, structural synchronization, or a lossless inverse transition. -Explicit counterpart creation, linking, comparison, and reviewed property -update may start in either layer. Ordinary edits MUST NOT automatically -overwrite the counterpart. Deletion MUST NOT silently cascade across layers.

-

Operational Capability normally creates a System Capability. Operational Mission -and System Mission are default counterparts in both directions and use Mission -traces. An explicit Operational Capability ↔ System Mission mapping remains -supported with a Manual trace; a uniquely linked such counterpart is reused -instead of silently creating a different mapping. Required/subtree process and -chain transitions include their contextual links, owned contributions and -referenced dependencies. New process/chain contribution mappings use Sequence -traces; older Scenario-kind occurrence mappings remain accepted. The current -prose baseline alone does not reconcile later topology changes.

-

In the prose-only workflow, name and description are compared independently -against each endpoint's last-reviewed baseline. An unchanged source MUST NOT overwrite an accepted -different target value. A source-only change proposes an update; different -changes on both sides require an explicit decision to take the source, keep -the target, or enter a merged value. An explicit decision records both current -values as reviewed, even when they differ. A target-only edit is preserved -without advancing that field's baseline, so it remains eligible to propagate -in the reverse direction. Equal current values can be recorded as converged.

-
-

5.1. Typed semantic review facts

-

baseline.semantic is either none or { facets [facet, ...]; }. -Each facet is a closed, self-tagged typed record, not a free-form path/value -entry. List position does not identify a facet. A scalar fact is exactly -{ Family { Leaf { oa observation; sa observation; }; }; }. Each observation -is either NotApplicable or { Value value; }, with the value type fixed -by Family/Leaf. Both sides are always canonical OA/SA, even during reverse review.

-

{ Value none; } means an applicable optional field was absent; -NotApplicable means the field's branch or record was not applicable. -An absent facet means Unreviewed. These meanings MUST NOT be conflated. Each -side is compared with its own last-reviewed value; deliberately unequal values -can be reviewed and unchanged. Reviewing one minimum does not acknowledge a -maximum, unit, default, header or referenced type definition.

-

Historical facts are independently acknowledged observations. They need not -compose one coherent former model: two bounds can have different review times. -They MAY outlive the endpoint's former record family, constructor and referenced -objects. Historical admission checks their closed payload types, finite values, -text, keys and uniqueness; it MUST NOT require a currently existing referenced -object or current family applicability. In contrast, a new graphical or planned -resolution MUST address the appropriate current record family and have at least -one currently applicable side. Both-side NotApplicable facts are invalid persisted -acknowledgements. A grouped constructor decision retires such obsolete branch facts.

-

The following table is the exhaustive scalar catalog. T? denotes an optional -T, T[] a list, and Id a historical UUID. Existing enum and typed payload -definitions in sections 3–4 apply. Every listed leaf wraps { oa ...; sa ...; }.

- - ---- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Closed scalar semantic facets

Family

Leaves and observation value types

Header

Name, Description: string; Parent: Id?

ExchangeItem

Nature: ExchangeItemKind; Mechanism: ExchangeMechanism; Supertype: Id?

Interface

Supertype: Id?

Data

Constructor: DataConstructor; IntegerMinimum, IntegerMaximum: i64?; RealMinimum, RealMaximum: finite f64?; StringMinimumLength: u32; StringMaximumLength: u32?; RecordSupertype: Id?; Default: DataValue?; Unit: string

Field

Type: TypeRef; Minimum: u32; Maximum: u32?; Unit: string; Direction: ParameterDirection; Storage: FieldStorage; Default: DataValue[]?

Port

Constructor: PortConstructor; ComponentKind: ComponentPortKind; Orientation: ComponentPortOrientation; LegacyDirection: PortDirection; LegacyInterface: Id?

Scenario

Viewpoint: ScenarioViewpoint; Context: Id?; Precondition, Postcondition: string

Lifeline

Represented: Id?

Occurrence

Constructor: OccurrenceConstructor; Precondition, Postcondition: string; Exchange, Sender, Receiver: Id?; MessageKind: MessageConstructor; TimerExpression: string; ReplyTo, Action, ActionLifeline: Id?; ControlKind: ControlKind; Sequence, Scenario, State, StateLifeline: Id?; FragmentOperator: FragmentOperator; BranchGuard: string

Event

Constructor: EventConstructor; TimeKind: TimeEventKind; TimeExpression, ChangeExpression: string

State

Kind: StateKind; Entry, Exit, DoActivity: string

Constraint

Expression: string

Relation

Source, Target: Id; AllocationOrder: u32; Transmission: TransmissionProtocol; Acquisition: AcquisitionProtocol; SourceRole, TargetRole: string; SourceMinimum, TargetMinimum: u32; SourceMaximum, TargetMaximum: u32?; SourceNavigable, TargetNavigable: bool; SourcePort, TargetPort: Id; Oriented: bool; ExtendCondition, SequenceGuard: string; TransitionKind: TransitionKind; Trigger, Guard, Effect: string; StateActionPhase: StateActionKind

-
-
-

5.2. Members, rows, order, and attached records

-

The remaining facet forms are closed records. keys means { oa key-or-none; sa key-or-none; }; at least one key MUST be present. review means the same -required OA/SA observation pair used above.

- - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Closed keyed and ordered semantic facets

Outer tag

Required fields

Key / reviewed value type

Member

collection; keys; review

Id / bool

Order

collection; review

No keys / Id[]

Literal

keys; review

Exact case-sensitive string / bool

Annotation

keys; review

{ path Id[]; field Id; } / MessageValue?

Binding

keys; review

Referenced lifeline Id / LifelineBinding?

Child

keys; review

Direct child Id / bool

Incident

keys; end; review

Relation Id, end Source or Target / bool

Artifact

keys; review

Artifact Id / nested artifact facet below

-

Member.collection is exactly Steps, FragmentBranches, FunctionInputItems, -FunctionOutputItems, SignalItems, ExchangeItems, TransitionTriggers, -TransitionEffects or Constrained. Order.collection is exactly Steps or -FragmentBranches. Order is independent of membership and MUST contain each -surviving member exactly once when applied. Membership Keep does not refresh -order history. An automatic insertion requires a unique unchanged mapped anchor; -ambiguous insertion or conflicting inferred orders requires an explicit final -target order. Independent removals can filter the order to surviving members.

-

Annotation identity is the complete (path, field) pair. Its MessageValue -payload contains path, field and expression; the payload key MUST -match its side's selector key. Binding payloads contain referenced and -local; referenced MUST match that side's key. Each row is one atomic -fact. Changing its key is an explicit old-row removal/new-row addition, not an -expression-only edit. Defaults containing repeated DataValues are atomic optional -vectors because those values have no stable member identity.

-

Artifact.review is { Leaf { oa observation; sa observation; }; } where Leaf -is Presence: bool; Name, Description or Uri: string; Owner: Id; -Relation: ArtifactRelation; Revision or Fragment: string?. Metadata decisions -are authorized on the artifact's attachment in the initial project. An explicit -Owner candidate may move a target-only link and record its resulting Owner and -other selected metadata. Presence remains relative to the trace owner. Subsequent -metadata decisions require its new owner trace; historical facts remain attached -to the trace that recorded them. No review fetches artifact content.

-

Duplicate scalar selectors and keyed selectors that occupy the same OA slot or -the same SA slot are invalid, even when the other side's key differs. Historical -order vectors MUST NOT repeat an Id. Review history has a limit of 16384 facets, -128 Ids in an annotation path, 1000000 bytes per string, and 1000000 traversal -steps per baseline. Exceeding these implementation work limits produces a -diagnostic; truncation MUST NOT be accepted as a complete review.

-
-
-

5.3. Constructor groups and decisions

-

Persisted constructors are discriminator words only:

-
    -
  • DataConstructor: Boolean, Integer, Real, String, Enumeration, Record.

  • -
  • PortConstructor: FunctionInput, FunctionOutput, Component, Legacy.

  • -
  • OccurrenceConstructor: Exchange, Action, Control, SequenceReference, -ScenarioUse, State, Fragment, Branch.

  • -
  • EventConstructor: Signal, Time, Change.

  • -
  • MessageConstructor: Asynchronous, Call, Reply, Create, Delete, Timer.

  • -
-

When a discriminator has no prior fact after explicit resets, both current sides -are applicable and equal, and the proposed target discriminator is unchanged, -KeepTarget, TakeSource or Candidate acknowledges only that discriminator. -It MUST NOT copy a constructor payload, acknowledge unselected descendants or -retire branch history. Descendant facets may be independently selected in the -same review; their decisions retain their own scopes and final-value audits. -Candidate records still MUST NOT change an unselected facet. Missing applicability -on either side does not qualify as an equal discriminator acknowledgement.

-

A grouped constructor resolution uses a transient typed target record, then -records the discriminator and each acknowledged descendant as separate facts. -It MUST NOT persist a second whole constructor payload overlapping those leaves. -Data includes its branch fields, literals and coupled default, but excludes Unit. -Port includes its contract, Legacy fields and item members. Event includes its -branch fields and Signal items. Occurrence includes its variant payload, members, -order, annotations and bindings, but excludes outer Precondition/Postcondition. -MessageKind includes its discriminator and dependent TimerExpression. State.Kind -is its own structural facet even though it changes the element's derived kind. -Other dependent changes caused by preparation remain explicit impacts.

-

A grouped constructor Keep requires an actual discriminator proposal or a -discriminator change since its prior review. Repeating Keep on an already reviewed -unequal pair MUST NOT acknowledge unrelated bound/default drift. A real target switch may -reconsider a previously kept difference. When the target already has the proposed -constructor, reconciliation MUST preserve target-only members rather than replace -its whole list. Removed branch facts participate in agreement as NotApplicable; -facts with neither side remaining applicable are retired.

-

Semantic review supports these explicit decisions in either direction:

-
    -
  • KeepTarget records the current source and retained target values for only the -selected facet, or the explicitly displayed constructor group.

  • -
  • TakeSource maps the selected source value through the frozen correspondence and -applies only that facet's delta. Unrelated target edits and target-only collection -members, rows, children and artifacts remain intact. Removing membership unlinks -the target member; it does not authorize deleting the target semantic object.

  • -
  • Candidate supplies final existing typed Element, Relation or Artifact records. -Only selected facets may differ from the result. Identity, alias and layer are -protected. New artifact creation requires a complete candidate and review of -every introduced metadata facet; removal also reviews the metadata retirement.

  • -
-

Headers have explicit semantic Name/Description facets; the original prose-only -workflow remains supported with its own baseline. Ordinary source editing can -explicitly edit a valid baseline; neither baseline format attests that a human -performed a particular review workflow. -Decision requests, final candidates and prepared impacts are transient editor/API -data. Source persists the resulting ordinary model and typed baseline facts; -it does not persist KeepTarget/TakeSource/Candidate as extra facet fields.

-
-
-

5.4. Correspondence, agreement, and atomic preparation

-

Correspondence is an explicit canonical OA-to-SA Id map, including historical -deleted IDs. Names and aliases MUST NOT guess correspondence. Reverse application -uses its inverse and rejects ambiguity. Multiple trace kinds for one endpoint -pair require an explicit trace selection. Changing a keyed pairing requires an -explicit reset naming the prior selector; old pairs are never silently replaced. -If a previously paired member or branch disappears on one side, retain its pair -with the absent/NotApplicable observation, suppressing a redundant one-sided -projection. A newly appearing pair still requires reset of old one-sided history.

-

All selected source proposals use one immutable initial project. Resets precede -catalog and constructor-scope derivation. Many sources may affect one target only -when their desired values agree on every actual canonical target slot, including -removals and members introduced by another proposal. Child membership and the -actual child's Parent share one slot; incidents share the relation's Source or -Target endpoint; artifact Presence and Owner share the actual artifact owner slot. -Conflicting desired values MUST reject independently of request order.

-

Materialize the agreed writes once, then audit each desired selected value. -Prepare one ordinary complete Command batch and compare its actual project with -the explicitly reviewed candidate. Additional cleanup, such as inherited message -annotation pruning, is reported as exact object before/after typed records -(or absence for creation/deletion). Every induced impact requires explicit review; -an accepted impact still MUST NOT contradict a selected Keep/Take. Repeat the -selected-value audit after preparation. Baseline updates belong to the same atomic -transaction and source-preserving writeback as model edits.

-

A plan's base fingerprint detects a stale model; its prepared fingerprint checks -candidate consistency. The plan is client-supplied data, not an authenticated -receipt or proof of its original decisions. Applying it validates the complete -ordinary candidate and preserves generated identities; it does not re-run its -request to generate new identities. Hosted clients additionally use the revision -preconditions defined in api.rst. Invalid/stale plans or failed source writeback -leave the accepted model, source, baseline, revision and history unchanged.

-

Own drift and linked impacts are separate, with incomplete review explicitly -visible. Absent facts MUST NOT produce a falsely complete Unchanged rollup. -Shared data/interface/event definitions are reviewed on their own traces; their -bounded derived impact walk follows reusable definitions and interface allocations, -not arbitrary architectural neighborhoods, and does not copy them into every -referrer's baseline. Its limits are 4096 objects per side and 32768 edges; exhaustion -is a diagnostic. Successful review remains one undoable transaction. Replanning -unchanged, already acknowledged leaf facts is a no-op only while their requests -remain applicable. A completed constructor switch or first acknowledgement of an -equal discriminator MUST be removed from subsequent requests; repeating it when -no switch remains is rejected. After an artifact moves to another owner, -metadata review through the old owner's trace is no longer -applicable and is rejected. An old plan becomes stale after an actual committed -change.

-
-
-

5.5. Source examples and historical token rules

-

This valid source records deliberately different constructor/unit observations. -Its historical integer minimum is absent; the Real side has no integer branch:

-
arcadia 7;
-project reviewed_dimensions "Reviewed dimensions" {
-    oa_data_type oa_dimension "Operational distance" {
-        definition { Integer { minimum 0; maximum 10; }; };
-        unit "m";
-    }
-    sa_data_type sa_dimension "System distance" {
-        definition { Real { minimum 0; maximum 20; }; };
-        unit "ft";
-    }
-    trace dimension_review "Dimension review" {
-        oa @oa_dimension;
-        sa @sa_dimension;
-        kind Item;
-        baseline {
-            oa { name "Approved OA name"; description ""; };
-            sa { name "Approved SA name"; description ""; };
-            semantic { facets [
-                { Data { Constructor { oa { Value Integer; }; sa { Value Real; }; }; }; },
-                { Data { IntegerMinimum { oa { Value none; }; sa NotApplicable; }; }; },
-                { Data { Unit { oa { Value "m"; }; sa { Value "ft"; }; }; }; }
-            ]; };
-        };
-    }
-}
-

Historical Id slots require uuid "..." even if the object still exists. -Only typed reference slots inside baseline.semantic accept that atom. -For example, the following is a valid historical facet value, including a deleted -named type; none and bare enum Boolean retain their different typed meanings:

-
{ Field { Type {
-    oa { Value { Named uuid "40000000-0000-4000-8000-000000000090"; }; };
-    sa { Value Boolean; };
-}; }; }
-

A qualified annotation uses the same historical UUID atoms in its key and payload. -This complete facet records one old row and an applicable absent target row:

-
{ Annotation {
-    keys {
-        oa {
-            path [uuid "40000000-0000-4000-8000-000000000091"];
-            field uuid "40000000-0000-4000-8000-000000000092";
-        };
-        sa none;
-    };
-    review {
-        oa { Value {
-            path [uuid "40000000-0000-4000-8000-000000000091"];
-            field uuid "40000000-0000-4000-8000-000000000092";
-            expression "latitude + 1";
-        }; };
-        sa { Value none; };
-    };
-}; }
-

Historical enums require bare words. Ordinary strings, including UUID-looking -units, literal keys and expressions, require quoted strings and remain text -during identity remapping. @alias is invalid anywhere inside a semantic -baseline. Live parents, trace endpoints and all other live references still -require @alias and MUST NOT accept the historical UUID atom.

-

The following substitutions in these examples are invalid:

-
    -
  • oa "NotApplicable"; or oa { Value "Integer"; }; in the Constructor -review: enum observations require bare words of the correct typed enum.

  • -
  • Named "40000000-0000-4000-8000-000000000090"; or Named @oa_dimension;: -a historical Id requires the explicit UUID atom.

  • -
  • Unit { oa { Value uuid "40000000-0000-4000-8000-000000000090"; }; ... }: -a text field cannot hold a reference atom.

  • -
  • Repeating the Unit facet, adding an unknown leaf, or supplying a numeric bound -as a quoted string: closed typed admission rejects the whole candidate.

  • -
  • Changing only the annotation payload's field UUID suffix from 092 to 093, -leaving the key at 092: the row's key and payload disagree.

  • -
  • Changing the document header to arcadia 4; while retaining semantic: -the frozen earlier schema does not admit a future baseline shape.

  • -
-
-
-
-

6. Text grammar and resolution

-

The input is UTF-8 without a byte-order mark. LF, CR, space, and tab separate -tokens. // starts a comment through the next LF or end of input. Quoted -strings use JSON string escaping, including \n, \r, \t, \\, -and Unicode escapes. Literal unescaped newlines inside strings are invalid. -Numbers use JSON decimal/exponent syntax; NaN, infinity, hexadecimal numbers, -leading plus signs, and leading zeroes are invalid.

-

A small model may begin with engineering declarations alone. This complete -source describes two participants, their work, responsibility and an exchange:

-
arcadia 7;
-project distress "Distress response" {
-    oa_actor vessel "Vessel in distress" {}
-    oa_entity rescue "Rescue coordination" {}
-    oa_activity report "Report distress" {}
-    oa_activity assess "Assess incident" {}
-    allocation vessel_reports "Crew reports" { source @vessel; target @report; }
-    allocation rescue_assesses "Team assesses" { source @rescue; target @assess; }
-    interaction distress_report "Distress report" { source @report; target @assess; }
-}
-

No diagram, placement, empty description or explicit root parent is required. -The accepted source adds identities; keep those clauses during later edits. -Changing "Assess incident" changes the display name while @assess still -resolves to the same activity. Behavior-coverage warnings permit this intermediate -model to be saved. Allocation remains explicit and distinct from decomposition.

-

Aliases match [A-Za-z_][A-Za-z0-9_]*. The following aliases are reserved: -arcadia, project, id, oa, sa, element, relation, diagram, node, artifact, -trace, body, alias, name, description, parent, from, to, entity, actor, -activity, capability, process, allocation, interaction, contribution, -realization, position, steps, true, false, null. Other field/constructor -spellings can be used in alias positions because references begin with @.

-

The grammar below uses * for repetition and ? for optional syntax. -element-kind and relation-kind are exactly the corresponding declaration -keywords listed above. Typed field -sets and enum constructors are closed by sections 3–5.

-
document    = "arcadia" "7" ";" project
-project     = "project" alias string identity? "{" project-entry* "}"
-project-entry = "description" string ";" | declaration
-declaration = element
-            | (relation-kind | "diagram" | "artifact" | "trace")
-              alias string identity? fields
-            | "node" alias identity? fields
-element     = element-kind alias string identity? element-body
-element-body = "{" (field-name value ";" | element)* "}"
-identity    = "id" uuid-string
-fields      = "{" (field-name value ";")* "}"
-value       = string | number | "true" | "false" | "none"
-            | enum-word | "@" alias | historical-id | list | fields
-historical-id = "uuid" uuid-string
-list        = "[" (value ("," value)*)? "]"
-

The project description defaults to empty and may appear once. There is no -semicolon after a declaration or project closing brace. Every field inside a -record ends with a semicolon, including fields whose values are nested records. -Trailing list commas are invalid. Fields may appear in any order.

-

UUID strings use 8-4-4-4-12 hexadecimal spelling, with uppercase or lowercase -hexadecimal accepted. Canonical output uses lowercase. Omitting an identity -creates a fresh UUID for that parse. After Apply, accepted source includes the -generated identity. Authors must keep identity clauses to preserve existing -objects across subsequent edits.

-

All declaration aliases are collected before references resolve, so forward -references are legal, including references to nodes and diagrams. Unknown -@alias values are errors at their reference occurrence. Quoted UUIDs and -bare words cannot substitute for reference-valued fields. Alias resolution -produces UUID references; the alias itself remains stored on its declaration. -Changing an alias in source requires updating its uses. Display names never -resolve references.

-

historical-id is context-restricted to the typed semantic baseline reference -slots in section 5.5; it is not an alternative spelling of a live reference.

-

Unknown fields, duplicate fields, incompatible constructor payloads, unsupported -enum spellings, duplicate aliases/identities, and structural validation errors -reject the entire candidate. No declaration silently replaces another. -Ordinary string fields require quoted strings and cannot contain an unquoted -@alias reference. Enum fields require unquoted named values or their typed -constructor records. Typed values MUST NOT nest more than 64 levels; malformed -deeply nested drafts produce a diagnostic instead of exhausting the parser stack.

-
-

6.1. Modular source projects

-

The pure source_project core API compiles a virtual source bundle, containing -entry and a files map from logical paths to UTF-8 strings. Its entry is a -root-level .arcadia file with the source7 project header above. A project entry -MAY also be include string ";". An included fragment consists only of ordinary -declarations and include statements; it MUST NOT contain another version header, -project wrapper, or project description. Includes MUST occur at document level, -never inside an element body or typed field value. For example:

-
arcadia 7;
-project rescue "Rescue" id "00000000-0000-4000-8000-000000000001" {
-    description "Operational model";
-    include "oa/participants.arcadia";
-    include "oa/activities/activity_respond.arcadia";
-    include "views/diagram_operational.arcadia";
-}
-

An include path is relative to its containing file. . and .. segments -normalize within the virtual project root; escaping that root is an error. Bundle -keys MUST already be normalized, use forward slashes and the lowercase -.arcadia suffix, and contain no empty path segments. Absolute, drive, UNC, -URL, backslash, control-character and non-portable Windows device paths are -rejected. Paths ending a segment in dot or space are rejected. Keys that collide -after Unicode lowercasing are rejected. References use the exact normalized -spelling; filesystems do not participate in resolution.

-

Every reachable file is compiled once, including through repeated or diamond -includes. Include cycles are errors with their chain and including location. -Missing files, duplicate JSON file keys and supplied unreachable files are -errors. The compiler MUST NOT read local files, network resources, environment -variables or the process working directory. The native loader is responsible -for translating a directory into this explicit bundle and enforcing physical -path ownership; the pure compiler grants no filesystem access.

-

inspect_includes(path, source, is_entry, limits) supports native discovery -before all included documents are available. It uses the same strict entry or -fragment syntax parser, applies document byte/record and local include-edge -budgets, and returns normalized targets with exact directive spans. Comments and -strings that mention include syntax are not directives. A syntax failure anywhere -in the document returns located errors and no partial include list; it does not -scan arbitrary token strings after failure. Global references and engineering -validation are deferred to bundle compilation. A native loader MUST retain the -unparsed draft on failure and enforce aggregate bytes/files/depth/edges while -loading; this inspection call does not own or inspect a directory.

-

All files share the existing global alias/UUID namespace. Files add no semantic -namespace, implicit parent, allocation or port contract. Forward references work -across files. Duplicate aliases and identities report the offending declaration -and the previous declaration location, including collisions with the project. -Moving a declaration between files MUST preserve the resulting model and UUIDs.

-

ProjectLimits adds file, include-depth and include-edge budgets to the existing -language::ParseLimits. Model bytes, graph objects, records and ordered -references are counted across the complete bundle, never reset per file. The -default candidate policy is 256 files, depth 32 (entry depth zero), 1,024 include -edges and 64 MiB combined UTF-8 source. Other model counts remain caller policy. -Repeated include edges count toward the edge budget; file contents count once. -The compiler admits input and stabilized output against the same limits.

-

compile_bundle returns either located errors and no candidate, or a -CompiledSourceProject with the validated DecodeResult, stabilized bundle, -derived SourceIndex and located engineering warnings. It inserts explicit -UUID clauses into headers that omitted them without otherwise reformatting the -input. The returned source index and diagnostics address the returned stabilized -bytes. Clients MUST retain that bundle after successful Apply; recompiling the -original buffers with missing identities would generate fresh identities.

-

Bundle diagnostics wrap the existing Diagnostic with a logical path and optional -related locations. Declaration spans, own-field value spans, lexical owners, -declaration bodies, project header/body/description spans and resolved includes are available in -the source index. Columns remain one-based Unicode scalars within the real file; -spans never refer to concatenated or synthetic source.

-

Single-string parse/lint APIs remain filesystem-free and reject include -statements with an explicit source-bundle requirement. V1/V2 single-file imports -continue through explicit migration. Modular projects accept source3 through -source7. Older bundles are upgraded as one project, preserving file paths, includes, nested -ownership and comments through targeted token edits. Generated allocation -declarations stay in their interface's existing file. Individual include fragments -are not independently migrated. Missing UUIDs stabilize before migration so new -allocation identities are deterministic on subsequent compilation. -An authored source4 bundle upgrades through the strict prose-only baseline -adapter; after any missing UUID stabilization, the version token alone changes -to 7. Existing prose, comments, includes and nested ownership remain intact. -Source5 semantic facet comments follow their closed selector identity through -row reordering and reidentification; UUID-looking text does not act as an Id.

-
-
-

6.2. Nested element ownership

-

An element body MAY contain element declarations interleaved with its ordinary -fields. Its immediate enclosing element is its lexical owner. For example:

-
sa_actor harbor "Harbor operator" {
-    sa_port radio "Radio" {
-        contract { Component { kind Standard; orientation Unset; }; };
-    }
-}
-

Nesting expresses semantic Element.parent only. It does not allocate a -function to an actor or System, deploy equipment, or add or reorder occurrence -references in an owner's explicit steps list. Those relationships retain -their explicit records and fields. The -same parent-kind, layer and acyclicity constraints apply as for flat source. -Only element declarations can nest: relations, diagrams, placements, artifacts, -traces and include directives remain at document level. A typed record value -contains fields, never element declarations.

-

If a nested declaration omits parent, it inherits its lexical owner's UUID. -An explicit parent MUST reference that owner; another reference or none -is an error located at the parent value. At document level, omitted parent -means none and explicit parent references retain their existing meaning. -Nesting creates no alias namespace. Aliases and UUIDs remain globally unique, -and references can target nested declarations before or after their definition -or from another included file. Identity stabilization inserts missing UUIDs in -nested headers just as it does in document-level headers.

-

Source positions distinguish each declaration's own fields from its descendants' -fields. A declaration's body span encloses its lexical children as well as its -own fields. An inherited parent has no synthetic source token or field span. -Located diagnostics and editor navigation MUST address real source bytes.

-
-
-
-

7. Canonical output and source tooling

-

For every structurally valid Project P, parsing its canonical output MUST -reproduce P, and canonicalizing that result MUST be idempotent:

-
parse(format(P)) == P
-format(parse(format(P))) == format(P)
-

Equality includes all persistent fields, stored aliases, body variants, -references, placements, artifacts, trace baselines, and ordered occurrences. -Map order is irrelevant. The writer preserves signed coordinate zero and -emits decimals that recover the original binary32 value.

-

Canonical output uses LF, four-space indentation, explicit identities, -defaults, and JSON-escaped strings. Elements are emitted in UUID order, then -relations; diagrams and their nodes follow, then artifacts, then traces. -Fields use deterministic name order. Output ends with LF. Alias values come -from the model, never from regenerating display names.

-

Comments and input whitespace are lexical trivia to the canonical writer. -Canonical export does not preserve them. The source-project editor and compiler -preserve authored comments and whitespace on Apply while inserting missing -identities. Explicit canonical export remains a separate operation. Project -update/export distinctions are described below. -Camera position, selection, source cursor, and undo history are not stored -model fields. Layout, collapse state, and manual port anchors inside -NodePlacement are model data.

-

The pure format_bundle exporter uses the same typed declaration writer as -format. It creates a small entry file, layer/category modules, separate -activity/function roots and behavior contexts, and separate diagrams with their -placements. Ports follow their owner; relations follow their source/context. -Module names derive from persistent aliases, not display names or UUID chunks. -Declarations are ordered by semantic ancestry and alias, with owners before -descendants. Case-colliding generated file names receive deterministic numeric -suffixes. When more than 240 distinct modules would be produced, aggregate -folders consolidate into category modules to avoid thousands of tiny files. -For every valid P, compile_bundle(format_bundle(P)).decoded.project == P -when the caller supplies sufficient admission budgets.

-

update_bundle(compiled, P, limits) is a core source update facility. It -checks that the compiled document/index/model snapshot still agrees, preserves -untouched document bytes, and recompiles the result before returning it. Flat -declarations retain their document-level position and file when reparented. -Nested declarations move into their new semantic owner's body, across files if -necessary; becoming a root moves them to document level. New children prefer -their owner's body. New roots use canonical module rules and add an explicit -entry include. -Removed declarations do not delete source files. Alias changes also update -declarations containing references to that alias. Invalid or stale updates -return errors without mutating the input workspace.

-

A moved declaration carries its contiguous leading comment block and comments -following its closing brace on the same line. A blank line separates a comment -block from the following declaration; such gap comments stay in the enclosing -scope. Comments trailing a preceding field likewise stay with that field. -Deletion retains annotations in the nearest surviving original scope and -separates them from adjacent declarations with blank lines. Surviving descendants -move with an enclosing declaration unless their own semantic parent changes; -independent parent and child moves are resolved together. Leading whitespace -adjusts to the destination scope; other token spelling remains authored.

-

Updates MUST NOT canonicalize surviving declarations in full. Header, field and -nested value positions are derived from the existing syntax parser. Unchanged -fields retain their exact bytes, including field order, omitted defaults, number -and string spelling, comments, indentation and line endings. Changed literals and -references replace only the required tokens; a reference with comments between -@ and its alias retains those comments. A previously omitted default is -inserted only if its semantic value changes. New fields use the existing record's -indentation and line-ending style without reformatting its other fields.

-

A constructor tag change retains shared nested fields in place. Removed fields, -constructors and declarations remove syntax tokens, retaining their comments -and whitespace as annotations in the enclosing record or file. A complete value -shape replacement likewise retains the old value's comments beside the new value. -The updater does not invent an engineering interpretation for orphaned comments.

-

List entries use stable referenced UUIDs, message-value field references or -lifeline-binding referenced identities when these keys are unique. Literal -entries use their literal value. Retained entries carry their authored value -text and inter-separator comments when reordered. Trivia after a preceding comma -belongs to the following entry; trivia before the next comma belongs to the -preceding entry. Separators are reconstructed before trailing line comments so -that the result remains valid syntax. Unkeyed or duplicate-key lists use source -positions. Removed-entry comments remain as annotations in the list; newly added -entries receive formatted syntax. These rules preserve comments without silently -attaching new semantic identity to an ambiguous list entry.

-

Retained declaration UUIDs remain unchanged unless explicitly edited. Source -ownership remains unchanged except for the nested relocation rules above. -Records match by UUID, not display name or alias. The model-only update_bundle treats an -explicit UUID replacement as removal and creation. The desktop instead uses -update_bundle_with_identity_map with the surviving-record provenance from -prepare_command: explicit identity edits and sequential identity swaps MUST -preserve each original declaration's owning file and attached source trivia, -unless the same edit changes its nested semantic parent and requires relocation. -New records have no original identity mapping. The mapping MUST be injective, -refer to existing records and preserve record classes. The -result is recompiled, reindexed and checked for equality with the requested -model before return. Equal model values do not imply a no-op if command -provenance removes or replaces a declaration; the resulting model and source -bundle together determine whether the accepted state changed. -Source changes needed for undo are workspace history data; -callers should retain complete compiled workspace snapshots rather than infer -past omission/layout choices from a model-only undo.

-

Canonical modular export is a distinct, explicit operation and does not promise -original comment/layout preservation. The native editor integrates per-file -drafts and source-project saves as specified below. The pure compiler performs -no filesystem access. REST source-project endpoints exchange the accepted -bundle with all its files. One revision and ETag protect the model and source -together, including source-only comment edits; stored envelopes contain both. -Canonical source endpoints remain explicit model interchange. The precise -transport, admission and persistence contract is in api.rst.

-

parse returns a complete structurally validated candidate or error -diagnostics and no model. Engineering warnings alone MUST NOT reject a valid -candidate. Parsing may stop at the first syntax/conversion error. Diagnostics -contain severity (Error or Warning), a code identifying the diagnostic category, -optional object UUID and field path, a message, one-based line and Unicode-scalar -column, and a half-open UTF-8 byte Span. Tabs count as one scalar; LF advances -the line. Structural errors and engineering warnings point to a relevant -declaration or field when available. Clients MUST use severity/code rather than -parse the human message; diagnostic wording is not a stable machine API.

-

Explicit behavior coverage diagnostics

-

The following shared engineering checks are advisory Warning diagnostics. -They MUST NOT reject graphical Apply, source compilation, Save, or an API -transaction solely because the model is incomplete. They run only after the -structural pass reports no Error, avoiding cascades from invalid ownership, -references, layers, or recursion. Their policy concerns explicit evidence in -the authored model, not proof that no engineering behavior exists.

-
    -
  • non_leaf_work_exchange locates each source or target of an -Interaction or FunctionalExchange that has direct same-kind child work. -Repair the exchange to name its responsible leaf Activity/Function, keeping -SA function-port ownership consistent.

  • -
  • work_without_behavior_context locates a leaf Activity/Function lacking -an included Action or an included Interaction/FunctionalExchange occurrence -that explicitly names it as an endpoint.

  • -
  • exchange_without_behavior_context locates an Interaction or -FunctionalExchange lacking an included Exchange occurrence. It is suppressed -while a more specific non-leaf endpoint warning applies to that exchange.

  • -
  • mission_without_capability locates an OperationalMission/Mission lacking -a valid same-layer outgoing MissionCapability. Participant responsibility -through MissionInvolvement does not satisfy this check.

  • -
  • capability_without_behavior_context locates a supported leaf -Capability/SystemCapability lacking either an incoming ChainInvolvement or -a same-layer Scenario explicitly naming it as context. It MUST NOT -duplicate unsupported_capability on the same record.

  • -
-

Record-level checks have no invented field path. Coverage traverses all Process, -FunctionalChain and Scenario roots, following only their explicitly listed, -correctly owned occurrences and Fragment branches. Reusable sequence/scenario -contents are traversed with identity-based cycle guards. Known work/exchange -references count despite unfinished lifelines; unordered occurrences do not. -Repeated exchanges and correlated replies contribute the same identity once. -Parent work does not cover its children. Bare lifelines, state observations, -StateAction/Availability links, timer-only occurrences, and participant/boundary -messages do not imply work/exchange coverage. Allocation does not imply that -every exchange carried by a boundary message occurs. Diagram layout is irrelevant.

-

As an explicit Studio completeness policy, same-kind capability grouping parents -are excluded from both capability support warnings; their leaf capabilities are -checked independently. A valid direct Scenario context also satisfies existing -unsupported_capability support. An empty linked process/chain or scenario -satisfies explicit context presence while retaining its own empty_sequence -warning. Include/extend/generalization, parent containment, and shared performers -MUST NOT manufacture a context. These advisory checks follow the distinction -between dataflow and its usage contexts described in the official -Arcadia Questions and Answers; -they are not a declaration of full Capella conformance.

-

parse_limited and decode_limited accept caller admission budgets for -input bytes, graph objects, total persistent records and ordered step references. -Counts MUST be checked before expensive structural validation; migration budgets -include generated records before migration. Exceeding a budget does not mutate -the model. Admission limits are host capacity policy, not hidden restrictions -on otherwise valid language semantics; ordinary parse has no such model -size policy. The REST service's fixed policy is specified in api.rst.

-

tokenize is tolerant: it covers every input byte with nonoverlapping -TokenSpans, including incomplete strings and invalid characters. Token classes -are Keyword, Identifier, String, Number, Comment, Punctuation, Whitespace, -and Error. @ is punctuation followed by the referenced alias token. -lint returns syntax, structural and engineering warning diagnostics for the -current draft without adopting a model. A valid unallocated SA function, for -example, can report missing System and allocation warnings before Apply. -analyze -combines tokens, diagnostics, and declaration spans with alias, optional -explicit UUID, and declaration kind. A malformed later declaration does not -erase earlier declaration spans. None of these APIs mutates a session.

-
-
-

8. Graphical parity and transactional editing

-

The graphical editor MUST expose all persistent properties through typed -controls, including optional references, lists, variant constructors, aliases, -IDs, nested parent changes, diagram placements, artifact metadata, and trace -baselines. Inspector controls may provide operations unavailable as a canvas -gesture. Exact strings, including tabs and line breaks in names, must remain -editable through an appropriate escaped-string input when ordinary widgets -cannot represent them.

-

A move into a functional parent, an allocation onto a performer, and a diagram -placement move are different transactions. The UI MUST identify which operation -will occur. Reparenting work must not silently change its performer. A computed -parent summary must remain distinguishable from direct allocation. A collapse -operation hides presentation detail without deleting the children.

-

Applying a reachable source bundle is one model transaction. Graphical edits -update its owning declarations through the source-preserving updater. Both -paths validate their candidate and model/source equality before -adoption; failure preserves the committed model, save baseline, and history. -Incomplete but structurally safe references and coverage gaps produce warnings -that allow continued authoring and saving.

-

Source and property drafts contribute to unsaved-work state. Invalid drafts -MUST NOT be replaced by generated text or lost on ordinary navigation. Undoing -source-buffer edits and undoing committed model changes are distinct operations. -Successful source Apply creates one model-history entry. Undo/redo restores -the full stored model and exact accepted source bundle, including aliases, -trace links, occurrence identity, comments and file ownership. Each open file -retains its own unapplied draft across file navigation. Located lint diagnostics -refer to the raw draft bytes rather than an identity-stabilized intermediate. -Property editing MUST NOT silently discard a pending source draft.

-

In Textual model, place the caret on a declaration alias or an @reference -and choose Definition (F12) or References (Shift+F12). -Back (Alt+Left) returns to the previous source location in this -workspace. These shortcuts act while the source editor has focus; the Text -toolbar also exposes the actions. If the previous draft changed, Back restores -its current cursor rather than reusing an obsolete selection.

-

Symbol navigation reads bounded snapshots of the currently loaded source -drafts. It does not apply drafts or load missing included files. The results -identify partial scope, draft evidence, detached or unverified files, alias -ambiguity and authored-ID collisions. Draft, detached and ambiguous definition -candidates require an explicit row choice. References are lexical uses of an -alias, not a list of resolved typed relationships; comments and strings are -excluded. Declaration and use counts include any rows omitted by the result -limits. If sources change, stale rows cannot navigate: run the query again. -Navigation MUST preserve the accepted model and every source draft, and select -locations in the original UTF-8 bytes. Displayed locations use one-based lines -and UTF-8 byte columns.

-

Changing a graphical UUID must atomically update map keys and every reference. -Alias changes preserve UUID identity. Deletion must account for owned elements, -incident relationships, ordered occurrences, and diagram placements without -leaving dangling references. Deleting a sequence does not delete the semantic -exchanges it references. Cross-layer counterparts are not owned descendants. -Text edits describe a complete candidate and must remove or repair dependencies -explicitly before Apply succeeds.

-
-
-

9. Persistence and explicit migration

-

Loading detects JSON from its first non-whitespace {; other input is parsed -as Arcadia source regardless of filename. Saving an .arcadia suffix uses -the accepted V7 source bundle (arcadia 7); other suffixes use V7 JSON. Both forms preserve the same -complete model. JSON rejects unknown fields throughout typed payloads, -duplicate maps, unsupported versions, and structural errors.

-

The explicit schema-4 reader retains the exact old Project/Trace/TraceBaseline -envelope with closed fields. It reuses unchanged typed elements, relations, -diagrams and artifacts. Version4 prose baselines migrate with semantic none; -their names/descriptions remain exact even when live endpoint values differ. -The adapter MUST NOT infer semantic review from live values, and version3/4 -inputs MUST reject semantic baseline fields. The migration report retains the -original from_schema and current to_schema 7. Reopening a current7 save produces -no migration report. The real V4 reviewed-prose fixtures remain compatibility input.

-

The schema-3 reader freezes the changed data payloads and old constructor catalog; -schema-4 fields/constructors MUST NOT be silently accepted as schema 3. Existing -typed PortContract records remain unchanged. Information/Material/Energy retain -their nature with Unspecified mechanism; old Event becomes Information with Event -mechanism. Old bounds/defaults/supertypes are absent, String length is 0..unbounded, -and fields retain type/multiplicity/unit with Unspecified direction, Value storage -and no explicit default. No physical mechanism or direction is guessed.

-

Each old Interface.items entry generates one InterfaceItemAllocation with its -original zero-based order and Unspecified protocols. UUID v5 uses the project UUID -namespace and name v2-interface-item:<interface-uuid>:<item-uuid> consistently -for schema-2 and schema-3 inputs. Aliases are deterministic and collision-free; -collision with any existing identity is an error rather than an overwrite. Existing -interface/item identities remain intact. Old nested message values acquire a path -only when exactly one valid path exists. Ambiguous paths, including recursive -reuse, retain the old annotation with a located warning rather than guessing.

-

Migration reports additionally contain semantic_defaults (object, field, -description), generated_interface_allocations (id, source, target, alias, -order) and unresolved_message_values (occurrence, field, reason). These new -report arrays default empty when reading older responses. Version 1 normalizes -through V2, then the typed-port V3 adapter, shared V4 data upgrade and V5 review -adapter. Original V2 and real V3 fixtures remain unchanged compatibility inputs. Current exports -always use version 7; reading/migrating alone never saves or changes revisions.

-

Versions 1–6 MUST reject ContextualLink, operation fields (including explicit -null/default values) and their new typed semantic facets. Current semantic review -adds Occurrence.Operation and Relation.ContextOwner/ContextSource/ContextTarget/ -ContextPurpose. Historical endpoints and operation selections remain typed -historical UUIDs, never live graph references or inferred current facts. -Version 5/6 migration preserves old Call/Reply/values, direct SequenceLinks, -traces, and version-appropriate view selectors without guessing operation intent -or contextual links. An authored source upgrade changes only the version token -until an actual model edit requires further writeback.

-

Desktop recovery MUST verify old 5/6 checkpoint fingerprints with the exact -historical serializer and closed old-version admission before accepting the -migrated model. It retains invalid raw buffers and authored review decisions; -transient impact approvals are reset and plans are recomputed against the -accepted base. A saved unchecked executable plan/command MUST NOT be restored.

-

Native source Save All checks the original contents of observed dependencies -before staging writes. Existing target files are replaced individually and the -entry is installed last. A transaction journal records originals and intended -writes; explicit recovery restores originals only when current bytes match a -known transaction state. Unrelated external contents MUST NOT be overwritten -by recovery. Missing entry files and lint updates MUST NOT hide a detected -pending-recovery action. This is not cross-process compare-and-swap or atomic -multi-file visibility. Save failures retain the accepted model and drafts. -Detached dirty buffers MUST be reattached or reverted before source saving; -JSON export does not replace the source project's saved baseline or location.

-

The frozen schema-2 reader retains staged compatibility defaults. Old Scenario bodies without a -viewpoint retain MixedLegacy, and old Exchange occurrence bodies without -message_kind, reply_to or values retain Asynchronous, none and []. -Old StateTransition bodies without transition_kind, triggers or -effects retain External, [] and []; existing trigger, guard and -effect strings are preserved exactly. Source declaration defaults and JSON -compatibility defaults MUST agree; no event or work reference is inferred from -legacy prose. -This compatibility applies equally to source and JSON. It adds explicit -defaults without changing any prior identity, name, reference, order, geometry, -artifact or trace field. The original V2 golden source/JSON in -crates/oa-core/tests/fixtures MUST NOT be regenerated from the newer demo; -they verify that every old persistent field survives. Canonical output writes -the added defaults through explicit migration to the current schema 7.

-

The V2 adapter preserves every original identity, alias, prose, parent, -placement, trace and artifact. Generic ports become Legacy with their original -direction/interface record. Existing FE endpoint IDs remain unchanged. For -only a missing endpoint it creates an item-bearing FunctionOutput/Input owned -by the already specified function, using UUID v5 in the project namespace -with v2-fe-port:<fe-id>:source or v2-fe-port:<fe-id>:target. Every -identity category is checked for collision; collisions are located errors, -never overwrites. New aliases follow the deterministic collision-free alias -convention. Every old boundary exchange receives oriented true; no -PortAllocation is inferred.

-

decode_with_report, load_with_report and their limited/source variants -return the model plus a migration report containing original unresolved port -IDs, required resolutions, generated endpoint IDs/aliases/owners, and boundary -orientation defaults. REST import, replacement, candidate validation and -project snapshots surface the same report. Opening an old server envelope -preserves its resource ID/revision and bytes; the next explicit edit writes -schema 7. Model/source exports contain only the canonical model. Migration -reports are transient response metadata, not persisted model facts.

-

Admission checks account for generated objects and their references before -construction. Both the old V2 contract and the resulting current graph are -validated. Migration itself does not save or increase a stored revision. -Canonical second saves are stable. V1 normalization produces the frozen V2 -model and then uses the same explicit adapters to reach schema 7.

-

Save validates a snapshot, writes a sibling temporary file, flushes it, and -atomically replaces the destination. Failed serialization or validation must -not truncate a previous file. External concurrent-writer conflict resolution -and crash recovery are outside this storage contract.

-

Version 1 text and JSON are accepted through an explicit adapter. Migration -preserves project, element, and relation UUIDs; names and descriptions; relation -endpoints; all coordinates; and exact process exchange order and repetition. -Allocation and Interaction remain their corresponding V2 relations. -Contribution becomes CapabilityInvolvement; the old Process-to-Capability -Realization becomes ChainInvolvement, retaining its ID and name.

-

Legacy positions become nodes in one OA Overview diagram. Each old process -step becomes a distinct occurrence element owned by its process. Generated -diagram, node, and occurrence identities use UUID v5 with the old Project ID -as namespace and deterministic role/index names. If such an identity collides, -a deterministic suffix is tried. Reimporting the same fully identified V1 -input therefore yields the same V5 result, including repeated occurrences. -No System or SA content is invented during an OA-only migration.

-

V1 source aliases are retained when valid in V2. JSON imports generate the old -canonical aliases deterministically. Any alias newly reserved in V2 is changed -by a deterministic suffix; aliases were not persistent V1 semantic data. -A project alias and aliases for new records are generated without colliding -with retained aliases. The existing bundled V1 text and JSON migrate to equal -V5 projects. Future versions remain errors until a separate migration exists.

-
-
-

10. Complete authoring example

-

This document intentionally omits IDs for new authoring. Apply and Save write -the assigned UUIDs explicitly.

-
arcadia 7;
-project harbor "Harbor assistance" {
-    oa_entity control "Harbor control" {}
-    oa_activity assess "Assess assistance" {}
-    allocation performs "Performs" {
-        source @control;
-        target @assess;
-    }
-    sa_system service "Harbor assistance system" {}
-    sa_function evaluate "Evaluate assistance request" {}
-    allocation system_performs "Performs" {
-        source @service;
-        target @evaluate;
-    }
-    trace assessment_trace "Operational realization" {
-        oa @assess;
-        sa @evaluate;
-        kind Refinement;
-    }
-    diagram operational_view "Operational allocation" {
-        layer Oa;
-        projection Allocation;
-    }
-    node assessment_card {
-        diagram @operational_view;
-        element @assess;
-        position { x 360; y 70; };
-    }
-}
-
-
-

11. Verification and primary references

-

cargo test -p oa-core --test language --locked verifies typed round-trips, -stable aliases, strict syntax/reference failures, Unicode source spans, -deterministic V1 migration, occurrence identity, coordinate bits, persistence, -and model-history integration. Core validation and session tests cover domain -mutation rules; native UI and API verification are separate evidence.

-

cargo test -p oa-core --test r3_semantic --test r3_source --test r3_migration --locked -checks the closed facet catalog, partial decisions, constructor scopes, canonical -agreement, target-only preservation, historic references, exact prepared impacts, -source/JSON parity, comment ownership and explicit original1–4 migration. The -source suite includes actual included-file save/reopen and prepared review -writeback. These checks establish core/source behavior; they do not establish -graphical or hosted API acceptance by themselves.

-

The research record in docs/research-v2-semantics.md distinguishes method -requirements from Studio design choices and links to original UI evidence. -The following sources inform this language without extending its scope:

- -
-
- - + + + + + + +Arcadia Studio OA / SA Language and Editing Contract + + + + +
+

Arcadia Studio OA / SA Language and Editing Contract

+
+
Version:
+
7 (integration in progress)
+
Status:
+
Source-7 contract under integration; native and API acceptance pending
+
Core implementation:
+

crates/oa-core

+
+
Text suffix:
+

.arcadia

+
+
+
+

1. Scope and conformance

+

This specification defines the shared Operational Analysis (OA) and System +Analysis (SA) model, its textual language, graphical editing obligations, +validation, persistence, and explicit migration from versions 1, 2, 3, 4, 5 and 6. MUST, MUST NOT, +and MAY indicate requirements, prohibitions, and permitted choices.

+

Schema 7 adds caller-qualified process/chain links and explicit interface-operation +selection on boundary scenario invocations. Schema 6 added OA communication means, +exchange categories and saved view options. Integration and acceptance are tracked +separately; this version label does not claim completed graphical/API parity. +Schema 5 added closed typed semantic review facts to trace baselines, including +historical references that may no longer exist in the live model. It retains +schema 4 bounded reusable data types, typed defaults, exchange mechanisms, +qualified values, specialization, interface allocations and associations, and +schema 3 typed ports and state views. Explicit older readers preserve meanings +and identities without inventing previously reviewed semantic values. +This contract does not by itself establish native acceptance or release completion.

+

OA describes stakeholders, operational work and needs. SA describes the +selected system boundary, external actors, functions, services and expected +behavior. SA permits at most one System of Interest; its absence is an +incomplete-model warning when other SA elements exist. The SA System MUST NOT +contain internal components. Functional decomposition is independent of that +structural boundary. Logical Architecture, Physical Architecture, deployment, +Capella file interchange, and executable behavioral simulation are outside +this specification. This independent language does not claim complete Arcadia, +Eclipse Capella, or SysML conformance.

+

An implementation conforms only when each supported field and relationship is +reachable in both graphical and textual editing, preserves identity through +save/open and undo, and uses the same structural validation. Merely displaying +a name or storing an opaque payload does not establish graphical support. +Actual verification evidence is separate from this contract.

+
+
+

2. One authoritative model

+

A Project stores schema_version (7), id, alias, name, +description, and maps named elements, relations, diagrams, +artifact_links, and traces. Each map is keyed by the UUID contained in +its record. Duplicate map keys and mismatching keys/identities are errors.

+

All objects share one UUID and alias namespace: Project, Element, Relation, +Diagram, NodePlacement, ArtifactLink, and Trace. UUIDs are durable identity; +aliases are durable, editable source labels. Display-name changes MUST NOT +regenerate existing aliases. Different objects MAY have the same display name.

+

Every Element has id, alias, name, description, parent +(optional element reference), and a typed body. Its kind and OA/SA layer +are derived from its body. A Relation has id, alias, name, +description, and a typed body; it has no independently editable kind tag +that can disagree with its payload.

+

Diagram placement does not determine semantic ownership. NodePlacement +records belong to diagrams and reference semantic elements. Multiple diagrams +can show the same element with independent placement and collapse state. +Within one diagram, each element appears at most once.

+

All stored strings MUST contain only XML 1.0 characters: U+0009, U+000A, +U+000D, U+0020–U+D7FF, U+E000–U+FFFD, or U+10000–U+10FFFF. Strings are not +trimmed or Unicode-normalized. Escaping a forbidden character does not permit +it. Coordinates and sizes are finite IEEE-754 binary32 values with absolute +value at most 1000000. Width and height MUST be positive. Signed zero and +subnormal coordinates are retained.

+
+
+

3. Element declarations and typed fields

+

Each element declaration starts with its layer and kind, for example +oa_activity or sa_function. Every element body accepts +description (default "") and parent (default none for a +document-level declaration, or its lexical element owner when nested; section +6.2). The table +lists all additional fields. ref means @alias; ref? also accepts +none. Lists preserve order. Unknown or duplicate fields are errors.

+ + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Element body field catalog

Declarations

Additional fields

Defaults

oa_entity, oa_actor, oa_role, oa_activity, oa_capability, oa_mission

None

Empty body

sa_system, sa_actor, sa_function, sa_capability, sa_mission

None

Empty body

oa_process, sa_functional_chain

steps: occurrence refs

[]

oa_exchange_item, sa_exchange_item

kind: Information, Material, Energy; mechanism: ExchangeMechanism; supertype: item ref?

Information; Unspecified; none

oa_exchange_category, sa_exchange_category

family: Work or Participant; exchanges: same-layer exchange refs

Work; []; root-level only

oa_data_type, sa_data_type

definition: DataTypeDefinition; default: DataValue?; unit: string

String with minimum_length 0 / maximum_length none; none; ""

oa_data_field, sa_data_field

type_ref; minimum: u32; maximum: optional u32; unit: string; direction: ParameterDirection; storage: FieldStorage; default: DataValue list?

String; 1; 1; ""; Unspecified; Value; none

oa_interface, sa_interface

supertype: interface ref?

none; membership is an identified interface_item_allocation relation

oa_port, sa_port

contract: required PortContract; legacy: optional LegacyPortContract

No implicit contract; legacy defaults none. OA admits Legacy only.

oa_scenario, sa_scenario

viewpoint: ScenarioViewpoint; context: ref?; precondition, postcondition: strings; steps: occurrence refs

MixedLegacy when viewpoint is omitted, including newly authored source 7 (see section 4.3); none; ""; ""; []

oa_lifeline, sa_lifeline

represented: participant, work, or role ref?

none

oa_occurrence, sa_occurrence

body: occurrence constructor; precondition, postcondition: strings

Exchange with none references, Asynchronous message kind and no values; two empty strings

oa_state_machine, sa_state_machine, oa_region, sa_region

None

Empty body

oa_state, sa_state, oa_mode, sa_mode, oa_initial, sa_initial, oa_final, sa_final

kind: StateKind; entry, exit, do_activity: strings

Kind selected by declaration; three empty strings

oa_choice, oa_junction, oa_fork, oa_join, oa_shallow_history, oa_deep_history, oa_entry_point, oa_exit_point, oa_terminate and corresponding sa_ declarations

Same State body; StateKind is Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint or Terminate

Kind selected by declaration; three empty strings

oa_event, sa_event

body: Signal, Time or Change constructor

Signal with items []

oa_constraint, sa_constraint

expression: string; constrained: semantic record refs

""; []

+

The explicit state kind field MUST agree with its declaration keyword. +u32 is an integer from 0 through 4294967295. A DataField's maximum MUST +be at least its minimum; none means unbounded. Primitive TypeRefs are +Boolean, Integer, Real, and String. A named TypeRef uses a DataType reference. +An Enumeration contains unique string literals. For example:

+
definition { Enumeration { literals ["Clear", "Fog"]; }; };
+type_ref { Named @weather_classification; };
+

These are typed constructors with named fields, not embedded JSON documents. +Constructor field names and enum spellings are case-sensitive. A selected +constructor uses its own field contract; it does not inherit the payload of a +different constructor from the declaration default. Required fields MUST be +supplied. Nullability and omission are separate properties: ? denotes a +value that can be none; a constructor's omission contract determines whether +the field may be absent. There is no general all-constructor-fields-required +rule. Other typed constructors use their contracts in the corresponding +sections; the occurrence rules below do not establish defaults for them.

+

The following table records the source-7 OccurrenceBody contracts. Optional +references may be omitted and then mean none. Exchange also admits omitted +message_kind and values, meaning Asynchronous and [] respectively. +These omissions are accepted in newly authored source 7, not only in schema-2 +compatibility input. Earlier schema admission of constructors and fields is +still governed by its version rules.

+

Canonical formatting writes the hydrated occurrence references and default +message_kind, reply_to and values explicitly. An absent +operation remains omitted in canonical output. Lossless no-op updates and +unrelated name edits retain authored omissions; canonical formatting is a +different operation. An admitted incomplete occurrence can still produce model +checks requesting its missing work, exchange, lifeline or referenced scenario.

+ + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Occurrence body constructors

Constructor

Required fields

Fields that may be omitted, with their resulting values

Exchange

None

exchange: exchange relation ref?, sender, receiver: lifeline refs?, reply_to: occurrence ref?, operation: OperationUse? → none; message_kind: MessageKind → Asynchronous; values: MessageValue records → []

Action

None

action: activity/function ref?, lifeline: ref? → none

Control

kind: Choice, Merge, Fork, Join, Loop

None

SequenceReference

None

sequence: process/functional-chain ref? → none

ScenarioUse

bindings: LifelineBinding records

scenario: scenario ref? → none

State

None

state: state/mode ref?, lifeline: ref? → none

Fragment

operator: Alternative, Parallel, Loop, Optional; branches: branch occurrence refs

None

Branch

guard: string; occurrences: occurrence refs

None

+

For example, body { Action { action @assess; lifeline @operator_line; }; }; +selects an Action occurrence. Processes and functional chains contain Exchange, +Action, Control and SequenceReference occurrences. Process/chain Actions are +contextual work contributions and MUST have lifeline none. A Control or +SequenceReference MUST belong directly to a Process or FunctionalChain, not a +Scenario. Scenarios contain Exchange, Action, State, Fragment, Branch and ScenarioUse +occurrences according to their enclosing ownership rules. Each +listed occurrence belongs to its enclosing sequence or branch through +parent. A Branch belongs to a Fragment; the Fragment's branches list +references the branches it owns.

+

An exchange and an occurrence of that exchange have different identities. +Repeating an exchange requires distinct occurrence records. Repeating one +occurrence identity within an ordered list is invalid. Conditions, guards, +entry/exit/do text, and constraint expressions are descriptive strings; this +version does not parse or execute them as a programming language.

+
+
+

4. Containment, allocation, and relationships

+

Containment MUST be acyclic and stay within one layer. Entities may contain +entities, actors, and roles. Actors are not structural containers. Activities +contain activities; functions contain functions. Capability grouping uses +same-layer capability parents; it does not mean inheritance or inclusion. +Operational and system missions are root semantic records; they do not own +capabilities by containment. MissionCapability expresses that association. +DataFields belong to ExchangeItems or Record DataTypes. Preserved generic ports belong to +participants or work. Lifelines belong to scenarios. State-machine ownership +uses participant → machine → region → state/mode; a state or mode can contain +a nested region. All other state vertices belong to Regions as well. Events +are root semantic records and do not acquire a machine owner when referenced.

+

DataFields, ports, lifelines, occurrences, Regions and all state vertices +require an owner. An unowned StateMachine can be saved as incomplete +work and produces a warning. The System cannot own internal components. +The present Studio profile does not decompose SA Actors.

+

All relations have a header identity, alias, display name, optional +description (default ""), and required source and target +endpoints. Ordinary endpoints are semantic references; ContextualLink endpoints +are qualified OccurrenceEndpoint records resolved from its required owner. +Their kind is the declaration keyword:

+ + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Relation catalog

Declaration

Endpoint meaning

Additional fields / defaults

allocation

Same-layer participant → leaf activity/function

None

interface_item_allocation

Same-layer Interface → ExchangeItem

order u32 / 0; transmission TransmissionProtocol / Unspecified; acquisition AcquisitionProtocol / Unspecified

data_association

Same-layer Record DataType → Record DataType

source_role, target_role / ""; source_minimum, target_minimum / 0; source_maximum, target_maximum / none; source_navigable, target_navigable / false

interaction

OA activity → OA activity

items refs / []

functional_exchange

SA function → SA function

Required source_port, target_port refs; items / []

boundary_exchange

Different participant owners; typed component ports have matching kinds

oriented bool / true; items refs / []

communication_mean

Different OA Entity/Actor elements; no operational ports or role inference

oriented bool / false; items refs / []

port_allocation

SA Component port → SA FunctionInput/FunctionOutput port

Requires the function's actual work Allocation to that component

capability_involvement

Participant, activity/function, or OA role → capability

None

chain_involvement

Process/functional chain → capability

None

role_realization

OA entity/actor → OA role

None

role_assignment

OA role → leaf OA activity

None

mission_capability

OA mission → OA capability, or SA mission → SA capability

None

mission_involvement

OA entity/actor → OA mission, or SA System/Actor → SA mission

None

capability_include

Including capability → included capability in one layer

None

capability_extend

Extending capability → extended capability in one layer

condition string / ""

capability_generalization

Specific capability → general capability in one layer

None

sequence_link

Preceding contribution → following contribution in one process/chain

guard string / ""

contextual_link

Qualified occurrence endpoints resolved from an explicit owner Process/FunctionalChain; section 4.2.1

Required owner, source and target OccurrenceEndpoint records, and typed purpose: Precedence, Exchange or CommonWork

state_transition

State vertices in one machine, subject to section 4.4

trigger, guard, effect strings / ""; transition_kind External; triggers, effects reference lists / []

availability

Work, participant, capability, or process/chain → state/mode

None

exchange_allocation

CommunicationMean → OA Interaction, or BoundaryExchange → same-layer Interaction/FunctionalExchange

None

interface_provision, interface_requirement

Participant/port → provided/required interface

None

state_action

State/mode → activity/function

phase: Entry, Do, Exit / Do

+

Relation endpoints and carried ExchangeItems stay in the same layer. Parallel +exchanges with distinct identities are permitted. Self-relations are invalid +except for a state transition that returns to its source state or an explicit +DataAssociation between two ends of the same Record type.

+
+

Source-6 communication and exchange categories

+

An OA communication_mean joins two distinct Entity/Actor records directly. +Its stable source/target tuple does not create operational ports. An unoriented +means permits either direction; an oriented ordinary use follows source to target. +The empty carried-item set is explicit, not a wildcard. Every item carried by an +allocated Interaction must specialize or equal a declared means item, using the +same inheritance rule as existing exchange allocations.

+

exchange_allocation remains a separately identified relation. A means may +allocate several Interactions and an Interaction may have several explicit means. +Allocation compares actual activity-to-participant Allocations, without inferring +responsibility from roles or nesting. Every known performer must fit one complete +permitted endpoint pair. A known contradiction is an error; a missing actual +performer is an incomplete-model warning. Reorienting or reconnecting a means +validates all dependent allocations and scenario uses in one candidate.

+

ParticipantInteraction and compatible MixedLegacy scenarios may reference means. +For bidirectional use, explicit sender and receiver lifelines determine one +compatible ordered endpoint pair. Missing or ambiguous bindings do not guess a +direction. A Reply reusing its Call's means reverses the resolved actual Call, +including a reverse Call on an unoriented means. The existing operation-response +exception permits the correlated Reply on an oriented means; this implies no +physical transport property. OA process contributions continue to use Interaction.

+

Mapped unoriented SA BoundaryExchange messages use the same explicit lifeline +resolution. Ordinary messages also respect component-port direction; legacy +port boundaries retain their stored direction. A correlated same-channel Reply +still reverses the actual Call, including across directional Flow ports.

+

CommunicationMean and SA BoundaryExchange support Exchange traces. OA-to-SA +transition requires a separate component-port choice for each end of each means: +reuse an existing compatible port, or create a port with explicitly chosen +Standard/Flow kind and orientation. Participant correspondence never substitutes +a port UUID for its participant UUID. Interfaces and PortAllocations are not +invented; unresolved technical allocations must be reviewed or omitted from the +selection and authored explicitly. Reverse transition projects SA port owners to +reviewed OA participant counterparts, without copying typed SA ports into OA. +Source/Target review retains the exact participant/port identities in history. +Taking an OA endpoint requires the scoped technical port decision, while Keep +and a valid Candidate retain explicit technical choices.

+

oa_exchange_category and sa_exchange_category are root-level named +semantic elements with family Work (default) or Participant, and exchanges +references (default []). Work categories contain Interaction/FunctionalExchange; +Participant categories contain CommunicationMean/BoundaryExchange. Members must +be distinct and in the category's layer; categories never own or merge exchanges. +Category transition requires mapped counterpart members. Category.Family and +Member { collection CategoryExchanges; ... } are closed typed semantic facts. +Deleting an exchange removes its category membership; deleting a category does +not remove its exchanges. Reidentification preserves all live references and +typed historical reference provenance.

+

Each Diagram has optional view_options (all selections unrestricted, no hidden +edges, Name labels by default). Its closed fields are hidden_edges, +relation_kinds, work_categories, participant_categories, coverage, +work_contexts and exchange_labels. Relation kinds use All or Only with +Relation(kind)/Trace entries; Only([]) draws no lines, including traces. Category +selectors use All or Any(category IDs); Any([]) draws none of that exchange family. +Selected categories are unioned within each family; independent restrictions +intersect. Coverage is All/InBehavior/OutsideBehavior; work contexts are All or +Any(explicit process, chain or scenario IDs). Contexts follow ordered owned +contributions and explicit reuse, sharing the advisory diagnostic evidence walk. +Labels are Name, Items or NameAndItems. These settings alter presentation only; +automatic port positions use unfiltered eligible lines. Hidden routes and semantic +identities remain saved. Removing a referenced selection preserves explicit empty +filters rather than widening them to All.

+

Traceability views retain their established two-layer projection: default options +preserve eligible OA and SA lines. Their hidden-edge, category and context +references may therefore select either layer. All ordinary projections require +these references in the declared diagram layer. A filter never hides the other +layer merely because the Traceability diagram has an OA or SA layer label.

+

Versions 1–5 reject the new relation, category constructor, diagram field (even +an explicitly supplied default), and category historical facts. Reading version 5 +preserves existing records, legacy OA boundaries and reviewed facts, with an +explicit upgrade report to the current version 7. Source upgrading changes the version token +without rewriting unrelated authored trivia. No old boundary is automatically +converted into communication means. Source-6 graphical/API/native acceptance is +still tracked separately from this integration contract.

+

Direct Allocation has at most one performer per leaf. Role sharing is a +different relationship: several OA participants can realize a Role. A direct +allocation inconsistent with its role's realized performers produces an +advisory warning; it does not prevent saving the model. Shared roles do not +imply a unique performer. +Allocation of a non-leaf work element is invalid. A computed graphical summary +of its allocated leaves MUST NOT create an extra semantic allocation.

+

Typed function ports belong directly to SA Functions. FunctionOutput +declares outgoing_items; FunctionInput declares incoming_items. +The lists contain distinct same-layer ExchangeItem references. A functional +exchange MUST reference output/input ports owned by its respective functions, +and each carried item MUST fit both declarations. Empty declarations accept +no items. A carried item satisfies an identical declaration or a declaration +of one of its explicit base items. A base item does not satisfy a more +specialized declaration; names imply no compatibility. Port declarations +remain independent of incident exchanges after creation.

+

A Component contract belongs to an SA System or SystemActor and stores +kind (Standard or Flow) and orientation (Unset, In, Out, InOut). +Standard requires Unset. Flow Unset is an incomplete draft, not InOut. Typed +component connections require matching kinds: Standard/Flow mismatch is an +error. Standard connections normally use oriented false; directional Flow +connections use true. Boundary orientation is separate from endpoint kind and +orientation. Oriented Flow endpoints permit Out at source and In at target; +unoriented Flow endpoints must permit at least one communication direction.

+

Provided/required interfaces are separate identified relations. A Standard +partner satisfies a requirement by providing the identical interface; stored +source/target order does not infer service roles. Component carried items must +fit the declared interface-item union when explicit declarations exist. An +explicitly empty interface is not a wildcard. Missing declarations and +unsatisfied required interfaces produce located completeness warnings.

+

PortAllocation relates a Component port to a typed function port only when +that function has an actual Allocation to the component owner. Standard may +allocate either function direction; Flow directions must agree. A typed +ExchangeAllocation requires both corresponding PortAllocations and compatible +carried items. Unoriented boundary exchanges allow reversed correspondence. +Visual allocation nesting is never evidence of a stored allocation relation.

+

Legacy requires legacy { direction ...; interface ...; }. It preserves +generic direction/interface data and emits a located compatibility warning. +It is an explicit compatibility constructor in JSON/source; ordinary creation +pickers do not offer it. Resolution to a typed SA contract retains this record +without treating it as another active constraint. OA generic ports remain +Legacy; OA Interactions stay activity-to-activity without function ports. +Deleting a referenced legacy interface clears that optional live reference.

+

A current-source functional exchange MUST specify both port aliases; the parser does +not create endpoints. Graphical/command FE creation plans ports atomically, +using unique compatible typed-port reuse or deterministic new endpoints. +Port deletion cascades to incident FEs; FE deletion retains independent ports.

+

For example:

+
arcadia 7;
+project ports_example "Typed function contract" {
+    sa_function send "Send" {}
+    sa_function receive "Receive" {}
+    sa_exchange_item payload "Payload" {}
+    sa_interface old_service "Preserved service" {}
+    interface_item_allocation service_payload "Service payload" { source @old_service; target @payload; }
+    sa_port output "Output" {
+        parent @send;
+        contract { FunctionOutput { outgoing_items [@payload]; }; };
+    }
+    sa_port input "Input" {
+        parent @receive;
+        contract { FunctionInput { incoming_items [@payload]; }; };
+    }
+    functional_exchange message "Message" {
+        source @send; target @receive;
+        source_port @output; target_port @input; items [@payload];
+    }
+    sa_port preserved "Unresolved original" {
+        parent @send; contract Legacy;
+        legacy { direction InOut; interface @old_service; };
+    }
+}
+

An exclusive region has at most one initial state. Missing initial state is +a completeness warning. Initial states have no incoming transitions and at +most one outgoing transition; their outgoing transition has no trigger/guard. +Final states have no outgoing transitions. Mixing State and Mode entries is +permitted by this Studio profile. StateAction links provide typed work +references alongside the descriptive entry/exit/do strings.

+
+
+

4.1. Capabilities and missions

+

Include means the including capability requires the included capability's +behavior. Extend means the source capability adds conditional behavior to the +target; its condition records when that behavior applies. Generalization +means a specific source capability specializes a general target capability. +Each relationship stays within OA capabilities or within SA capabilities. +Include and Generalization graphs MUST each be acyclic, including transitive +cycles. Extend does not introduce an additional cycle restriction; the ordinary +no-self rule still applies. An empty Extend condition is an engineering warning +so an incomplete capability map can be authored and saved.

+

MissionCapability and MissionInvolvement express need and stakeholder/system +participation. They MUST NOT create a capability parent, a performer allocation, +or a cross-layer realization. These relationships remain distinct when the +same objects appear in a diagram or share names.

+
+
+

4.2. Contextual process and chain topology

+

A Process or FunctionalChain owns contribution occurrences with stable +identities. steps is their persistent membership and presentation order; +an identity MUST occur at most once in that list. A work contribution points to +an existing same-layer Activity or Function. Reusing the same work or exchange +requires separate occurrence identities and MUST NOT duplicate the referenced +engineering element. A SequenceReference points to another same-layer Process +or FunctionalChain of the owner's kind. Its target may be none while the +draft is incomplete. Direct and transitive recursive sequence reuse are errors.

+

SequenceLinks connect distinct occurrences directly owned by the same process +or chain. Their source is the predecessor and target is the successor. A link +from work to an exchange requires that work to be the referenced exchange's +source; an exchange-to-work link requires the target work to be its endpoint. +Two adjacent exchange contributions require the first exchange's target to +match the second exchange's source. A mismatch is a structural error. Other +links express contextual precedence without manufacturing a global exchange.

+

When links or work/control/reuse contributions exist, topology is explicit. +Reordering steps MUST NOT add, remove or reorder SequenceLinks. The editor +MUST retain the difference between alternative Choice/Merge and parallel +Fork/Join contributions. Choice/Fork expect one incoming and at least two +outgoing links; Merge/Join expect at least two incoming and one outgoing link. +Loop expects at least one incoming and two outgoing links for repetition and +exit. These cardinalities are completeness warnings, not barriers to building +an incomplete flow. Choice and Loop outgoing paths with empty guards warn; +an explicit else guard is permitted. Guards remain descriptive strings.

+

Validation warns about missing/ambiguous entry and exit, isolated or unreachable +contributions, contributions with no reachable exit, and splits/merges that +lack explicit controls. Topology cycles are permitted for loops; the recursive +SequenceReference prohibition is a different rule. A legacy exchange-only +sequence without explicit links retains its ordered linear meaning and +adjacent-exchange gap diagnostics. Implementations MUST NOT silently invent +topology links when loading or reordering such a sequence.

+ +
+
+

4.3. Typed scenarios and reusable behavior

+

ScenarioViewpoint is one of OperationalActivity (OA activity lifelines), +ParticipantInteraction (OA entity/actor/role lifelines), Functional (SA function +lifelines), BoundaryInterface (SA System/Actor lifelines), or MixedLegacy. +The first two are OA viewpoints; the next two are SA viewpoints. Lifeline +references MUST agree with that perspective. OperationalActivity messages use +Interactions; Functional messages use FunctionalExchanges. Participant views +may use their layer's work exchanges through effective performer allocation, +or participant boundary exchanges through the relevant ports. Corresponding +lifelines and endpoints MUST agree; a viewpoint is not a label that suppresses +reference checks.

+

Omitted schema-2 viewpoints hydrate as MixedLegacy in both source and JSON. +This preserves pre-extension mixed work/participant scenes without discarding +their lifelines or silently assigning a stricter meaning. MixedLegacy emits a +review warning. Source parsing also uses MixedLegacy when viewpoint is +omitted from a newly authored source-7 scenario; it does not infer a strict +viewpoint from the layer or lifelines. Write the intended viewpoint explicitly +when starting a scenario. For example, this new OA source project starts an +activity-based scenario whose lifelines and occurrences can be added later:

+
arcadia 7;
+project response "Distress response" {
+    oa_scenario assessment "Assess the distress report" {
+        viewpoint OperationalActivity;
+    }
+}
+

An empty scenario can produce completeness warnings while remaining editable. +The desktop's Behavior New flow asks authors to choose one of the layer's +two strict viewpoints explicitly. Authors can also select a strict viewpoint +after reviewing a legacy scene; canonical source then records that choice.

+

MessageKind is Asynchronous, Call, Reply, Create, Delete, or +{ Timer { expression "after 5 s"; }; }. A process/chain exchange +contribution MUST remain Asynchronous with no reply correlation. Call/reply, +creation/deletion and timer semantics belong to scenarios. Reply's reply_to +references a Call occurrence in the same scenario. The call MUST precede the +reply on a guaranteed common sequence/enclosing branch path. Reply lifelines +reverse the call's sender/receiver; it may reuse the call's exchange in reverse +or use an explicit reverse-direction exchange. A second reply to the same call +on that guaranteed path is an error. Missing reply correlation is a draft +warning; non-Reply messages MUST NOT have reply_to.

+

Create identifies the receiver lifeline's creation. An earlier guaranteed use +of that lifeline is an error. Delete identifies its destruction; a later +guaranteed use is an error. Ordering checks respect branch ownership; they MUST +NOT flatten alternative branches into a false unconditional order. A Timer is +local to its sender lifeline, has no global exchange or values, and has either +no receiver or the same receiver as sender. Its nonempty expression describes +the delay/deadline; an empty expression warns. Conditions and expressions are +stored engineering annotations, not an executable simulation language.

+

Each MessageValue is { path [@parameter]; field @data_field; expression "value expression"; }. +Its identity is the pair (path, field), so two uses of one Record can have +different values for the same terminal field. Path contains enclosing DataField +references, excluding the terminal field; direct parameters use []. Every +hop MUST follow Value storage through a named Record's effective fields. Reference +storage stops traversal. Operation Call accepts In/InOut and Reply accepts +Out/InOut/Return, interpreted from the provider's perspective. Nested field paths +inherit their direct parameter's direction. Unspecified direction warns. +At most one annotation for a pair is allowed. Omitted path defaults to []; +an ambiguous preserved legacy nested annotation warns message_value_ambiguous_path +until an explicit path is selected. New graphical choices always identify a path. +Values never overwrite types or items. Expression text is descriptive, preserved +exactly and not evaluated; an empty expression warns.

+
+

4.3.1. Explicit interface-operation invocations

+

The optional Exchange occurrence field operation distinguishes ordinary +messages from explicit operation intent. Omitted or none retains ordinary +channel semantics and is omitted in canonical output. A present closed record +with absent/none allocation means an explicitly unresolved operation. +A present allocation @service_operation selects that exact +InterfaceItemAllocation relation, not its item UUID, a name, a port or whichever +allocation happens to carry the same parameters. Thus parameterless Reset and +ReadStatus operations on one channel remain distinct maintained identities.

+

Operation intent is permitted only on SA BoundaryInterface scenario Call or +Asynchronous occurrences. A selected allocation MUST expose an Information +Operation item through the actual concrete Standard component ports used by the +invocation: receiver Provides, sender Requires a compatible interface exposing +that same allocation, using the normal interface inheritance rules. Containment +does not inherit participant interface roles onto a port. A separately allocated +same item is not the selected allocation. The actual channel traversal determines +direction, including an unoriented channel used in reverse. Call requires +Synchronous transmission; Asynchronous requires Asynchronous transmission; +Unspecified is incomplete and warns. A selected allocation without these proofs +is an error. Explicit unresolved intent remains editable with a located warning.

+

Reply MUST store no operation selection. It derives allocation, provider and +signature from its exact correlated Call, while independently satisfying the +existing reply order, reverse-participant and channel checks. It may reuse the +Call channel in reverse or use a separate explicit return channel. The separate +channel MUST carry the selected item or an admitted subtype and satisfy its own +actual endpoint contract, including for a parameterless/void return. The Reply +receiver need not provide the operation; the provider was established by the +Call. No operation or correlation may be guessed from similar names or fields.

+

Selected Call/Asynchronous values use only that operation's In/InOut signature; +Reply values use Out/InOut/Return. All ordinary nested-path and multiplicity rules +still apply. Unresolved operation intent preserves still-live expressions and +performs independent live-reference/path checks with an incomplete-signature +warning. It MUST NOT fall back to the union of items carried by the channel. +Selecting a new allocation validates all retained values and refuses incompatible +values without silently pruning them or creating defaults.

+

Deletion cleanup clears a deleted selected allocation to explicit unresolved +intent and preserves still-live values; it does not erase the Call or Reply. +The whole candidate MUST still satisfy independent channel contracts. Deleting +the sole allocation can therefore refuse while a boundary still carries its +now-unadmitted item. An explicit repair batch must address every affected channel, +including a separate Reply channel. If a surviving allocation admits the item, +deletion can succeed, but it MUST NOT select that surviving allocation as the +operation. Item/field deletion uses the normal proven-reference cleanup rules.

+

SA→OA transition of operation intent requires one explicit +OperationOmission { source, rationale } per selected invocation. Rationale +MUST be nonempty. Missing, irrelevant, duplicate or wrong-direction decisions +are errors. Reply derives its Call's abstraction without a second operation +choice. The source model retains its operation; other facts and values are mapped +explicitly and the complete target is validated. The transition MUST NOT invent +OA technical ports, operation identities or functional refinement to avoid this +decision.

+
+
+

4.3.2. Scenario reuse

+

ScenarioUse references an existing same-layer scenario and maps its lifelines +with bindings [ { referenced @callee_line; local @caller_line; } ];. +Each binding is one-to-one and references a direct lifeline of the appropriate +scenario. Bound lifelines represent the same semantic object, or an explicitly +realized operational role. Strict viewpoints MUST agree; MixedLegacy retains +the compatibility case until reviewed. Missing bindings produce warnings. +Direct/transitive recursive scenario use is invalid; repetition uses a Loop +fragment instead. ScenarioUse owns neither its referenced scenario nor its +lifelines; deleting a use does not delete the reusable definition.

+
+
+
+

4.4. State vertices, events and transition contracts

+

StateKind comprises State, Mode, Initial, Final, Choice, Junction, Fork, Join, +ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate. Every vertex +belongs to a Region. Only State and Mode are stable states: only these may own +nested Regions, typed StateAction links, availability and scenario state +observations. Multiple sibling Regions describe orthogonal configurations. +EntryPoint and ExitPoint retain Region ownership; their boundary is that +Region's owning State/Mode or StateMachine. Their graphical symbols belong on +that boundary. This is Studio's explicit containment encoding.

+

Events are ordinary same-layer semantic elements, independent of diagram +placements. EventBody has exactly these constructors:

+ + + + + + + + + + + + + + + + + + +
Event definitions

Constructor

Fields and meaning

Signal

items is a unique list of same-layer ExchangeItem references; empty is valid

Time

kind is After or At; expression is descriptive time text

Change

expression describes the Boolean condition becoming true

+

After measures relative time from activation of a transition's source state; +At describes an absolute instant. Empty Time/Change expressions are located +completeness warnings. The application does not execute expressions, fire +timers or transport signals. For example:

+
sa_event timeout "Recovery timeout" {
+    body { Time { kind After; expression "30 s"; }; };
+}
+sa_event recovered "Pressure recovered" {
+    body { Change { expression "pressure > 4 && status == \"Ready\""; }; };
+}
+

StateTransition's triggers references Events or layer-appropriate work +(OA Activity / SA Function). Referenced work need not have the machine owner's +performer: external work can cause the observed transition. Multiple triggers +are alternatives. effects references work describing effects, without +executing it. Duplicate, missing, wrong-layer or wrong-kind references are +errors. The existing trigger, guard and effect strings remain +authored prose; they MUST NOT be renamed, parsed as UUIDs or inferred by name.

+

With typed triggers, trigger adds descriptive information. With no typed +triggers and nonblank trigger prose, the transition has an unresolved +descriptive trigger and emits a warning. Both empty denotes completion from +a stable state or transient routing from a pseudostate. Deleting the final +typed trigger when prose is empty MUST leave an explicit descriptive reminder; +it MUST NOT silently create a completion transition. Guards/effect prose remain +unchanged. Signal payload references and typed effect references are pruned +when their targets are removed.

+

TransitionKind is External, Local or Internal. External leaves/re-enters the +affected configuration; a stable self-transition includes exit and entry +behavior. Internal requires the same stable source and target, preserves nested +configuration, and does not imply entry/exit. Local requires a composite stable +source and proper descendant target, preserving the source activation; an +EntryPoint also routes locally into its boundary. Local self/outside targets +are invalid. Other pseudostates route externally. Endpoints MUST share a layer +and StateMachine; crossing nested Regions within that machine is not generally +forbidden. A pseudostate self-transition is invalid.

+

A simple state's completion means entry/do behavior is complete. A composite +completes when its active Regions complete. Final completes its containing +Region; Terminate ends the containing machine's activation. These are modeling +meanings, not evidence of a running state-machine engine.

+

Missing connections/guards and unfinished initial/history paths produce +warnings, so an engineer can insert vertices before wiring them. Contradictory +upper cardinalities, forbidden directions/kinds and invalid references reject.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Vertex connection constraints

Vertex

Structural restrictions

Incomplete-model warnings

Initial

At most one per Region; no incoming; at most one outgoing; no outgoing trigger or guard

Missing initial/default entry or outgoing

Final / Terminate

No outgoing or nested Region

No incoming

Choice

At most one incoming; outgoing transient alternatives

Missing incoming, fewer than two outgoing, missing guard or else

Junction

Transient routing, no implicit concurrency

Missing side or ambiguous unguarded alternatives

Fork

At most one incoming; outgoing stable targets in distinct orthogonal Regions

Missing incoming or fewer than two outgoing

Join

At most one outgoing; incoming stable sources in distinct orthogonal Regions

Fewer than two incoming or missing outgoing

ShallowHistory

At most one per Region; at most one fallback to a direct stable child of that Region; no fallback trigger/guard

Missing fallback/default entry

DeepHistory

At most one per Region; at most one fallback to a stable descendant of that Region; no fallback trigger/guard

Missing fallback/default entry

EntryPoint

Incoming from outside boundary, outgoing Local into it; at most one outgoing per destination Region

Missing outgoing or incomplete affected-Region entry

ExitPoint

Incoming from inside boundary; at most one outgoing External to outside

Missing input/output; top-level exit is an exit endpoint, not implicit termination

+

Fork outputs and Join inputs MUST NOT carry guards or typed/descriptive +triggers. Their distinct orthogonal Regions are the first Regions beneath the +nearest common composite or machine, not merely each endpoint's immediate +parent. Choice/Junction routing expresses conditional alternatives. Retained +pseudostate entry/exit/do prose produces a warning and remains editable; it +does not become a typed state behavior. StateAction remains the maintained +reference for entry/do/exit work on stable states.

+

Other pseudostate outgoing segments MUST NOT introduce event/work triggers +or trigger prose. A Choice has at most one literal else guard; the tool +does not prove arbitrary guard expressions disjoint or exhaustive. Choice +expresses a dynamic decision after incoming effects; Junction expresses a +compound path selected by guards before traversing it. ShallowHistory remembers +the immediate state and uses default entry for deeper configuration; +DeepHistory remembers nested configuration. Fallback applies when no resumable +history exists. History is per Region, including top-level Regions in this +Studio profile; no running history state is persisted or synthesized.

+
+
+

4.5. Data contracts, specialization and interface allocation

+

ExchangeItem kind describes nature: Information, Material or Energy. +mechanism is Unspecified, Flow, Event, Operation or SharedData. Unknown +mechanisms remain explicit Unspecified drafts with a warning. Material/Energy +with Event/Operation/SharedData warns for engineering review; it is not silently +reclassified. Empty Event notifications and void Operations are valid. An empty +carried/allocated Flow, SharedData or Unspecified item warns.

+

An Operation's directly owned DataFields are parameters. direction is +Unspecified, In, Out, InOut or Return, from the provider's perspective. At most +one effective parameter is Return. Unspecified parameters warn; non-operation +fields MUST use Unspecified. Nested record fields inherit direction from their +direct parameter when used in message paths, rather than storing context on a +shared type. storage is Value or Reference. Reference requires a named Record, +has no explicit default, and does not convey ownership or deletion of the type.

+

DataTypeDefinition has exactly the following constructors:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Reusable definitions

Constructor

Required fields

Boolean

None

Integer

minimum, maximum: optional signed 64-bit integers

Real

minimum, maximum: optional finite IEEE-754 binary64 values

String

minimum_length: u32; maximum_length: optional u32

Enumeration

literals: unique nonempty, case-sensitive strings

Record

supertype: same-layer Record ref?

+

Bounds are inclusive and ordered; an absent bound means unbounded within the +represented numeric domain. Integer comparisons MUST NOT convert to binary64. +NaN, infinities, overflow and reversed bounds reject. String length counts Unicode +scalar values, not UTF-8 bytes or grapheme clusters. Enum defaults match exact +literals. Empty enum definitions remain incomplete drafts with a warning.

+

DataValue is a tagged scalar: { Boolean true; }, { Integer 42; }, +{ Real 1.5; }, { String "value"; } or { Enumeration "Ready"; }. +There is no implicit numeric or string coercion. A type default is one compatible +scalar within its bounds. Record type defaults and record/object instance literals +are unsupported. A field default is absent (none) or a list of compatible +scalars with length inside its multiplicity. [] explicitly supplies an empty +collection and requires minimum 0, including Value Record fields; a nonempty +Record literal rejects. Reference fields MUST use none. A one-valued scalar +field uses a one-value list. A named type default supplies an individual element's +default, not a collection or an implicit instance of a mandatory field.

+

Numeric types may carry unit text. Empty field units inherit the named type unit; +an explicit different unit warns when that type has a unit. Units on nonnumeric +fields/types warn and remain preserved. Unit strings never convert values or +establish dimensional compatibility. Expressions are not executed.

+

Record, ExchangeItem and Interface each permit one same-family, same-layer +supertype. Specialization is acyclic and separate from containment. Effective +fields are base fields followed by local fields, preserving original field UUIDs +and owners. Local names MUST NOT shadow inherited names. Item specialization +preserves base nature and any specified mechanism; an Unspecified base mechanism +may be refined. Deleting a derived record never deletes its base. Mandatory +Value-field cycles warn record_no_finite_value; optional or Reference cycles +are valid without that warning. Traversal is cycle guarded and bounded; a payload +path budget failure is an explicit diagnostic, not silent truncation.

+

InterfaceItemAllocation is an ordinary identified relation from Interface to +ExchangeItem. It stores order and independent transmission/acquisition +protocols. Transmission is Unspecified, Synchronous, Asynchronous, Unicast, +Multicast or Broadcast; acquisition is Unspecified, Read or Accept. No protocol +combination is inferred from mechanism. Effective allocations are base-first, +then local ordered by (order, alias, UUID). A local item already allocated locally +or inherited is an error. Removing an allocation keeps its item and other +allocations; if live boundary compatibility would break, the transaction MUST +repair the affected contract explicitly or reject atomically. Artifacts can +reference the allocation identity independently of the global item.

+

An exchange carrying a specialized item satisfies a declaration of its base, +never the reverse. Standard provided/required compatibility uses explicit +interface ancestry. Identity/ancestry, not matching names, defines conformance. +The current typed function/component port contracts remain unchanged.

+

DataAssociation connects Record types without adding fields or transitively +carrying payload. Each named end has role, multiplicity and navigability. Source +multiplicity counts source instances per target instance and vice versa; it is +not a count of model elements. An end's navigable flag permits navigation toward +that end. Self-associations are valid; if both ends are navigable they require +distinct nonempty roles. Empty roles and duplicate role contracts warn, whereas +invalid ranges reject. Composition/shared aggregation, multiple inheritance, +redefinition, instance literals and arbitrary-precision decimal arithmetic are +outside this bounded contract.

+

This complete data contract example is also exercised through the REST API:

+
arcadia 7;
+project engineering_data "Engineering data" {
+    sa_data_type voltage "Voltage" {
+        definition { Real { minimum 0.0; maximum 48.0; }; };
+        default { Real 24.0; }; unit "V";
+    }
+    sa_data_type coordinate "Coordinate" {
+        definition { Record { supertype none; }; };
+        sa_data_field latitude "Latitude" { type_ref Real; }
+    }
+    sa_data_type located "Located asset" {
+        definition { Record { supertype @coordinate; }; };
+        sa_data_field supply "Supply" { type_ref { Named @voltage; }; }
+    }
+    sa_exchange_item navigate "Navigate" {
+        mechanism Operation;
+        sa_data_field origin "Origin" { type_ref { Named @coordinate; }; direction In; }
+        sa_data_field destination "Destination" { type_ref { Named @coordinate; }; direction In; }
+        sa_data_field accepted "Accepted" { type_ref Boolean; direction Return; }
+    }
+    sa_interface navigation "Navigation" {}
+    sa_interface monitoring "Monitoring" {}
+    interface_item_allocation nav_operation "Navigation operation" {
+        source @navigation; target @navigate; transmission Synchronous; acquisition Accept;
+    }
+    interface_item_allocation monitor_operation "Monitoring operation" {
+        source @monitoring; target @navigate; transmission Asynchronous; acquisition Read;
+    }
+    data_association location_link "Location relationship" {
+        source @coordinate; target @located;
+        source_role "location"; target_role "assets";
+        source_maximum 1;
+    }
+}
+
+
+
+

5. Diagrams, artifacts, and cross-layer traceability

+

diagram has name, identity, alias, description, layer (Oa by default), +projection (Overview by default), and routes (empty list by default). +The string projections are Overview, Decomposition, Allocation, +FunctionalDataflow, Capabilities, and Traceability. StateMachine is a payload +constructor with one required machine reference. Traceability diagrams +can show both layers; other diagrams show their chosen layer.

+

Allocation is an architecture view: it shows participants and work, nests +allocated work under its performer, and displays interactions, functional +exchanges and boundary exchanges. FunctionalDataflow shows the work hierarchy +and its exchanges. Capabilities shows participants, roles, missions and +capabilities with their involvement, exploitation, include, extend and +generalization links. Decomposition and Overview retain general containment; +Overview also exposes all relationships. These string projections hide +inapplicable placements without deleting them or changing semantic records.

+

A StateMachine projection MUST reference an existing StateMachine in the +diagram's layer. Its placements MUST refer to that machine, its Regions, or +its state vertices, including nested Regions and vertices. Events, other +machines and their members, and unrelated semantic elements are not valid +placements in this view. Empty and partial state views are valid; neither +creating nor reading a view synthesizes semantic elements or placements. +Changing an existing diagram to StateMachine, or changing its machine, MUST +remove or relocate incompatible placements in the same transaction. Context +errors use state_diagram_context; incompatible placements use +state_diagram_member. Neither case silently hides invalid members.

+

This complete source example places one state from an existing machine:

+
arcadia 7;
+project operating "Operating modes" {
+    oa_entity crew "Crew" {}
+    oa_state_machine modes "Modes" { parent @crew; }
+    oa_region main "Main region" { parent @modes; }
+    oa_state ready "Ready" { parent @main; }
+    diagram mode_view "Operating modes" {
+        layer Oa;
+        projection { StateMachine { machine @modes; }; };
+    }
+    node ready_view { diagram @mode_view; element @ready; }
+}
+

The JSON projection is +{"StateMachine":{"machine":"11111111-1111-4111-8111-111111111111"}}, +where the UUID is the referenced machine's identity. A bare "StateMachine", +a missing or null machine, an invalid UUID, or additional payload fields +is malformed. Existing projections retain their string representation, for +example "Overview" in JSON and projection Overview; in source.

+

Ports are boundary anchors of visible owners, with their existing +semantic identity. An explicitly placed port is not rendered as a separate +large node. Functional exchanges use their declared source/target ports; +boundary exchanges attach to their participant ports even if those ports have +no separate placement. Collapsing an owner hides its interior; projected links +may terminate at a visible ancestor without creating a new port or exchange.

+

node has an alias and identity but no display-name string. Required fields +are diagram and element references. Optional fields are position +(record with x/y, default 0/0), size (width/height, default 224/94), and +collapsed (false), and port_anchors (empty list). Node identity is +independent of its semantic element. +Coordinates are absolute diagram coordinates. Placement does not alter parent, +allocation, or realization. Hiding an element from a diagram must remain +distinguishable from deleting it from the model. +Changing a node's diagram preserves the placement identity, alias, coordinates, +size, collapse state, manual port anchors, and attached artifacts. The destination +must accept the element's layer, satisfy StateMachine membership when applicable, +and must not already contain another placement of that element.

+

Each port_anchors entry is a PortAnchor record with required port +reference, side (Left, Right, Top, or Bottom), and offset (finite +binary32 from 0 through 1). It has no separate identity or alias. The port MUST +be an existing Port directly owned by the placement's element. A port MAY +have one manual anchor per placement; different diagrams MAY position it +independently. List order is preserved but has no positional meaning.

+

offset measures a fraction of the complete projected owner edge, from top +on Left/Right or from left on Top/Bottom. Zero and one denote its corners. +The side is presentation data and MUST NOT change the port's contract, +orientation, ownership, allocations, or connected exchanges. Absent anchors use +automatic positioning. Empty lists are omitted in canonical JSON/source; +earlier schema-3 input without this field hydrates as [].

+
node actor_view {
+    diagram @architecture; element @vessel;
+    port_anchors [{ port @communication; side Top; offset 0.25; }];
+}
+

SetPortAnchor { diagram, node, anchor } upserts by port identity and retains +unrelated entries. RemovePortAnchor { diagram, node, port } restores +automatic positioning; it is a no-op only for an existing direct child port +of that placement. Unknown fields, duplicate port references, non-port or +foreign-owner references, and invalid offsets are structural errors. Each +anchor counts as one record against ParseLimits.max_records.

+

These commands use the same atomic history, conditional REST transactions, and +source writeback as other presentation edits. Source references resolve across +files, and anchor comments follow port identity. Reidentifying a port updates +anchor references; deleting it removes its anchors. Reparenting MUST repair +old-owner anchors in the same transaction, for example by removing them before +the Reparent command. Moving, resizing, arranging, or folding an owner retains +its authored sides and fractions. Frozen schema-1/schema-2 readers do not accept +port_anchors; their explicit migration supplies an empty list.

+

Each routes entry is an EdgeRoute record with a required edge reference +to an existing Relation or Trace, waypoints (default []), and label +(default none). Route records have no separate UUID or alias. An edge occurs +at most once in a diagram's route list; different diagrams MAY store independent +routes for the same edge. List order and explicit empty entries are retained by +source and JSON round trips, although list order has no routing meaning. Edges +need not currently be visible in their diagram.

+
routes [
+    { edge @report_exchange;
+      waypoints [{ x 420; y 260; }, { x 690; y 260; }];
+      label { fraction 0.55; offset { x 0; y -16; }; };
+    }
+];
+

Waypoints are pinned absolute diagram positions in source-to-target traversal +order; at most 128 are allowed per edge. Computed endpoints and automatic elbows +MUST NOT be persisted as authored waypoints. A label record requires both +fraction (finite binary32 from 0 through 1, measured along the computed +polyline's arc length) and offset (a relative x/y diagram displacement). +All position/displacement components obey the coordinate bounds in section 2. +An absent label uses automatic placement. Unknown fields, missing required +nested fields, duplicate route edges, non-edge references and invalid numbers +are structural errors. A pinned point that obstructs routing remains stored; +geometric routing difficulty is reported separately from structural validity. +Each EdgeRoute counts as one record against ParseLimits.max_records; pinned +points are bounded by the per-route limit and aggregate input byte limits.

+

SetEdgeRoute { diagram, route } upserts an entry by edge identity, retaining +the order and contents of unrelated entries. RemoveEdgeRoute { diagram, edge } +removes it; removing an absent entry is a no-op for an existing diagram and edge. +The latter is the Reset routing operation. Either command participates in the +same atomic transactions, validation and undo as other model edits. Deleting a +relation or trace removes its routes in every diagram; reidentifying it updates +route references without changing geometry. Folding, hiding, changing projection +or relocating placements MUST NOT erase routing metadata. Group movement +translates pins when both actual endpoint owners move by the same delta; label +offsets remain relative. Raw EditNode and source position edits are exact +record edits and do not implicitly translate pins.

+

This is an additive schema-3 field. Earlier schema-3 JSON/source that omits +routes loads as an empty list. Frozen schema-1 and schema-2 readers do not +accept this new field; migration creates empty route lists. Source projects keep +routes in their owning diagram declaration/file, resolve edge @alias across +files, and retain route-entry comments by referenced edge identity when entries +are reordered. The existing REST transaction endpoint accepts the same externally +tagged command records with UUID references and conditional ETags; no separate +routing endpoint or UI-only routing model exists.

+

artifact has name, identity, alias, description, required owner ref, +relation (References, Satisfies, Verifies, Implements, DerivedFrom; +default References), uri (default empty), revision (optional string), +and fragment (optional string). The two optional strings default to none. +URI must be nonempty, have no whitespace/backslash/control characters, and +use valid percent escapes. The owner is another existing model object. +Validation MUST NOT fetch the URI. Revision and fragment are retained verbatim.

+

trace has name, identity, alias, description, required oa and sa +endpoint references, kind (default Refinement), and baseline (default +none). Endpoints are canonical: SA realizes OA, regardless of which side +initiated creation. Trace does not change endpoint identity or containment. +Multiple distinct counterparts are permitted; duplicate endpoint/kind triples +are invalid.

+ + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Trace kinds

Kind

OA → SA endpoint types

Refinement

Activity → Function

Participant

Entity/Actor → System/Actor

Capability

Operational Capability → System Capability

Mission

Operational Mission → System Mission

Exchange

Interaction → FunctionalExchange, or CommunicationMean → BoundaryExchange

Sequence

Process → FunctionalChain, or paired occurrence contributions directly owned by Process / FunctionalChain

Item

Same shared kind: ExchangeItem, DataType, DataField, Interface, or Port

Scenario

Same shared kind: Scenario, Lifeline, or Occurrence

State

Same shared kind: StateMachine, Region, any StateKind vertex, or Event

Manual

Engineer-defined relationship between OA/SA elements or relations

+

A nonempty baseline retains the required prose records oa { name "..."; description "..."; }; sa { name "..."; description "..."; }; and optionally +semantic (default none), defined below. The original prose records and typed +semantic facts are distinct review histories. Importing an older prose baseline +MUST NOT fabricate semantic acknowledgements. Neither history is a full model +snapshot, structural synchronization, or a lossless inverse transition. +Explicit counterpart creation, linking, comparison, and reviewed property +update may start in either layer. Ordinary edits MUST NOT automatically +overwrite the counterpart. Deletion MUST NOT silently cascade across layers.

+

Operational Capability normally creates a System Capability. Operational Mission +and System Mission are default counterparts in both directions and use Mission +traces. An explicit Operational Capability ↔ System Mission mapping remains +supported with a Manual trace; a uniquely linked such counterpart is reused +instead of silently creating a different mapping. Required/subtree process and +chain transitions include their contextual links, owned contributions and +referenced dependencies. New process/chain contribution mappings use Sequence +traces; older Scenario-kind occurrence mappings remain accepted. The current +prose baseline alone does not reconcile later topology changes.

+

In the prose-only workflow, name and description are compared independently +against each endpoint's last-reviewed baseline. An unchanged source MUST NOT overwrite an accepted +different target value. A source-only change proposes an update; different +changes on both sides require an explicit decision to take the source, keep +the target, or enter a merged value. An explicit decision records both current +values as reviewed, even when they differ. A target-only edit is preserved +without advancing that field's baseline, so it remains eligible to propagate +in the reverse direction. Equal current values can be recorded as converged.

+
+

5.1. Typed semantic review facts

+

baseline.semantic is either none or { facets [facet, ...]; }. +Each facet is a closed, self-tagged typed record, not a free-form path/value +entry. List position does not identify a facet. A scalar fact is exactly +{ Family { Leaf { oa observation; sa observation; }; }; }. Each observation +is either NotApplicable or { Value value; }, with the value type fixed +by Family/Leaf. Both sides are always canonical OA/SA, even during reverse review.

+

{ Value none; } means an applicable optional field was absent; +NotApplicable means the field's branch or record was not applicable. +An absent facet means Unreviewed. These meanings MUST NOT be conflated. Each +side is compared with its own last-reviewed value; deliberately unequal values +can be reviewed and unchanged. Reviewing one minimum does not acknowledge a +maximum, unit, default, header or referenced type definition.

+

Historical facts are independently acknowledged observations. They need not +compose one coherent former model: two bounds can have different review times. +They MAY outlive the endpoint's former record family, constructor and referenced +objects. Historical admission checks their closed payload types, finite values, +text, keys and uniqueness; it MUST NOT require a currently existing referenced +object or current family applicability. In contrast, a new graphical or planned +resolution MUST address the appropriate current record family and have at least +one currently applicable side. Both-side NotApplicable facts are invalid persisted +acknowledgements. A grouped constructor decision retires such obsolete branch facts.

+

The following table is the exhaustive scalar catalog. T? denotes an optional +T, T[] a list, and Id a historical UUID. Existing enum and typed payload +definitions in sections 3–4 apply. Every listed leaf wraps { oa ...; sa ...; }.

+ + ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Closed scalar semantic facets

Family

Leaves and observation value types

Header

Name, Description: string; Parent: Id?

ExchangeItem

Nature: ExchangeItemKind; Mechanism: ExchangeMechanism; Supertype: Id?

Interface

Supertype: Id?

Data

Constructor: DataConstructor; IntegerMinimum, IntegerMaximum: i64?; RealMinimum, RealMaximum: finite f64?; StringMinimumLength: u32; StringMaximumLength: u32?; RecordSupertype: Id?; Default: DataValue?; Unit: string

Field

Type: TypeRef; Minimum: u32; Maximum: u32?; Unit: string; Direction: ParameterDirection; Storage: FieldStorage; Default: DataValue[]?

Port

Constructor: PortConstructor; ComponentKind: ComponentPortKind; Orientation: ComponentPortOrientation; LegacyDirection: PortDirection; LegacyInterface: Id?

Scenario

Viewpoint: ScenarioViewpoint; Context: Id?; Precondition, Postcondition: string

Lifeline

Represented: Id?

Occurrence

Constructor: OccurrenceConstructor; Precondition, Postcondition: string; Exchange, Sender, Receiver: Id?; MessageKind: MessageConstructor; TimerExpression: string; ReplyTo, Action, ActionLifeline: Id?; ControlKind: ControlKind; Sequence, Scenario, State, StateLifeline: Id?; FragmentOperator: FragmentOperator; BranchGuard: string

Event

Constructor: EventConstructor; TimeKind: TimeEventKind; TimeExpression, ChangeExpression: string

State

Kind: StateKind; Entry, Exit, DoActivity: string

Constraint

Expression: string

Relation

Source, Target: Id; AllocationOrder: u32; Transmission: TransmissionProtocol; Acquisition: AcquisitionProtocol; SourceRole, TargetRole: string; SourceMinimum, TargetMinimum: u32; SourceMaximum, TargetMaximum: u32?; SourceNavigable, TargetNavigable: bool; SourcePort, TargetPort: Id; Oriented: bool; ExtendCondition, SequenceGuard: string; TransitionKind: TransitionKind; Trigger, Guard, Effect: string; StateActionPhase: StateActionKind

+
+
+

5.2. Members, rows, order, and attached records

+

The remaining facet forms are closed records. keys means { oa key-or-none; sa key-or-none; }; at least one key MUST be present. review means the same +required OA/SA observation pair used above.

+ + +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Closed keyed and ordered semantic facets

Outer tag

Required fields

Key / reviewed value type

Member

collection; keys; review

Id / bool

Order

collection; review

No keys / Id[]

Literal

keys; review

Exact case-sensitive string / bool

Annotation

keys; review

{ path Id[]; field Id; } / MessageValue?

Binding

keys; review

Referenced lifeline Id / LifelineBinding?

Child

keys; review

Direct child Id / bool

Incident

keys; end; review

Relation Id, end Source or Target / bool

Artifact

keys; review

Artifact Id / nested artifact facet below

+

Member.collection is exactly Steps, FragmentBranches, FunctionInputItems, +FunctionOutputItems, SignalItems, ExchangeItems, TransitionTriggers, +TransitionEffects or Constrained. Order.collection is exactly Steps or +FragmentBranches. Order is independent of membership and MUST contain each +surviving member exactly once when applied. Membership Keep does not refresh +order history. An automatic insertion requires a unique unchanged mapped anchor; +ambiguous insertion or conflicting inferred orders requires an explicit final +target order. Independent removals can filter the order to surviving members.

+

Annotation identity is the complete (path, field) pair. Its MessageValue +payload contains path, field and expression; the payload key MUST +match its side's selector key. Binding payloads contain referenced and +local; referenced MUST match that side's key. Each row is one atomic +fact. Changing its key is an explicit old-row removal/new-row addition, not an +expression-only edit. Defaults containing repeated DataValues are atomic optional +vectors because those values have no stable member identity.

+

Artifact.review is { Leaf { oa observation; sa observation; }; } where Leaf +is Presence: bool; Name, Description or Uri: string; Owner: Id; +Relation: ArtifactRelation; Revision or Fragment: string?. Metadata decisions +are authorized on the artifact's attachment in the initial project. An explicit +Owner candidate may move a target-only link and record its resulting Owner and +other selected metadata. Presence remains relative to the trace owner. Subsequent +metadata decisions require its new owner trace; historical facts remain attached +to the trace that recorded them. No review fetches artifact content.

+

Duplicate scalar selectors and keyed selectors that occupy the same OA slot or +the same SA slot are invalid, even when the other side's key differs. Historical +order vectors MUST NOT repeat an Id. Review history has a limit of 16384 facets, +128 Ids in an annotation path, 1000000 bytes per string, and 1000000 traversal +steps per baseline. Exceeding these implementation work limits produces a +diagnostic; truncation MUST NOT be accepted as a complete review.

+
+
+

5.3. Constructor groups and decisions

+

Persisted constructors are discriminator words only:

+
    +
  • DataConstructor: Boolean, Integer, Real, String, Enumeration, Record.

  • +
  • PortConstructor: FunctionInput, FunctionOutput, Component, Legacy.

  • +
  • OccurrenceConstructor: Exchange, Action, Control, SequenceReference, +ScenarioUse, State, Fragment, Branch.

  • +
  • EventConstructor: Signal, Time, Change.

  • +
  • MessageConstructor: Asynchronous, Call, Reply, Create, Delete, Timer.

  • +
+

When a discriminator has no prior fact after explicit resets, both current sides +are applicable and equal, and the proposed target discriminator is unchanged, +KeepTarget, TakeSource or Candidate acknowledges only that discriminator. +It MUST NOT copy a constructor payload, acknowledge unselected descendants or +retire branch history. Descendant facets may be independently selected in the +same review; their decisions retain their own scopes and final-value audits. +Candidate records still MUST NOT change an unselected facet. Missing applicability +on either side does not qualify as an equal discriminator acknowledgement.

+

A grouped constructor resolution uses a transient typed target record, then +records the discriminator and each acknowledged descendant as separate facts. +It MUST NOT persist a second whole constructor payload overlapping those leaves. +Data includes its branch fields, literals and coupled default, but excludes Unit. +Port includes its contract, Legacy fields and item members. Event includes its +branch fields and Signal items. Occurrence includes its variant payload, members, +order, annotations and bindings, but excludes outer Precondition/Postcondition. +MessageKind includes its discriminator and dependent TimerExpression. State.Kind +is its own structural facet even though it changes the element's derived kind. +Other dependent changes caused by preparation remain explicit impacts.

+

A grouped constructor Keep requires an actual discriminator proposal or a +discriminator change since its prior review. Repeating Keep on an already reviewed +unequal pair MUST NOT acknowledge unrelated bound/default drift. A real target switch may +reconsider a previously kept difference. When the target already has the proposed +constructor, reconciliation MUST preserve target-only members rather than replace +its whole list. Removed branch facts participate in agreement as NotApplicable; +facts with neither side remaining applicable are retired.

+

Semantic review supports these explicit decisions in either direction:

+
    +
  • KeepTarget records the current source and retained target values for only the +selected facet, or the explicitly displayed constructor group.

  • +
  • TakeSource maps the selected source value through the frozen correspondence and +applies only that facet's delta. Unrelated target edits and target-only collection +members, rows, children and artifacts remain intact. Removing membership unlinks +the target member; it does not authorize deleting the target semantic object.

  • +
  • Candidate supplies final existing typed Element, Relation or Artifact records. +Only selected facets may differ from the result. Identity, alias and layer are +protected. New artifact creation requires a complete candidate and review of +every introduced metadata facet; removal also reviews the metadata retirement.

  • +
+

Headers have explicit semantic Name/Description facets; the original prose-only +workflow remains supported with its own baseline. Ordinary source editing can +explicitly edit a valid baseline; neither baseline format attests that a human +performed a particular review workflow. +Decision requests, final candidates and prepared impacts are transient editor/API +data. Source persists the resulting ordinary model and typed baseline facts; +it does not persist KeepTarget/TakeSource/Candidate as extra facet fields.

+
+
+

5.4. Correspondence, agreement, and atomic preparation

+

Correspondence is an explicit canonical OA-to-SA Id map, including historical +deleted IDs. Names and aliases MUST NOT guess correspondence. Reverse application +uses its inverse and rejects ambiguity. Multiple trace kinds for one endpoint +pair require an explicit trace selection. Changing a keyed pairing requires an +explicit reset naming the prior selector; old pairs are never silently replaced. +If a previously paired member or branch disappears on one side, retain its pair +with the absent/NotApplicable observation, suppressing a redundant one-sided +projection. A newly appearing pair still requires reset of old one-sided history.

+

All selected source proposals use one immutable initial project. Resets precede +catalog and constructor-scope derivation. Many sources may affect one target only +when their desired values agree on every actual canonical target slot, including +removals and members introduced by another proposal. Child membership and the +actual child's Parent share one slot; incidents share the relation's Source or +Target endpoint; artifact Presence and Owner share the actual artifact owner slot. +Conflicting desired values MUST reject independently of request order.

+

Materialize the agreed writes once, then audit each desired selected value. +Prepare one ordinary complete Command batch and compare its actual project with +the explicitly reviewed candidate. Additional cleanup, such as inherited message +annotation pruning, is reported as exact object before/after typed records +(or absence for creation/deletion). Every induced impact requires explicit review; +an accepted impact still MUST NOT contradict a selected Keep/Take. Repeat the +selected-value audit after preparation. Baseline updates belong to the same atomic +transaction and source-preserving writeback as model edits.

+

A plan's base fingerprint detects a stale model; its prepared fingerprint checks +candidate consistency. The plan is client-supplied data, not an authenticated +receipt or proof of its original decisions. Applying it validates the complete +ordinary candidate and preserves generated identities; it does not re-run its +request to generate new identities. Hosted clients additionally use the revision +preconditions defined in api.rst. Invalid/stale plans or failed source writeback +leave the accepted model, source, baseline, revision and history unchanged.

+

Own drift and linked impacts are separate, with incomplete review explicitly +visible. Absent facts MUST NOT produce a falsely complete Unchanged rollup. +Shared data/interface/event definitions are reviewed on their own traces; their +bounded derived impact walk follows reusable definitions and interface allocations, +not arbitrary architectural neighborhoods, and does not copy them into every +referrer's baseline. Its limits are 4096 objects per side and 32768 edges; exhaustion +is a diagnostic. Successful review remains one undoable transaction. Replanning +unchanged, already acknowledged leaf facts is a no-op only while their requests +remain applicable. A completed constructor switch or first acknowledgement of an +equal discriminator MUST be removed from subsequent requests; repeating it when +no switch remains is rejected. After an artifact moves to another owner, +metadata review through the old owner's trace is no longer +applicable and is rejected. An old plan becomes stale after an actual committed +change.

+
+
+

5.5. Source examples and historical token rules

+

This valid source records deliberately different constructor/unit observations. +Its historical integer minimum is absent; the Real side has no integer branch:

+
arcadia 7;
+project reviewed_dimensions "Reviewed dimensions" {
+    oa_data_type oa_dimension "Operational distance" {
+        definition { Integer { minimum 0; maximum 10; }; };
+        unit "m";
+    }
+    sa_data_type sa_dimension "System distance" {
+        definition { Real { minimum 0; maximum 20; }; };
+        unit "ft";
+    }
+    trace dimension_review "Dimension review" {
+        oa @oa_dimension;
+        sa @sa_dimension;
+        kind Item;
+        baseline {
+            oa { name "Approved OA name"; description ""; };
+            sa { name "Approved SA name"; description ""; };
+            semantic { facets [
+                { Data { Constructor { oa { Value Integer; }; sa { Value Real; }; }; }; },
+                { Data { IntegerMinimum { oa { Value none; }; sa NotApplicable; }; }; },
+                { Data { Unit { oa { Value "m"; }; sa { Value "ft"; }; }; }; }
+            ]; };
+        };
+    }
+}
+

Historical Id slots require uuid "..." even if the object still exists. +Only typed reference slots inside baseline.semantic accept that atom. +For example, the following is a valid historical facet value, including a deleted +named type; none and bare enum Boolean retain their different typed meanings:

+
{ Field { Type {
+    oa { Value { Named uuid "40000000-0000-4000-8000-000000000090"; }; };
+    sa { Value Boolean; };
+}; }; }
+

A qualified annotation uses the same historical UUID atoms in its key and payload. +This complete facet records one old row and an applicable absent target row:

+
{ Annotation {
+    keys {
+        oa {
+            path [uuid "40000000-0000-4000-8000-000000000091"];
+            field uuid "40000000-0000-4000-8000-000000000092";
+        };
+        sa none;
+    };
+    review {
+        oa { Value {
+            path [uuid "40000000-0000-4000-8000-000000000091"];
+            field uuid "40000000-0000-4000-8000-000000000092";
+            expression "latitude + 1";
+        }; };
+        sa { Value none; };
+    };
+}; }
+

Historical enums require bare words. Ordinary strings, including UUID-looking +units, literal keys and expressions, require quoted strings and remain text +during identity remapping. @alias is invalid anywhere inside a semantic +baseline. Live parents, trace endpoints and all other live references still +require @alias and MUST NOT accept the historical UUID atom.

+

The following substitutions in these examples are invalid:

+
    +
  • oa "NotApplicable"; or oa { Value "Integer"; }; in the Constructor +review: enum observations require bare words of the correct typed enum.

  • +
  • Named "40000000-0000-4000-8000-000000000090"; or Named @oa_dimension;: +a historical Id requires the explicit UUID atom.

  • +
  • Unit { oa { Value uuid "40000000-0000-4000-8000-000000000090"; }; ... }: +a text field cannot hold a reference atom.

  • +
  • Repeating the Unit facet, adding an unknown leaf, or supplying a numeric bound +as a quoted string: closed typed admission rejects the whole candidate.

  • +
  • Changing only the annotation payload's field UUID suffix from 092 to 093, +leaving the key at 092: the row's key and payload disagree.

  • +
  • Changing the document header to arcadia 4; while retaining semantic: +the frozen earlier schema does not admit a future baseline shape.

  • +
+
+
+
+

6. Text grammar and resolution

+

The input is UTF-8 without a byte-order mark. LF, CR, space, and tab separate +tokens. // starts a comment through the next LF or end of input. Quoted +strings use JSON string escaping, including \n, \r, \t, \\, +and Unicode escapes. Literal unescaped newlines inside strings are invalid. +Numbers use JSON decimal/exponent syntax; NaN, infinity, hexadecimal numbers, +leading plus signs, and leading zeroes are invalid.

+

A small model may begin with engineering declarations alone. This complete +source describes two participants, their work, responsibility and an exchange:

+
arcadia 7;
+project distress "Distress response" {
+    oa_actor vessel "Vessel in distress" {}
+    oa_entity rescue "Rescue coordination" {}
+    oa_activity report "Report distress" {}
+    oa_activity assess "Assess incident" {}
+    allocation vessel_reports "Crew reports" { source @vessel; target @report; }
+    allocation rescue_assesses "Team assesses" { source @rescue; target @assess; }
+    interaction distress_report "Distress report" { source @report; target @assess; }
+}
+

No diagram, placement, empty description or explicit root parent is required. +The accepted source adds identities; keep those clauses during later edits. +Changing "Assess incident" changes the display name while @assess still +resolves to the same activity. Behavior-coverage warnings permit this intermediate +model to be saved. Allocation remains explicit and distinct from decomposition.

+

Aliases match [A-Za-z_][A-Za-z0-9_]*. The following aliases are reserved: +arcadia, project, id, oa, sa, element, relation, diagram, node, artifact, +trace, body, alias, name, description, parent, from, to, entity, actor, +activity, capability, process, allocation, interaction, contribution, +realization, position, steps, true, false, null. Other field/constructor +spellings can be used in alias positions because references begin with @.

+

The grammar below uses * for repetition and ? for optional syntax. +element-kind and relation-kind are exactly the corresponding declaration +keywords listed above. Typed field +sets and enum constructors are closed by sections 3–5.

+
document    = "arcadia" "7" ";" project
+project     = "project" alias string identity? "{" project-entry* "}"
+project-entry = "description" string ";" | declaration
+declaration = element
+            | (relation-kind | "diagram" | "artifact" | "trace")
+              alias string identity? fields
+            | "node" alias identity? fields
+element     = element-kind alias string identity? element-body
+element-body = "{" (field-name value ";" | element)* "}"
+identity    = "id" uuid-string
+fields      = "{" (field-name value ";")* "}"
+value       = string | number | "true" | "false" | "none"
+            | enum-word | "@" alias | historical-id | list | fields
+historical-id = "uuid" uuid-string
+list        = "[" (value ("," value)*)? "]"
+

The project description defaults to empty and may appear once. There is no +semicolon after a declaration or project closing brace. Every field inside a +record ends with a semicolon, including fields whose values are nested records. +Trailing list commas are invalid. Fields may appear in any order.

+

UUID strings use 8-4-4-4-12 hexadecimal spelling, with uppercase or lowercase +hexadecimal accepted. Canonical output uses lowercase. Omitting an identity +creates a fresh UUID for that parse. After Apply, accepted source includes the +generated identity. Authors must keep identity clauses to preserve existing +objects across subsequent edits.

+

All declaration aliases are collected before references resolve, so forward +references are legal, including references to nodes and diagrams. Unknown +@alias values are errors at their reference occurrence. Quoted UUIDs and +bare words cannot substitute for reference-valued fields. Alias resolution +produces UUID references; the alias itself remains stored on its declaration. +Changing an alias in source requires updating its uses. Display names never +resolve references.

+

historical-id is context-restricted to the typed semantic baseline reference +slots in section 5.5; it is not an alternative spelling of a live reference.

+

Unknown fields, duplicate fields, incompatible constructor payloads, unsupported +enum spellings, duplicate aliases/identities, and structural validation errors +reject the entire candidate. No declaration silently replaces another. +Ordinary string fields require quoted strings and cannot contain an unquoted +@alias reference. Enum fields require unquoted named values or their typed +constructor records. Typed values MUST NOT nest more than 64 levels; malformed +deeply nested drafts produce a diagnostic instead of exhausting the parser stack.

+
+

6.1. Modular source projects

+

The pure source_project core API compiles a virtual source bundle, containing +entry and a files map from logical paths to UTF-8 strings. Its entry is a +root-level .arcadia file with the source7 project header above. A project entry +MAY also be include string ";". An included fragment consists only of ordinary +declarations and include statements; it MUST NOT contain another version header, +project wrapper, or project description. Includes MUST occur at document level, +never inside an element body or typed field value. For example:

+
arcadia 7;
+project rescue "Rescue" id "00000000-0000-4000-8000-000000000001" {
+    description "Operational model";
+    include "oa/participants.arcadia";
+    include "oa/activities/activity_respond.arcadia";
+    include "views/diagram_operational.arcadia";
+}
+

An include path is relative to its containing file. . and .. segments +normalize within the virtual project root; escaping that root is an error. Bundle +keys MUST already be normalized, use forward slashes and the lowercase +.arcadia suffix, and contain no empty path segments. Absolute, drive, UNC, +URL, backslash, control-character and non-portable Windows device paths are +rejected. Paths ending a segment in dot or space are rejected. Keys that collide +after Unicode lowercasing are rejected. References use the exact normalized +spelling; filesystems do not participate in resolution.

+

Every reachable file is compiled once, including through repeated or diamond +includes. Include cycles are errors with their chain and including location. +Missing files, duplicate JSON file keys and supplied unreachable files are +errors. The compiler MUST NOT read local files, network resources, environment +variables or the process working directory. The native loader is responsible +for translating a directory into this explicit bundle and enforcing physical +path ownership; the pure compiler grants no filesystem access.

+

inspect_includes(path, source, is_entry, limits) supports native discovery +before all included documents are available. It uses the same strict entry or +fragment syntax parser, applies document byte/record and local include-edge +budgets, and returns normalized targets with exact directive spans. Comments and +strings that mention include syntax are not directives. A syntax failure anywhere +in the document returns located errors and no partial include list; it does not +scan arbitrary token strings after failure. Global references and engineering +validation are deferred to bundle compilation. A native loader MUST retain the +unparsed draft on failure and enforce aggregate bytes/files/depth/edges while +loading; this inspection call does not own or inspect a directory.

+

All files share the existing global alias/UUID namespace. Files add no semantic +namespace, implicit parent, allocation or port contract. Forward references work +across files. Duplicate aliases and identities report the offending declaration +and the previous declaration location, including collisions with the project. +Moving a declaration between files MUST preserve the resulting model and UUIDs.

+

ProjectLimits adds file, include-depth and include-edge budgets to the existing +language::ParseLimits. Model bytes, graph objects, records and ordered +references are counted across the complete bundle, never reset per file. The +default candidate policy is 256 files, depth 32 (entry depth zero), 1,024 include +edges and 64 MiB combined UTF-8 source. Other model counts remain caller policy. +Repeated include edges count toward the edge budget; file contents count once. +The compiler admits input and stabilized output against the same limits.

+

compile_bundle returns either located errors and no candidate, or a +CompiledSourceProject with the validated DecodeResult, stabilized bundle, +derived SourceIndex and located engineering warnings. It inserts explicit +UUID clauses into headers that omitted them without otherwise reformatting the +input. The returned source index and diagnostics address the returned stabilized +bytes. Clients MUST retain that bundle after successful Apply; recompiling the +original buffers with missing identities would generate fresh identities.

+

Bundle diagnostics wrap the existing Diagnostic with a logical path and optional +related locations. Declaration spans, own-field value spans, lexical owners, +declaration bodies, project header/body/description spans and resolved includes are available in +the source index. Columns remain one-based Unicode scalars within the real file; +spans never refer to concatenated or synthetic source.

+

Single-string parse/lint APIs remain filesystem-free and reject include +statements with an explicit source-bundle requirement. V1/V2 single-file imports +continue through explicit migration. Modular projects accept source3 through +source7. Older bundles are upgraded as one project, preserving file paths, includes, nested +ownership and comments through targeted token edits. Generated allocation +declarations stay in their interface's existing file. Individual include fragments +are not independently migrated. Missing UUIDs stabilize before migration so new +allocation identities are deterministic on subsequent compilation. +An authored source4 bundle upgrades through the strict prose-only baseline +adapter; after any missing UUID stabilization, the version token alone changes +to 7. Existing prose, comments, includes and nested ownership remain intact. +Source5 semantic facet comments follow their closed selector identity through +row reordering and reidentification; UUID-looking text does not act as an Id.

+
+
+

6.2. Nested element ownership

+

An element body MAY contain element declarations interleaved with its ordinary +fields. Its immediate enclosing element is its lexical owner. For example:

+
sa_actor harbor "Harbor operator" {
+    sa_port radio "Radio" {
+        contract { Component { kind Standard; orientation Unset; }; };
+    }
+}
+

Nesting expresses semantic Element.parent only. It does not allocate a +function to an actor or System, deploy equipment, or add or reorder occurrence +references in an owner's explicit steps list. Those relationships retain +their explicit records and fields. The +same parent-kind, layer and acyclicity constraints apply as for flat source. +Only element declarations can nest: relations, diagrams, placements, artifacts, +traces and include directives remain at document level. A typed record value +contains fields, never element declarations.

+

If a nested declaration omits parent, it inherits its lexical owner's UUID. +An explicit parent MUST reference that owner; another reference or none +is an error located at the parent value. At document level, omitted parent +means none and explicit parent references retain their existing meaning. +Nesting creates no alias namespace. Aliases and UUIDs remain globally unique, +and references can target nested declarations before or after their definition +or from another included file. Identity stabilization inserts missing UUIDs in +nested headers just as it does in document-level headers.

+

Source positions distinguish each declaration's own fields from its descendants' +fields. A declaration's body span encloses its lexical children as well as its +own fields. An inherited parent has no synthetic source token or field span. +Located diagnostics and editor navigation MUST address real source bytes.

+
+
+
+

7. Canonical output and source tooling

+

For every structurally valid Project P, parsing its canonical output MUST +reproduce P, and canonicalizing that result MUST be idempotent:

+
parse(format(P)) == P
+format(parse(format(P))) == format(P)
+

Equality includes all persistent fields, stored aliases, body variants, +references, placements, artifacts, trace baselines, and ordered occurrences. +Map order is irrelevant. The writer preserves signed coordinate zero and +emits decimals that recover the original binary32 value.

+

Canonical output uses LF, four-space indentation, explicit identities, +defaults, and JSON-escaped strings. Elements are emitted in UUID order, then +relations; diagrams and their nodes follow, then artifacts, then traces. +Fields use deterministic name order. Output ends with LF. Alias values come +from the model, never from regenerating display names.

+

Comments and input whitespace are lexical trivia to the canonical writer. +Canonical export does not preserve them. The source-project editor and compiler +preserve authored comments and whitespace on Apply while inserting missing +identities. Explicit canonical export remains a separate operation. Project +update/export distinctions are described below. +Camera position, selection, source cursor, and undo history are not stored +model fields. Layout, collapse state, and manual port anchors inside +NodePlacement are model data.

+

The pure format_bundle exporter uses the same typed declaration writer as +format. It creates a small entry file, layer/category modules, separate +activity/function roots and behavior contexts, and separate diagrams with their +placements. Ports follow their owner; relations follow their source/context. +Module names derive from persistent aliases, not display names or UUID chunks. +Declarations are ordered by semantic ancestry and alias, with owners before +descendants. Case-colliding generated file names receive deterministic numeric +suffixes. When more than 240 distinct modules would be produced, aggregate +folders consolidate into category modules to avoid thousands of tiny files. +For every valid P, compile_bundle(format_bundle(P)).decoded.project == P +when the caller supplies sufficient admission budgets.

+

update_bundle(compiled, P, limits) is a core source update facility. It +checks that the compiled document/index/model snapshot still agrees, preserves +untouched document bytes, and recompiles the result before returning it. Flat +declarations retain their document-level position and file when reparented. +Nested declarations move into their new semantic owner's body, across files if +necessary; becoming a root moves them to document level. New children prefer +their owner's body. New roots use canonical module rules and add an explicit +entry include. +Removed declarations do not delete source files. Alias changes also update +declarations containing references to that alias. Invalid or stale updates +return errors without mutating the input workspace.

+

A moved declaration carries its contiguous leading comment block and comments +following its closing brace on the same line. A blank line separates a comment +block from the following declaration; such gap comments stay in the enclosing +scope. Comments trailing a preceding field likewise stay with that field. +Deletion retains annotations in the nearest surviving original scope and +separates them from adjacent declarations with blank lines. Surviving descendants +move with an enclosing declaration unless their own semantic parent changes; +independent parent and child moves are resolved together. Leading whitespace +adjusts to the destination scope; other token spelling remains authored.

+

Updates MUST NOT canonicalize surviving declarations in full. Header, field and +nested value positions are derived from the existing syntax parser. Unchanged +fields retain their exact bytes, including field order, omitted defaults, number +and string spelling, comments, indentation and line endings. Changed literals and +references replace only the required tokens; a reference with comments between +@ and its alias retains those comments. A previously omitted default is +inserted only if its semantic value changes. New fields use the existing record's +indentation and line-ending style without reformatting its other fields.

+

A constructor tag change retains shared nested fields in place. Removed fields, +constructors and declarations remove syntax tokens, retaining their comments +and whitespace as annotations in the enclosing record or file. A complete value +shape replacement likewise retains the old value's comments beside the new value. +The updater does not invent an engineering interpretation for orphaned comments.

+

List entries use stable referenced UUIDs, message-value field references or +lifeline-binding referenced identities when these keys are unique. Literal +entries use their literal value. Retained entries carry their authored value +text and inter-separator comments when reordered. Trivia after a preceding comma +belongs to the following entry; trivia before the next comma belongs to the +preceding entry. Separators are reconstructed before trailing line comments so +that the result remains valid syntax. Unkeyed or duplicate-key lists use source +positions. Removed-entry comments remain as annotations in the list; newly added +entries receive formatted syntax. These rules preserve comments without silently +attaching new semantic identity to an ambiguous list entry.

+

Retained declaration UUIDs remain unchanged unless explicitly edited. Source +ownership remains unchanged except for the nested relocation rules above. +Records match by UUID, not display name or alias. The model-only update_bundle treats an +explicit UUID replacement as removal and creation. The desktop instead uses +update_bundle_with_identity_map with the surviving-record provenance from +prepare_command: explicit identity edits and sequential identity swaps MUST +preserve each original declaration's owning file and attached source trivia, +unless the same edit changes its nested semantic parent and requires relocation. +New records have no original identity mapping. The mapping MUST be injective, +refer to existing records and preserve record classes. The +result is recompiled, reindexed and checked for equality with the requested +model before return. Equal model values do not imply a no-op if command +provenance removes or replaces a declaration; the resulting model and source +bundle together determine whether the accepted state changed. +Source changes needed for undo are workspace history data; +callers should retain complete compiled workspace snapshots rather than infer +past omission/layout choices from a model-only undo.

+

Canonical modular export is a distinct, explicit operation and does not promise +original comment/layout preservation. The native editor integrates per-file +drafts and source-project saves as specified below. The pure compiler performs +no filesystem access. REST source-project endpoints exchange the accepted +bundle with all its files. One revision and ETag protect the model and source +together, including source-only comment edits; stored envelopes contain both. +Canonical source endpoints remain explicit model interchange. The precise +transport, admission and persistence contract is in api.rst.

+

parse returns a complete structurally validated candidate or error +diagnostics and no model. Engineering warnings alone MUST NOT reject a valid +candidate. Parsing may stop at the first syntax/conversion error. Diagnostics +contain severity (Error or Warning), a code identifying the diagnostic category, +optional object UUID and field path, a message, one-based line and Unicode-scalar +column, and a half-open UTF-8 byte Span. Tabs count as one scalar; LF advances +the line. Structural errors and engineering warnings point to a relevant +declaration or field when available. Clients MUST use severity/code rather than +parse the human message; diagnostic wording is not a stable machine API.

+

Explicit behavior coverage diagnostics

+

The following shared engineering checks are advisory Warning diagnostics. +They MUST NOT reject graphical Apply, source compilation, Save, or an API +transaction solely because the model is incomplete. They run only after the +structural pass reports no Error, avoiding cascades from invalid ownership, +references, layers, or recursion. Their policy concerns explicit evidence in +the authored model, not proof that no engineering behavior exists.

+
    +
  • non_leaf_work_exchange locates each source or target of an +Interaction or FunctionalExchange that has direct same-kind child work. +Repair the exchange to name its responsible leaf Activity/Function, keeping +SA function-port ownership consistent.

  • +
  • work_without_behavior_context locates a leaf Activity/Function lacking +an included Action or an included Interaction/FunctionalExchange occurrence +that explicitly names it as an endpoint.

  • +
  • exchange_without_behavior_context locates an Interaction or +FunctionalExchange lacking an included Exchange occurrence. It is suppressed +while a more specific non-leaf endpoint warning applies to that exchange.

  • +
  • mission_without_capability locates an OperationalMission/Mission lacking +a valid same-layer outgoing MissionCapability. Participant responsibility +through MissionInvolvement does not satisfy this check.

  • +
  • capability_without_behavior_context locates a supported leaf +Capability/SystemCapability lacking either an incoming ChainInvolvement or +a same-layer Scenario explicitly naming it as context. It MUST NOT +duplicate unsupported_capability on the same record.

  • +
+

Record-level checks have no invented field path. Coverage traverses all Process, +FunctionalChain and Scenario roots, following only their explicitly listed, +correctly owned occurrences and Fragment branches. Reusable sequence/scenario +contents are traversed with identity-based cycle guards. Known work/exchange +references count despite unfinished lifelines; unordered occurrences do not. +Repeated exchanges and correlated replies contribute the same identity once. +Parent work does not cover its children. Bare lifelines, state observations, +StateAction/Availability links, timer-only occurrences, and participant/boundary +messages do not imply work/exchange coverage. Allocation does not imply that +every exchange carried by a boundary message occurs. Diagram layout is irrelevant.

+

As an explicit Studio completeness policy, same-kind capability grouping parents +are excluded from both capability support warnings; their leaf capabilities are +checked independently. A valid direct Scenario context also satisfies existing +unsupported_capability support. An empty linked process/chain or scenario +satisfies explicit context presence while retaining its own empty_sequence +warning. Include/extend/generalization, parent containment, and shared performers +MUST NOT manufacture a context. These advisory checks follow the distinction +between dataflow and its usage contexts described in the official +Arcadia Questions and Answers; +they are not a declaration of full Capella conformance.

+

parse_limited and decode_limited accept caller admission budgets for +input bytes, graph objects, total persistent records and ordered step references. +Counts MUST be checked before expensive structural validation; migration budgets +include generated records before migration. Exceeding a budget does not mutate +the model. Admission limits are host capacity policy, not hidden restrictions +on otherwise valid language semantics; ordinary parse has no such model +size policy. The REST service's fixed policy is specified in api.rst.

+

tokenize is tolerant: it covers every input byte with nonoverlapping +TokenSpans, including incomplete strings and invalid characters. Token classes +are Keyword, Identifier, String, Number, Comment, Punctuation, Whitespace, +and Error. @ is punctuation followed by the referenced alias token. +lint returns syntax, structural and engineering warning diagnostics for the +current draft without adopting a model. A valid unallocated SA function, for +example, can report missing System and allocation warnings before Apply. +analyze +combines tokens, diagnostics, and declaration spans with alias, optional +explicit UUID, and declaration kind. A malformed later declaration does not +erase earlier declaration spans. None of these APIs mutates a session.

+
+
+

8. Graphical parity and transactional editing

+

The graphical editor MUST expose all persistent properties through typed +controls, including optional references, lists, variant constructors, aliases, +IDs, nested parent changes, diagram placements, artifact metadata, and trace +baselines. Inspector controls may provide operations unavailable as a canvas +gesture. Exact strings, including tabs and line breaks in names, must remain +editable through an appropriate escaped-string input when ordinary widgets +cannot represent them.

+

A move into a functional parent, an allocation onto a performer, and a diagram +placement move are different transactions. The UI MUST identify which operation +will occur. Reparenting work must not silently change its performer. A computed +parent summary must remain distinguishable from direct allocation. A collapse +operation hides presentation detail without deleting the children.

+

Applying a reachable source bundle is one model transaction. Graphical edits +update its owning declarations through the source-preserving updater. Both +paths validate their candidate and model/source equality before +adoption; failure preserves the committed model, save baseline, and history. +Incomplete but structurally safe references and coverage gaps produce warnings +that allow continued authoring and saving.

+

Source and property drafts contribute to unsaved-work state. Invalid drafts +MUST NOT be replaced by generated text or lost on ordinary navigation. Undoing +source-buffer edits and undoing committed model changes are distinct operations. +Successful source Apply creates one model-history entry. Undo/redo restores +the full stored model and exact accepted source bundle, including aliases, +trace links, occurrence identity, comments and file ownership. Each open file +retains its own unapplied draft across file navigation. Located lint diagnostics +refer to the raw draft bytes rather than an identity-stabilized intermediate. +Property editing MUST NOT silently discard a pending source draft.

+

In Textual model, place the caret on a declaration alias or an @reference +and choose Definition (F12) or References (Shift+F12). +Back (Alt+Left) returns to the previous source location in this +workspace. These shortcuts act while the source editor has focus; the Text +toolbar also exposes the actions. If the previous draft changed, Back restores +its current cursor rather than reusing an obsolete selection.

+

Symbol navigation reads bounded snapshots of the currently loaded source +drafts. It does not apply drafts or load missing included files. The results +identify partial scope, draft evidence, detached or unverified files, alias +ambiguity and authored-ID collisions. Draft, detached and ambiguous definition +candidates require an explicit row choice. References are lexical uses of an +alias, not a list of resolved typed relationships; comments and strings are +excluded. Declaration and use counts include any rows omitted by the result +limits. If sources change, stale rows cannot navigate: run the query again. +Navigation MUST preserve the accepted model and every source draft, and select +locations in the original UTF-8 bytes. Displayed locations use one-based lines +and UTF-8 byte columns.

+

Changing a graphical UUID must atomically update map keys and every reference. +Alias changes preserve UUID identity. Deletion must account for owned elements, +incident relationships, ordered occurrences, and diagram placements without +leaving dangling references. Deleting a sequence does not delete the semantic +exchanges it references. Cross-layer counterparts are not owned descendants. +Text edits describe a complete candidate and must remove or repair dependencies +explicitly before Apply succeeds.

+
+
+

9. Persistence and explicit migration

+

Loading detects JSON from its first non-whitespace {; other input is parsed +as Arcadia source regardless of filename. Saving an .arcadia suffix uses +the accepted V7 source bundle (arcadia 7); other suffixes use V7 JSON. Both forms preserve the same +complete model. JSON rejects unknown fields throughout typed payloads, +duplicate maps, unsupported versions, and structural errors.

+

The explicit schema-4 reader retains the exact old Project/Trace/TraceBaseline +envelope with closed fields. It reuses unchanged typed elements, relations, +diagrams and artifacts. Version4 prose baselines migrate with semantic none; +their names/descriptions remain exact even when live endpoint values differ. +The adapter MUST NOT infer semantic review from live values, and version3/4 +inputs MUST reject semantic baseline fields. The migration report retains the +original from_schema and current to_schema 7. Reopening a current7 save produces +no migration report. The real V4 reviewed-prose fixtures remain compatibility input.

+

The schema-3 reader freezes the changed data payloads and old constructor catalog; +schema-4 fields/constructors MUST NOT be silently accepted as schema 3. Existing +typed PortContract records remain unchanged. Information/Material/Energy retain +their nature with Unspecified mechanism; old Event becomes Information with Event +mechanism. Old bounds/defaults/supertypes are absent, String length is 0..unbounded, +and fields retain type/multiplicity/unit with Unspecified direction, Value storage +and no explicit default. No physical mechanism or direction is guessed.

+

Each old Interface.items entry generates one InterfaceItemAllocation with its +original zero-based order and Unspecified protocols. UUID v5 uses the project UUID +namespace and name v2-interface-item:<interface-uuid>:<item-uuid> consistently +for schema-2 and schema-3 inputs. Aliases are deterministic and collision-free; +collision with any existing identity is an error rather than an overwrite. Existing +interface/item identities remain intact. Old nested message values acquire a path +only when exactly one valid path exists. Ambiguous paths, including recursive +reuse, retain the old annotation with a located warning rather than guessing.

+

Migration reports additionally contain semantic_defaults (object, field, +description), generated_interface_allocations (id, source, target, alias, +order) and unresolved_message_values (occurrence, field, reason). These new +report arrays default empty when reading older responses. Version 1 normalizes +through V2, then the typed-port V3 adapter, shared V4 data upgrade and V5 review +adapter. Original V2 and real V3 fixtures remain unchanged compatibility inputs. Current exports +always use version 7; reading/migrating alone never saves or changes revisions.

+

Versions 1–6 MUST reject ContextualLink, operation fields (including explicit +null/default values) and their new typed semantic facets. Current semantic review +adds Occurrence.Operation and Relation.ContextOwner/ContextSource/ContextTarget/ +ContextPurpose. Historical endpoints and operation selections remain typed +historical UUIDs, never live graph references or inferred current facts. +Version 5/6 migration preserves old Call/Reply/values, direct SequenceLinks, +traces, and version-appropriate view selectors without guessing operation intent +or contextual links. An authored source upgrade changes only the version token +until an actual model edit requires further writeback.

+

Desktop recovery MUST verify old 5/6 checkpoint fingerprints with the exact +historical serializer and closed old-version admission before accepting the +migrated model. It retains invalid raw buffers and authored review decisions; +transient impact approvals are reset and plans are recomputed against the +accepted base. A saved unchecked executable plan/command MUST NOT be restored.

+

Native source Save All checks the original contents of observed dependencies +before staging writes. Existing target files are replaced individually and the +entry is installed last. A transaction journal records originals and intended +writes; explicit recovery restores originals only when current bytes match a +known transaction state. Unrelated external contents MUST NOT be overwritten +by recovery. Missing entry files and lint updates MUST NOT hide a detected +pending-recovery action. This is not cross-process compare-and-swap or atomic +multi-file visibility. Save failures retain the accepted model and drafts. +Detached dirty buffers MUST be reattached or reverted before source saving; +JSON export does not replace the source project's saved baseline or location.

+

The frozen schema-2 reader retains staged compatibility defaults. Old Scenario bodies without a +viewpoint retain MixedLegacy, and old Exchange occurrence bodies without +message_kind, reply_to or values retain Asynchronous, none and []. +Old StateTransition bodies without transition_kind, triggers or +effects retain External, [] and []; existing trigger, guard and +effect strings are preserved exactly. Source declaration defaults and JSON +compatibility defaults MUST agree; no event or work reference is inferred from +legacy prose. +This compatibility applies equally to source and JSON. It adds explicit +defaults without changing any prior identity, name, reference, order, geometry, +artifact or trace field. The original V2 golden source/JSON in +crates/oa-core/tests/fixtures MUST NOT be regenerated from the newer demo; +they verify that every old persistent field survives. Canonical output writes +the added defaults through explicit migration to the current schema 7.

+

The V2 adapter preserves every original identity, alias, prose, parent, +placement, trace and artifact. Generic ports become Legacy with their original +direction/interface record. Existing FE endpoint IDs remain unchanged. For +only a missing endpoint it creates an item-bearing FunctionOutput/Input owned +by the already specified function, using UUID v5 in the project namespace +with v2-fe-port:<fe-id>:source or v2-fe-port:<fe-id>:target. Every +identity category is checked for collision; collisions are located errors, +never overwrites. New aliases follow the deterministic collision-free alias +convention. Every old boundary exchange receives oriented true; no +PortAllocation is inferred.

+

decode_with_report, load_with_report and their limited/source variants +return the model plus a migration report containing original unresolved port +IDs, required resolutions, generated endpoint IDs/aliases/owners, and boundary +orientation defaults. REST import, replacement, candidate validation and +project snapshots surface the same report. Opening an old server envelope +preserves its resource ID/revision and bytes; the next explicit edit writes +schema 7. Model/source exports contain only the canonical model. Migration +reports are transient response metadata, not persisted model facts.

+

Admission checks account for generated objects and their references before +construction. Both the old V2 contract and the resulting current graph are +validated. Migration itself does not save or increase a stored revision. +Canonical second saves are stable. V1 normalization produces the frozen V2 +model and then uses the same explicit adapters to reach schema 7.

+

Save validates a snapshot, writes a sibling temporary file, flushes it, and +atomically replaces the destination. Failed serialization or validation must +not truncate a previous file. External concurrent-writer conflict resolution +and crash recovery are outside this storage contract.

+

Version 1 text and JSON are accepted through an explicit adapter. Migration +preserves project, element, and relation UUIDs; names and descriptions; relation +endpoints; all coordinates; and exact process exchange order and repetition. +Allocation and Interaction remain their corresponding V2 relations. +Contribution becomes CapabilityInvolvement; the old Process-to-Capability +Realization becomes ChainInvolvement, retaining its ID and name.

+

Legacy positions become nodes in one OA Overview diagram. Each old process +step becomes a distinct occurrence element owned by its process. Generated +diagram, node, and occurrence identities use UUID v5 with the old Project ID +as namespace and deterministic role/index names. If such an identity collides, +a deterministic suffix is tried. Reimporting the same fully identified V1 +input therefore yields the same V5 result, including repeated occurrences. +No System or SA content is invented during an OA-only migration.

+

V1 source aliases are retained when valid in V2. JSON imports generate the old +canonical aliases deterministically. Any alias newly reserved in V2 is changed +by a deterministic suffix; aliases were not persistent V1 semantic data. +A project alias and aliases for new records are generated without colliding +with retained aliases. The existing bundled V1 text and JSON migrate to equal +V5 projects. Future versions remain errors until a separate migration exists.

+
+
+

10. Complete authoring example

+

This document intentionally omits IDs for new authoring. Apply and Save write +the assigned UUIDs explicitly.

+
arcadia 7;
+project harbor "Harbor assistance" {
+    oa_entity control "Harbor control" {}
+    oa_activity assess "Assess assistance" {}
+    allocation performs "Performs" {
+        source @control;
+        target @assess;
+    }
+    sa_system service "Harbor assistance system" {}
+    sa_function evaluate "Evaluate assistance request" {}
+    allocation system_performs "Performs" {
+        source @service;
+        target @evaluate;
+    }
+    trace assessment_trace "Operational realization" {
+        oa @assess;
+        sa @evaluate;
+        kind Refinement;
+    }
+    diagram operational_view "Operational allocation" {
+        layer Oa;
+        projection Allocation;
+    }
+    node assessment_card {
+        diagram @operational_view;
+        element @assess;
+        position { x 360; y 70; };
+    }
+}
+
+
+

11. Verification and primary references

+

cargo test -p oa-core --test language --locked verifies typed round-trips, +stable aliases, strict syntax/reference failures, Unicode source spans, +deterministic V1 migration, occurrence identity, coordinate bits, persistence, +and model-history integration. Core validation and session tests cover domain +mutation rules; native UI and API verification are separate evidence.

+

cargo test -p oa-core --test r3_semantic --test r3_source --test r3_migration --locked +checks the closed facet catalog, partial decisions, constructor scopes, canonical +agreement, target-only preservation, historic references, exact prepared impacts, +source/JSON parity, comment ownership and explicit original1–4 migration. The +source suite includes actual included-file save/reopen and prepared review +writeback. These checks establish core/source behavior; they do not establish +graphical or hosted API acceptance by themselves.

+

The research record in docs/research-v2-semantics.md distinguishes method +requirements from Studio design choices and links to original UI evidence. +The following sources inform this language without extending its scope:

+ +
+
+ + 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 + } + } + } +} diff --git a/harness/fixtures/rest-coverage/coverage-probe.json b/harness/fixtures/rest-coverage/coverage-probe.json index 8fe6f6d9..1953a028 100644 --- a/harness/fixtures/rest-coverage/coverage-probe.json +++ b/harness/fixtures/rest-coverage/coverage-probe.json @@ -1,58 +1,58 @@ -{ - "incomplete": { - "entry": "project.arcadia", - "files": { - "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @activity_group; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", - "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}", - "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" - } - }, - "repaired": { - "entry": "project.arcadia", - "files": { - "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @a; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", - "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ca_use, @ab_use, @pc_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence ca_use \"Use verified service\" id \"f0660906-0000-4000-8000-000000000032\" {\n parent @delivery; body { Exchange { exchange @eca; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence pc_use \"Use responsible leaf exchange\" id \"f0660906-0000-4000-8000-000000000033\" {\n parent @delivery; body { Exchange { exchange @ep; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\nmission_capability mission_delivery \"Mission delivery\" id \"f0660906-0000-4000-8000-000000000061\" { source @m; target @q; }\nchain_involvement context \"Explicit delivery context\" id \"f0660906-0000-4000-8000-000000000062\" { source @delivery; target @q; }", - "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" - } - }, - "invalid": { - "entry": "project.arcadia", - "files": { - "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @crew; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", - "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}", - "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" - } - }, - "expected": [ - { - "object_id": "f0660906-0000-4000-8000-000000000051", - "path": "work.arcadia", - "code": "non_leaf_work_exchange", - "field": "source" - }, - { - "object_id": "f0660906-0000-4000-8000-000000000013", - "path": "work.arcadia", - "code": "work_without_behavior_context", - "field": null - }, - { - "object_id": "f0660906-0000-4000-8000-000000000052", - "path": "work.arcadia", - "code": "exchange_without_behavior_context", - "field": null - }, - { - "object_id": "f0660906-0000-4000-8000-000000000021", - "path": "work.arcadia", - "code": "mission_without_capability", - "field": null - }, - { - "object_id": "f0660906-0000-4000-8000-000000000020", - "path": "work.arcadia", - "code": "capability_without_behavior_context", - "field": null - } - ] +{ + "incomplete": { + "entry": "project.arcadia", + "files": { + "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @activity_group; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", + "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}", + "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" + } + }, + "repaired": { + "entry": "project.arcadia", + "files": { + "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @a; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", + "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ca_use, @ab_use, @pc_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence ca_use \"Use verified service\" id \"f0660906-0000-4000-8000-000000000032\" {\n parent @delivery; body { Exchange { exchange @eca; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence pc_use \"Use responsible leaf exchange\" id \"f0660906-0000-4000-8000-000000000033\" {\n parent @delivery; body { Exchange { exchange @ep; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\nmission_capability mission_delivery \"Mission delivery\" id \"f0660906-0000-4000-8000-000000000061\" { source @m; target @q; }\nchain_involvement context \"Explicit delivery context\" id \"f0660906-0000-4000-8000-000000000062\" { source @delivery; target @q; }", + "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" + } + }, + "invalid": { + "entry": "project.arcadia", + "files": { + "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @crew; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", + "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}", + "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" + } + }, + "expected": [ + { + "object_id": "f0660906-0000-4000-8000-000000000051", + "path": "work.arcadia", + "code": "non_leaf_work_exchange", + "field": "source" + }, + { + "object_id": "f0660906-0000-4000-8000-000000000013", + "path": "work.arcadia", + "code": "work_without_behavior_context", + "field": null + }, + { + "object_id": "f0660906-0000-4000-8000-000000000052", + "path": "work.arcadia", + "code": "exchange_without_behavior_context", + "field": null + }, + { + "object_id": "f0660906-0000-4000-8000-000000000021", + "path": "work.arcadia", + "code": "mission_without_capability", + "field": null + }, + { + "object_id": "f0660906-0000-4000-8000-000000000020", + "path": "work.arcadia", + "code": "capability_without_behavior_context", + "field": null + } + ] } \ No newline at end of file