Preserve exact navigation prerelease GUI inputs
This commit is contained in:
@@ -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/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/package*.log whitespace=-blank-at-eof,cr-at-eol
|
||||||
harness/experiments/contextual-prerelease-20260911/producer/source-push.log -whitespace
|
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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1365
-1365
File diff suppressed because it is too large
Load Diff
@@ -1,147 +1,147 @@
|
|||||||
use super::artifacts::{Editor, Event, Raw};
|
use super::artifacts::{Editor, Event, Raw};
|
||||||
use super::*;
|
use super::*;
|
||||||
use iced::{
|
use iced::{
|
||||||
Element as View, Fill,
|
Element as View, Fill,
|
||||||
widget::{button, checkbox, column, pick_list, row, text, text_editor, text_input},
|
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> {
|
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);
|
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 [
|
for (layer, label, selected) in [
|
||||||
(Layer::Oa, "Operational", editor.oa.clone()),
|
(Layer::Oa, "Operational", editor.oa.clone()),
|
||||||
(Layer::Sa, "System", editor.sa.clone()),
|
(Layer::Sa, "System", editor.sa.clone()),
|
||||||
] {
|
] {
|
||||||
content = content.push(text(label).size(12)).push(
|
content = content.push(text(label).size(12)).push(
|
||||||
row![
|
row![
|
||||||
pick_list(editor.choices(p, trace, layer), selected, move |v| {
|
pick_list(editor.choices(p, trace, layer), selected, move |v| {
|
||||||
Event::Pick(layer, Some(v))
|
Event::Pick(layer, Some(v))
|
||||||
})
|
})
|
||||||
.placeholder("Choose exact artifact identity")
|
.placeholder("Choose exact artifact identity")
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding(7),
|
.padding(7),
|
||||||
button("No identity")
|
button("No identity")
|
||||||
.padding(7)
|
.padding(7)
|
||||||
.on_press(Event::Pick(layer, None))
|
.on_press(Event::Pick(layer, None))
|
||||||
]
|
]
|
||||||
.spacing(7),
|
.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()
|
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>(
|
pub(super) fn candidate<'a>(
|
||||||
editor: &'a Editor,
|
editor: &'a Editor,
|
||||||
p: &'a Project,
|
p: &'a Project,
|
||||||
facet: &'a FacetSelector,
|
facet: &'a FacetSelector,
|
||||||
) -> Option<View<'a, Event>> {
|
) -> Option<View<'a, Event>> {
|
||||||
let value = editor.drafts.get(facet)?;
|
let value = editor.drafts.get(facet)?;
|
||||||
let (_, which) = artifacts::parts(facet)?;
|
let (_, which) = artifacts::parts(facet)?;
|
||||||
let mut content =
|
let mut content =
|
||||||
column![text(format!("Final artifact {}", artifacts::field(which)?)).size(12)].spacing(7);
|
column![text(format!("Final artifact {}", artifacts::field(which)?)).size(12)].spacing(7);
|
||||||
match value {
|
match value {
|
||||||
Raw::Text(_) if which == ArtifactFacet::Description => {
|
Raw::Text(_) if which == ArtifactFacet::Description => {
|
||||||
let draft = editor.descriptions.get(facet)?;
|
let draft = editor.descriptions.get(facet)?;
|
||||||
let facet = facet.clone();
|
let facet = facet.clone();
|
||||||
content = content.push(
|
content = content.push(
|
||||||
text_editor(draft)
|
text_editor(draft)
|
||||||
.height(110)
|
.height(110)
|
||||||
.on_action(move |action| Event::Description(facet.clone(), action)),
|
.on_action(move |action| Event::Description(facet.clone(), action)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Raw::Text(value) => {
|
Raw::Text(value) => {
|
||||||
let facet = facet.clone();
|
let facet = facet.clone();
|
||||||
content = content.push(
|
content = content.push(
|
||||||
text_input("Exact text", value)
|
text_input("Exact text", value)
|
||||||
.on_input(move |value| Event::Input(facet.clone(), Raw::Text(value)))
|
.on_input(move |value| Event::Input(facet.clone(), Raw::Text(value)))
|
||||||
.padding(7),
|
.padding(7),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Raw::Owner(value) => {
|
Raw::Owner(value) => {
|
||||||
let mut choices: Vec<_> = p
|
let mut choices: Vec<_> = p
|
||||||
.identities()
|
.identities()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(id, alias)| Choice {
|
.map(|(id, alias)| Choice {
|
||||||
id,
|
id,
|
||||||
label: format!("{} · @{alias}", p.named(id)),
|
label: format!("{} · @{alias}", p.named(id)),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
choices.sort_by_cached_key(|c| c.label.to_lowercase());
|
choices.sort_by_cached_key(|c| c.label.to_lowercase());
|
||||||
let selected = value.parse::<Id>().ok().map(|id| view::choice(p, id));
|
let selected = value.parse::<Id>().ok().map(|id| view::choice(p, id));
|
||||||
let raw_facet = facet.clone();
|
let raw_facet = facet.clone();
|
||||||
let pick_facet = facet.clone();
|
let pick_facet = facet.clone();
|
||||||
content = content
|
content = content
|
||||||
.push(
|
.push(
|
||||||
pick_list(choices, selected, move |v: Choice| {
|
pick_list(choices, selected, move |v: Choice| {
|
||||||
Event::Input(pick_facet.clone(), Raw::Owner(v.id.to_string()))
|
Event::Input(pick_facet.clone(), Raw::Owner(v.id.to_string()))
|
||||||
})
|
})
|
||||||
.placeholder("Explicit final owner")
|
.placeholder("Explicit final owner")
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.padding(7),
|
.padding(7),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
text_input("Owner identity (retains rejected input)", value)
|
text_input("Owner identity (retains rejected input)", value)
|
||||||
.on_input(move |v| Event::Input(raw_facet.clone(), Raw::Owner(v)))
|
.on_input(move |v| Event::Input(raw_facet.clone(), Raw::Owner(v)))
|
||||||
.padding(7),
|
.padding(7),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Raw::Relation(value) => {
|
Raw::Relation(value) => {
|
||||||
let facet = facet.clone();
|
let facet = facet.clone();
|
||||||
let variants = [
|
let variants = [
|
||||||
ArtifactRelation::References,
|
ArtifactRelation::References,
|
||||||
ArtifactRelation::Satisfies,
|
ArtifactRelation::Satisfies,
|
||||||
ArtifactRelation::Verifies,
|
ArtifactRelation::Verifies,
|
||||||
ArtifactRelation::Implements,
|
ArtifactRelation::Implements,
|
||||||
ArtifactRelation::DerivedFrom,
|
ArtifactRelation::DerivedFrom,
|
||||||
];
|
];
|
||||||
let choices: Vec<_> = variants.into_iter().map(RelationChoice).collect();
|
let choices: Vec<_> = variants.into_iter().map(RelationChoice).collect();
|
||||||
content = content.push(
|
content = content.push(
|
||||||
pick_list(choices, Some(RelationChoice(*value)), move |v| {
|
pick_list(choices, Some(RelationChoice(*value)), move |v| {
|
||||||
Event::Input(facet.clone(), Raw::Relation(v.0))
|
Event::Input(facet.clone(), Raw::Relation(v.0))
|
||||||
})
|
})
|
||||||
.padding(7),
|
.padding(7),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Raw::Optional {
|
Raw::Optional {
|
||||||
present,
|
present,
|
||||||
text: value,
|
text: value,
|
||||||
} => {
|
} => {
|
||||||
let toggle_facet = facet.clone();
|
let toggle_facet = facet.clone();
|
||||||
let retained = value.clone();
|
let retained = value.clone();
|
||||||
let input_facet = facet.clone();
|
let input_facet = facet.clone();
|
||||||
let enabled = *present;
|
let enabled = *present;
|
||||||
content = content
|
content = content
|
||||||
.push(
|
.push(
|
||||||
checkbox(*present)
|
checkbox(*present)
|
||||||
.label("Present (checked with empty text means an explicit empty value)")
|
.label("Present (checked with empty text means an explicit empty value)")
|
||||||
.on_toggle(move |present| {
|
.on_toggle(move |present| {
|
||||||
Event::Input(
|
Event::Input(
|
||||||
toggle_facet.clone(),
|
toggle_facet.clone(),
|
||||||
Raw::Optional {
|
Raw::Optional {
|
||||||
present,
|
present,
|
||||||
text: retained.clone(),
|
text: retained.clone(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.push(
|
.push(
|
||||||
text_input("Retained optional text", value)
|
text_input("Retained optional text", value)
|
||||||
.on_input(move |text| {
|
.on_input(move |text| {
|
||||||
Event::Input(
|
Event::Input(
|
||||||
input_facet.clone(),
|
input_facet.clone(),
|
||||||
Raw::Optional {
|
Raw::Optional {
|
||||||
present: enabled,
|
present: enabled,
|
||||||
text,
|
text,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.padding(7),
|
.padding(7),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(content.into())
|
Some(content.into())
|
||||||
}
|
}
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
struct RelationChoice(ArtifactRelation);
|
struct RelationChoice(ArtifactRelation);
|
||||||
impl std::fmt::Display for RelationChoice {
|
impl std::fmt::Display for RelationChoice {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self.0)
|
write!(f, "{:?}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +1,79 @@
|
|||||||
//! Explicit attachment pair and final destination field controls.
|
//! Explicit attachment pair and final destination field controls.
|
||||||
use super::{
|
use super::{
|
||||||
attachments::{Editor, Event, Family},
|
attachments::{Editor, Event, Family},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use iced::{
|
use iced::{
|
||||||
Element as View, Fill,
|
Element as View, Fill,
|
||||||
widget::{button, column, pick_list, row, text, text_input},
|
widget::{button, column, pick_list, row, text, text_input},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn pairs<'a>(
|
pub(super) fn pairs<'a>(
|
||||||
editor: &'a Editor,
|
editor: &'a Editor,
|
||||||
project: &'a Project,
|
project: &'a Project,
|
||||||
trace: &'a Trace,
|
trace: &'a Trace,
|
||||||
) -> View<'a, Event> {
|
) -> View<'a, Event> {
|
||||||
let mut content = column![
|
let mut content = column![
|
||||||
text("Existing children and relationship endpoints").size(16),
|
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),
|
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),
|
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),
|
text_input("Filter attachment identities by name or alias", &editor.search).on_input(Event::Search).size(12).padding(7),
|
||||||
].spacing(9);
|
].spacing(9);
|
||||||
for layer in [Layer::Oa, Layer::Sa] {
|
for layer in [Layer::Oa, Layer::Sa] {
|
||||||
let label = if layer == Layer::Oa {
|
let label = if layer == Layer::Oa {
|
||||||
"Operational"
|
"Operational"
|
||||||
} else {
|
} else {
|
||||||
"System"
|
"System"
|
||||||
};
|
};
|
||||||
let owner = if layer == Layer::Oa {
|
let owner = if layer == Layer::Oa {
|
||||||
trace.oa
|
trace.oa
|
||||||
} else {
|
} else {
|
||||||
trace.sa
|
trace.sa
|
||||||
};
|
};
|
||||||
content = content.push(text(format!("{label} owner: {}", project.named(owner))).size(12));
|
content = content.push(text(format!("{label} owner: {}", project.named(owner))).size(12));
|
||||||
content = content.push(
|
content = content.push(
|
||||||
row![
|
row![
|
||||||
pick_list(
|
pick_list(
|
||||||
editor.choices(project, trace, layer),
|
editor.choices(project, trace, layer),
|
||||||
editor.selection(layer).cloned(),
|
editor.selection(layer).cloned(),
|
||||||
move |choice| Event::Pick(layer, Some(choice))
|
move |choice| Event::Pick(layer, Some(choice))
|
||||||
)
|
)
|
||||||
.placeholder(format!("Choose an existing {label} identity"))
|
.placeholder(format!("Choose an existing {label} identity"))
|
||||||
.text_size(12)
|
.text_size(12)
|
||||||
.padding(7)
|
.padding(7)
|
||||||
.width(Fill),
|
.width(Fill),
|
||||||
button(text("No identity on this side").size(11))
|
button(text("No identity on this side").size(11))
|
||||||
.style(crate::style::nav)
|
.style(crate::style::nav)
|
||||||
.padding(7)
|
.padding(7)
|
||||||
.on_press(Event::Pick(layer, None)),
|
.on_press(Event::Pick(layer, None)),
|
||||||
]
|
]
|
||||||
.spacing(7),
|
.spacing(7),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
content = content.push(
|
content = content.push(
|
||||||
button(text("Use exact attachment pair").size(12))
|
button(text("Use exact attachment pair").size(12))
|
||||||
.padding(8)
|
.padding(8)
|
||||||
.style(crate::style::nav)
|
.style(crate::style::nav)
|
||||||
.on_press(Event::Stage),
|
.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()
|
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>(
|
pub(super) fn candidate<'a>(
|
||||||
editor: &'a Editor,
|
editor: &'a Editor,
|
||||||
project: &'a Project,
|
project: &'a Project,
|
||||||
facet: &'a FacetSelector,
|
facet: &'a FacetSelector,
|
||||||
layer: Layer,
|
layer: Layer,
|
||||||
) -> Option<View<'a, Message>> {
|
) -> Option<View<'a, Message>> {
|
||||||
let draft = editor.drafts.get(facet)?;
|
let draft = editor.drafts.get(facet)?;
|
||||||
let (id, _) = attachments::actual_slot(facet, layer)?;
|
let (id, _) = attachments::actual_slot(facet, layer)?;
|
||||||
let path = attachments::path(project, facet, layer)?;
|
let path = attachments::path(project, facet, layer)?;
|
||||||
Some(
|
Some(
|
||||||
column![
|
column![
|
||||||
text(format!("Destination record: {}", project.named(id))).size(12),
|
text(format!("Destination record: {}", project.named(id))).size(12),
|
||||||
crate::forms::scoped(draft, project, &path, attachments::label(facet)),
|
crate::forms::scoped(draft, project, &path, attachments::label(facet)),
|
||||||
]
|
]
|
||||||
.spacing(7)
|
.spacing(7)
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,350 +1,350 @@
|
|||||||
//! Closed presence-only adapter. The core independently validates the full final record.
|
//! Closed presence-only adapter. The core independently validates the full final record.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum Family {
|
pub enum Family {
|
||||||
Member(ReferenceCollection),
|
Member(ReferenceCollection),
|
||||||
Literal,
|
Literal,
|
||||||
Annotation,
|
Annotation,
|
||||||
Binding,
|
Binding,
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for Family {
|
impl std::fmt::Display for Family {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
Self::Member(ReferenceCollection::FunctionInputItems) => "Function input items",
|
Self::Member(ReferenceCollection::FunctionInputItems) => "Function input items",
|
||||||
Self::Member(ReferenceCollection::FunctionOutputItems) => "Function output items",
|
Self::Member(ReferenceCollection::FunctionOutputItems) => "Function output items",
|
||||||
Self::Member(ReferenceCollection::SignalItems) => "Signal items",
|
Self::Member(ReferenceCollection::SignalItems) => "Signal items",
|
||||||
Self::Member(ReferenceCollection::CategoryExchanges) => "Category exchanges",
|
Self::Member(ReferenceCollection::CategoryExchanges) => "Category exchanges",
|
||||||
Self::Member(ReferenceCollection::ExchangeItems) => "Exchange items",
|
Self::Member(ReferenceCollection::ExchangeItems) => "Exchange items",
|
||||||
Self::Member(ReferenceCollection::TransitionTriggers) => "Transition triggers",
|
Self::Member(ReferenceCollection::TransitionTriggers) => "Transition triggers",
|
||||||
Self::Member(ReferenceCollection::TransitionEffects) => "Transition effects",
|
Self::Member(ReferenceCollection::TransitionEffects) => "Transition effects",
|
||||||
Self::Member(ReferenceCollection::Constrained) => "Constrained identities",
|
Self::Member(ReferenceCollection::Constrained) => "Constrained identities",
|
||||||
Self::Member(ReferenceCollection::Steps) => "Ordered steps",
|
Self::Member(ReferenceCollection::Steps) => "Ordered steps",
|
||||||
Self::Member(ReferenceCollection::FragmentBranches) => "Fragment branches",
|
Self::Member(ReferenceCollection::FragmentBranches) => "Fragment branches",
|
||||||
Self::Literal => "Enumeration literals",
|
Self::Literal => "Enumeration literals",
|
||||||
Self::Annotation => "Message values",
|
Self::Annotation => "Message values",
|
||||||
Self::Binding => "Scenario lifeline bindings",
|
Self::Binding => "Scenario lifeline bindings",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(super) const FAMILIES: [Family; 13] = [
|
pub(super) const FAMILIES: [Family; 13] = [
|
||||||
Family::Member(ReferenceCollection::Steps),
|
Family::Member(ReferenceCollection::Steps),
|
||||||
Family::Member(ReferenceCollection::FragmentBranches),
|
Family::Member(ReferenceCollection::FragmentBranches),
|
||||||
Family::Member(ReferenceCollection::FunctionInputItems),
|
Family::Member(ReferenceCollection::FunctionInputItems),
|
||||||
Family::Member(ReferenceCollection::FunctionOutputItems),
|
Family::Member(ReferenceCollection::FunctionOutputItems),
|
||||||
Family::Member(ReferenceCollection::SignalItems),
|
Family::Member(ReferenceCollection::SignalItems),
|
||||||
Family::Member(ReferenceCollection::ExchangeItems),
|
Family::Member(ReferenceCollection::ExchangeItems),
|
||||||
Family::Member(ReferenceCollection::CategoryExchanges),
|
Family::Member(ReferenceCollection::CategoryExchanges),
|
||||||
Family::Member(ReferenceCollection::TransitionTriggers),
|
Family::Member(ReferenceCollection::TransitionTriggers),
|
||||||
Family::Member(ReferenceCollection::TransitionEffects),
|
Family::Member(ReferenceCollection::TransitionEffects),
|
||||||
Family::Member(ReferenceCollection::Constrained),
|
Family::Member(ReferenceCollection::Constrained),
|
||||||
Family::Literal,
|
Family::Literal,
|
||||||
Family::Annotation,
|
Family::Annotation,
|
||||||
Family::Binding,
|
Family::Binding,
|
||||||
];
|
];
|
||||||
pub(super) fn family(facet: &FacetSelector) -> Option<Family> {
|
pub(super) fn family(facet: &FacetSelector) -> Option<Family> {
|
||||||
let family = match facet {
|
let family = match facet {
|
||||||
FacetSelector::Member { collection, .. } => Family::Member(*collection),
|
FacetSelector::Member { collection, .. } => Family::Member(*collection),
|
||||||
FacetSelector::Literal(_) => Family::Literal,
|
FacetSelector::Literal(_) => Family::Literal,
|
||||||
FacetSelector::Annotation(_) => Family::Annotation,
|
FacetSelector::Annotation(_) => Family::Annotation,
|
||||||
FacetSelector::Binding(_) => Family::Binding,
|
FacetSelector::Binding(_) => Family::Binding,
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
FAMILIES.contains(&family).then_some(family)
|
FAMILIES.contains(&family).then_some(family)
|
||||||
}
|
}
|
||||||
pub(super) fn supported(facet: &FacetSelector) -> bool {
|
pub(super) fn supported(facet: &FacetSelector) -> bool {
|
||||||
family(facet).is_some()
|
family(facet).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed paths for the closed collections and the exact enum literal set.
|
// Fixed paths for the closed collections and the exact enum literal set.
|
||||||
// They are selected by the typed facet; callers cannot supply arbitrary paths.
|
// They are selected by the typed facet; callers cannot supply arbitrary paths.
|
||||||
pub(super) fn path(value: &Value, family: Family) -> Option<String> {
|
pub(super) fn path(value: &Value, family: Family) -> Option<String> {
|
||||||
let prefixes = ["/body/Oa/Shared", "/body/Sa/Shared"];
|
let prefixes = ["/body/Oa/Shared", "/body/Sa/Shared"];
|
||||||
let tails: &[&str] = match family {
|
let tails: &[&str] = match family {
|
||||||
Family::Member(ReferenceCollection::Steps) => {
|
Family::Member(ReferenceCollection::Steps) => {
|
||||||
return [
|
return [
|
||||||
"/body/Oa/Process/steps",
|
"/body/Oa/Process/steps",
|
||||||
"/body/Sa/FunctionalChain/steps",
|
"/body/Sa/FunctionalChain/steps",
|
||||||
"/body/Oa/Shared/Scenario/steps",
|
"/body/Oa/Shared/Scenario/steps",
|
||||||
"/body/Sa/Shared/Scenario/steps",
|
"/body/Sa/Shared/Scenario/steps",
|
||||||
"/body/Oa/Shared/Occurrence/body/Branch/occurrences",
|
"/body/Oa/Shared/Occurrence/body/Branch/occurrences",
|
||||||
"/body/Sa/Shared/Occurrence/body/Branch/occurrences",
|
"/body/Sa/Shared/Occurrence/body/Branch/occurrences",
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|p| value.pointer(p).is_some_and(Value::is_array))
|
.find(|p| value.pointer(p).is_some_and(Value::is_array))
|
||||||
.map(str::to_owned);
|
.map(str::to_owned);
|
||||||
}
|
}
|
||||||
Family::Member(ReferenceCollection::FragmentBranches) => {
|
Family::Member(ReferenceCollection::FragmentBranches) => {
|
||||||
&["Occurrence/body/Fragment/branches"]
|
&["Occurrence/body/Fragment/branches"]
|
||||||
}
|
}
|
||||||
Family::Member(ReferenceCollection::FunctionInputItems) => {
|
Family::Member(ReferenceCollection::FunctionInputItems) => {
|
||||||
&["Port/contract/FunctionInput/incoming_items"]
|
&["Port/contract/FunctionInput/incoming_items"]
|
||||||
}
|
}
|
||||||
Family::Member(ReferenceCollection::FunctionOutputItems) => {
|
Family::Member(ReferenceCollection::FunctionOutputItems) => {
|
||||||
&["Port/contract/FunctionOutput/outgoing_items"]
|
&["Port/contract/FunctionOutput/outgoing_items"]
|
||||||
}
|
}
|
||||||
Family::Member(ReferenceCollection::CategoryExchanges) => &["ExchangeCategory/exchanges"],
|
Family::Member(ReferenceCollection::CategoryExchanges) => &["ExchangeCategory/exchanges"],
|
||||||
Family::Member(ReferenceCollection::SignalItems) => &["Event/body/Signal/items"],
|
Family::Member(ReferenceCollection::SignalItems) => &["Event/body/Signal/items"],
|
||||||
Family::Member(ReferenceCollection::Constrained) => &["Constraint/constrained"],
|
Family::Member(ReferenceCollection::Constrained) => &["Constraint/constrained"],
|
||||||
Family::Literal => &["DataType/definition/Enumeration/literals"],
|
Family::Literal => &["DataType/definition/Enumeration/literals"],
|
||||||
Family::Annotation => &["Occurrence/body/Exchange/values"],
|
Family::Annotation => &["Occurrence/body/Exchange/values"],
|
||||||
Family::Binding => &["Occurrence/body/ScenarioUse/bindings"],
|
Family::Binding => &["Occurrence/body/ScenarioUse/bindings"],
|
||||||
Family::Member(ReferenceCollection::ExchangeItems) => {
|
Family::Member(ReferenceCollection::ExchangeItems) => {
|
||||||
return [
|
return [
|
||||||
"Interaction",
|
"Interaction",
|
||||||
"FunctionalExchange",
|
"FunctionalExchange",
|
||||||
"BoundaryExchange",
|
"BoundaryExchange",
|
||||||
"CommunicationMean",
|
"CommunicationMean",
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|kind| format!("/body/{kind}/items"))
|
.map(|kind| format!("/body/{kind}/items"))
|
||||||
.find(|p| value.pointer(p).is_some_and(Value::is_array));
|
.find(|p| value.pointer(p).is_some_and(Value::is_array));
|
||||||
}
|
}
|
||||||
Family::Member(ReferenceCollection::TransitionTriggers) => {
|
Family::Member(ReferenceCollection::TransitionTriggers) => {
|
||||||
return value
|
return value
|
||||||
.pointer("/body/StateTransition/triggers")
|
.pointer("/body/StateTransition/triggers")
|
||||||
.is_some_and(Value::is_array)
|
.is_some_and(Value::is_array)
|
||||||
.then(|| "/body/StateTransition/triggers".into());
|
.then(|| "/body/StateTransition/triggers".into());
|
||||||
}
|
}
|
||||||
Family::Member(ReferenceCollection::TransitionEffects) => {
|
Family::Member(ReferenceCollection::TransitionEffects) => {
|
||||||
return value
|
return value
|
||||||
.pointer("/body/StateTransition/effects")
|
.pointer("/body/StateTransition/effects")
|
||||||
.is_some_and(Value::is_array)
|
.is_some_and(Value::is_array)
|
||||||
.then(|| "/body/StateTransition/effects".into());
|
.then(|| "/body/StateTransition/effects".into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
prefixes
|
prefixes
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|prefix| tails.iter().map(move |tail| format!("{prefix}/{tail}")))
|
.flat_map(|prefix| tails.iter().map(move |tail| format!("{prefix}/{tail}")))
|
||||||
.find(|p| value.pointer(p).is_some_and(Value::is_array))
|
.find(|p| value.pointer(p).is_some_and(Value::is_array))
|
||||||
}
|
}
|
||||||
pub(super) fn key(facet: &FacetSelector, layer: Layer) -> Option<Value> {
|
pub(super) fn key(facet: &FacetSelector, layer: Layer) -> Option<Value> {
|
||||||
match facet {
|
match facet {
|
||||||
FacetSelector::Member { keys, .. } => if layer == Layer::Oa { keys.oa } else { keys.sa }
|
FacetSelector::Member { keys, .. } => if layer == Layer::Oa { keys.oa } else { keys.sa }
|
||||||
.map(|id| serde_json::to_value(id).expect("Id")),
|
.map(|id| serde_json::to_value(id).expect("Id")),
|
||||||
FacetSelector::Literal(keys) => if layer == Layer::Oa {
|
FacetSelector::Literal(keys) => if layer == Layer::Oa {
|
||||||
keys.oa.as_ref()
|
keys.oa.as_ref()
|
||||||
} else {
|
} else {
|
||||||
keys.sa.as_ref()
|
keys.sa.as_ref()
|
||||||
}
|
}
|
||||||
.map(|v| Value::String(v.clone())),
|
.map(|v| Value::String(v.clone())),
|
||||||
FacetSelector::Annotation(keys) => if layer == Layer::Oa {
|
FacetSelector::Annotation(keys) => if layer == Layer::Oa {
|
||||||
keys.oa.as_ref()
|
keys.oa.as_ref()
|
||||||
} else {
|
} else {
|
||||||
keys.sa.as_ref()
|
keys.sa.as_ref()
|
||||||
}
|
}
|
||||||
.map(|key| serde_json::to_value(key).expect("Annotation key")),
|
.map(|key| serde_json::to_value(key).expect("Annotation key")),
|
||||||
FacetSelector::Binding(keys) => if layer == Layer::Oa { keys.oa } else { keys.sa }
|
FacetSelector::Binding(keys) => if layer == Layer::Oa { keys.oa } else { keys.sa }
|
||||||
.map(|id| serde_json::to_value(id).expect("Binding key")),
|
.map(|id| serde_json::to_value(id).expect("Binding key")),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(super) fn overlap(a: &FacetSelector, b: &FacetSelector) -> bool {
|
pub(super) fn overlap(a: &FacetSelector, b: &FacetSelector) -> bool {
|
||||||
if family(a).is_none() || family(a) != family(b) {
|
if family(a).is_none() || family(a) != family(b) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
[Layer::Oa, Layer::Sa]
|
[Layer::Oa, Layer::Sa]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.any(|layer| key(a, layer).is_some() && key(a, layer) == key(b, layer))
|
.any(|layer| key(a, layer).is_some() && key(a, layer) == key(b, layer))
|
||||||
}
|
}
|
||||||
pub(super) fn project_presence(
|
pub(super) fn project_presence(
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
facet: &FacetSelector,
|
facet: &FacetSelector,
|
||||||
layer: Layer,
|
layer: Layer,
|
||||||
present: bool,
|
present: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let family = family(facet).ok_or("This keyed family remains read-only.")?;
|
let family = family(facet).ok_or("This keyed family remains read-only.")?;
|
||||||
let key = key(facet, layer)
|
let key = key(facet, layer)
|
||||||
.ok_or("Choose the destination identity in Members before setting or taking presence.")?;
|
.ok_or("Choose the destination identity in Members before setting or taking presence.")?;
|
||||||
let path =
|
let path =
|
||||||
path(value, family).ok_or("This collection is not applicable to the destination kind.")?;
|
path(value, family).ok_or("This collection is not applicable to the destination kind.")?;
|
||||||
let list = value
|
let list = value
|
||||||
.pointer_mut(&path)
|
.pointer_mut(&path)
|
||||||
.and_then(Value::as_array_mut)
|
.and_then(Value::as_array_mut)
|
||||||
.ok_or("Destination collection is unavailable.")?;
|
.ok_or("Destination collection is unavailable.")?;
|
||||||
if present {
|
if present {
|
||||||
if !list.contains(&key) {
|
if !list.contains(&key) {
|
||||||
list.push(key);
|
list.push(key);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
list.retain(|v| v != &key);
|
list.retain(|v| v != &key);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReviewRow {
|
impl ReviewRow {
|
||||||
pub(super) fn keyed_value(
|
pub(super) fn keyed_value(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
trace: &Trace,
|
trace: &Trace,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
facet: &FacetSelector,
|
facet: &FacetSelector,
|
||||||
decision: SemanticDecision,
|
decision: SemanticDecision,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if keyed_rows::supported(facet) {
|
if keyed_rows::supported(facet) {
|
||||||
return self.project_row(project, trace, value, facet, decision);
|
return self.project_row(project, trace, value, facet, decision);
|
||||||
}
|
}
|
||||||
let present = match decision {
|
let present = match decision {
|
||||||
SemanticDecision::KeepTarget => return Ok(()),
|
SemanticDecision::KeepTarget => return Ok(()),
|
||||||
SemanticDecision::TakeSource => {
|
SemanticDecision::TakeSource => {
|
||||||
// A missing source identity is a target-local row, not a removal.
|
// A missing source identity is a target-local row, not a removal.
|
||||||
if key(facet, self.direction.source_layer()).is_none() {
|
if key(facet, self.direction.source_layer()).is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let fact = capture_facet(project, trace.oa, trace.sa, facet)?;
|
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.")?
|
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
|
SemanticDecision::Candidate => *self
|
||||||
.presence
|
.presence
|
||||||
.get(facet)
|
.get(facet)
|
||||||
.ok_or("Choose final membership presence.")?,
|
.ok_or("Choose final membership presence.")?,
|
||||||
};
|
};
|
||||||
project_presence(value, facet, self.direction.target_layer(), present)
|
project_presence(value, facet, self.direction.target_layer(), present)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn member_families(&self, project: &Project) -> Vec<Family> {
|
pub(super) fn member_families(&self, project: &Project) -> Vec<Family> {
|
||||||
let Some(trace) = self.effective_trace(project) else {
|
let Some(trace) = self.effective_trace(project) else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
let values: Vec<_> = [trace.oa, trace.sa]
|
let values: Vec<_> = [trace.oa, trace.sa]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|id| Draft::from_project(project, id).map(|d| d.value))
|
.filter_map(|id| Draft::from_project(project, id).map(|d| d.value))
|
||||||
.collect();
|
.collect();
|
||||||
FAMILIES
|
FAMILIES
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|family| values.iter().any(|value| path(value, *family).is_some()))
|
.filter(|family| values.iter().any(|value| path(value, *family).is_some()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn member_choices(&self, project: &Project, layer: Layer) -> Vec<Choice> {
|
pub(super) fn member_choices(&self, project: &Project, layer: Layer) -> Vec<Choice> {
|
||||||
if self.member_family == Family::Binding {
|
if self.member_family == Family::Binding {
|
||||||
return self.binding_choices(project, layer, true);
|
return self.binding_choices(project, layer, true);
|
||||||
}
|
}
|
||||||
let Some(trace) = self.effective_trace(project) else {
|
let Some(trace) = self.effective_trace(project) else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
let id = if layer == Layer::Oa {
|
let id = if layer == Layer::Oa {
|
||||||
trace.oa
|
trace.oa
|
||||||
} else {
|
} else {
|
||||||
trace.sa
|
trace.sa
|
||||||
};
|
};
|
||||||
let Some(mut draft) = Draft::from_project(project, id) else {
|
let Some(mut draft) = Draft::from_project(project, id) else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
if layer == self.direction.target_layer() {
|
if layer == self.direction.target_layer() {
|
||||||
draft.set_context(self.combined_value(project, true).ok());
|
draft.set_context(self.combined_value(project, true).ok());
|
||||||
}
|
}
|
||||||
let selected = if layer == Layer::Oa {
|
let selected = if layer == Layer::Oa {
|
||||||
&self.member_oa
|
&self.member_oa
|
||||||
} else {
|
} else {
|
||||||
&self.member_sa
|
&self.member_sa
|
||||||
};
|
};
|
||||||
let Some(path) = path(&draft.context_value(), self.member_family) else {
|
let Some(path) = path(&draft.context_value(), self.member_family) else {
|
||||||
return selected.iter().cloned().collect();
|
return selected.iter().cloned().collect();
|
||||||
};
|
};
|
||||||
crate::forms::reference_choices(
|
crate::forms::reference_choices(
|
||||||
&draft,
|
&draft,
|
||||||
project,
|
project,
|
||||||
&format!("{path}/0"),
|
&format!("{path}/0"),
|
||||||
selected.as_ref().map_or(Id::nil(), |c| c.id),
|
selected.as_ref().map_or(Id::nil(), |c| c.id),
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|c| !c.id.is_nil())
|
.filter(|c| !c.id.is_nil())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn edit_pair(
|
pub(super) fn edit_pair(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
facet: FacetSelector,
|
facet: FacetSelector,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
self.member_family = family(&facet).ok_or("This keyed family remains read-only.")?;
|
self.member_family = family(&facet).ok_or("This keyed family remains read-only.")?;
|
||||||
match facet {
|
match facet {
|
||||||
FacetSelector::Annotation(keys) => {
|
FacetSelector::Annotation(keys) => {
|
||||||
self.annotation_oa.selected = keys.oa.map(|key| crate::data_values::FieldPathKey {
|
self.annotation_oa.selected = keys.oa.map(|key| crate::data_values::FieldPathKey {
|
||||||
path: key.path,
|
path: key.path,
|
||||||
field: key.field,
|
field: key.field,
|
||||||
});
|
});
|
||||||
self.annotation_sa.selected = keys.sa.map(|key| crate::data_values::FieldPathKey {
|
self.annotation_sa.selected = keys.sa.map(|key| crate::data_values::FieldPathKey {
|
||||||
path: key.path,
|
path: key.path,
|
||||||
field: key.field,
|
field: key.field,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
FacetSelector::Binding(keys) => {
|
FacetSelector::Binding(keys) => {
|
||||||
self.member_oa = keys.oa.map(|id| view::choice(project, id));
|
self.member_oa = keys.oa.map(|id| view::choice(project, id));
|
||||||
self.member_sa = keys.sa.map(|id| view::choice(project, id));
|
self.member_sa = keys.sa.map(|id| view::choice(project, id));
|
||||||
}
|
}
|
||||||
FacetSelector::Member { keys, .. } => {
|
FacetSelector::Member { keys, .. } => {
|
||||||
self.member_oa = keys.oa.map(|id| view::choice(project, id));
|
self.member_oa = keys.oa.map(|id| view::choice(project, id));
|
||||||
self.member_sa = keys.sa.map(|id| view::choice(project, id));
|
self.member_sa = keys.sa.map(|id| view::choice(project, id));
|
||||||
}
|
}
|
||||||
FacetSelector::Literal(keys) => {
|
FacetSelector::Literal(keys) => {
|
||||||
self.literal_oa = keys.oa.is_some();
|
self.literal_oa = keys.oa.is_some();
|
||||||
self.literal_sa = keys.sa.is_some();
|
self.literal_sa = keys.sa.is_some();
|
||||||
self.literal = keys.oa.or(keys.sa).unwrap_or_default();
|
self.literal = keys.oa.or(keys.sa).unwrap_or_default();
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
self.tab = Tab::Members;
|
self.tab = Tab::Members;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn stage_member(&mut self, project: &Project) -> Result<(), String> {
|
pub(super) fn stage_member(&mut self, project: &Project) -> Result<(), String> {
|
||||||
if !FAMILIES.contains(&self.member_family) {
|
if !FAMILIES.contains(&self.member_family) {
|
||||||
return Err("This keyed family remains read-only.".into());
|
return Err("This keyed family remains read-only.".into());
|
||||||
}
|
}
|
||||||
let facet = match self.member_family {
|
let facet = match self.member_family {
|
||||||
Family::Member(collection) => FacetSelector::Member {
|
Family::Member(collection) => FacetSelector::Member {
|
||||||
collection,
|
collection,
|
||||||
keys: KeyPair {
|
keys: KeyPair {
|
||||||
oa: self.member_oa.as_ref().map(|c| c.id),
|
oa: self.member_oa.as_ref().map(|c| c.id),
|
||||||
sa: self.member_sa.as_ref().map(|c| c.id),
|
sa: self.member_sa.as_ref().map(|c| c.id),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Family::Literal => FacetSelector::Literal(KeyPair {
|
Family::Literal => FacetSelector::Literal(KeyPair {
|
||||||
oa: self.literal_oa.then(|| self.literal.clone()),
|
oa: self.literal_oa.then(|| self.literal.clone()),
|
||||||
sa: self.literal_sa.then(|| self.literal.clone()),
|
sa: self.literal_sa.then(|| self.literal.clone()),
|
||||||
}),
|
}),
|
||||||
Family::Annotation => FacetSelector::Annotation(KeyPair {
|
Family::Annotation => FacetSelector::Annotation(KeyPair {
|
||||||
oa: self
|
oa: self
|
||||||
.annotation_oa
|
.annotation_oa
|
||||||
.selected
|
.selected
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|key| AnnotationKey {
|
.map(|key| AnnotationKey {
|
||||||
path: key.path.clone(),
|
path: key.path.clone(),
|
||||||
field: key.field,
|
field: key.field,
|
||||||
}),
|
}),
|
||||||
sa: self
|
sa: self
|
||||||
.annotation_sa
|
.annotation_sa
|
||||||
.selected
|
.selected
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|key| AnnotationKey {
|
.map(|key| AnnotationKey {
|
||||||
path: key.path.clone(),
|
path: key.path.clone(),
|
||||||
field: key.field,
|
field: key.field,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Family::Binding => FacetSelector::Binding(KeyPair {
|
Family::Binding => FacetSelector::Binding(KeyPair {
|
||||||
oa: self.member_oa.as_ref().map(|c| c.id),
|
oa: self.member_oa.as_ref().map(|c| c.id),
|
||||||
sa: self.member_sa.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.")?;
|
let trace = self.effective_trace(project).ok_or("Choose a trace.")?;
|
||||||
capture_facet(project, trace.oa, trace.sa, &facet)?;
|
capture_facet(project, trace.oa, trace.sa, &facet)?;
|
||||||
self.stage_row_references(&facet)?;
|
self.stage_row_references(&facet)?;
|
||||||
if let FacetSelector::Member {
|
if let FacetSelector::Member {
|
||||||
keys:
|
keys:
|
||||||
KeyPair {
|
KeyPair {
|
||||||
oa: Some(oa),
|
oa: Some(oa),
|
||||||
sa: Some(sa),
|
sa: Some(sa),
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} = &facet
|
} = &facet
|
||||||
{
|
{
|
||||||
self.references.insert(*oa, *sa);
|
self.references.insert(*oa, *sa);
|
||||||
}
|
}
|
||||||
self.staged.insert(facet.clone());
|
self.staged.insert(facet.clone());
|
||||||
self.selected = Some(facet);
|
self.selected = Some(facet);
|
||||||
self.tab = Tab::Facts;
|
self.tab = Tab::Facts;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,345 +1,345 @@
|
|||||||
//! Atomic message-value and lifeline-binding rows with exact, immutable keys.
|
//! Atomic message-value and lifeline-binding rows with exact, immutable keys.
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_values::{FieldPathChoice, FieldPathKey};
|
use crate::data_values::{FieldPathChoice, FieldPathKey};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AddressDraft {
|
pub struct AddressDraft {
|
||||||
pub selected: Option<FieldPathKey>,
|
pub selected: Option<FieldPathKey>,
|
||||||
pub scope: Option<FieldPathKey>,
|
pub scope: Option<FieldPathKey>,
|
||||||
pub page: usize,
|
pub page: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RowDraft {
|
pub struct RowDraft {
|
||||||
pub expression: String,
|
pub expression: String,
|
||||||
pub editor: text_editor::Content,
|
pub editor: text_editor::Content,
|
||||||
pub local: Option<Choice>,
|
pub local: Option<Choice>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn supported(facet: &FacetSelector) -> bool {
|
pub(super) fn supported(facet: &FacetSelector) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
facet,
|
facet,
|
||||||
FacetSelector::Annotation(_) | FacetSelector::Binding(_)
|
FacetSelector::Annotation(_) | FacetSelector::Binding(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn target_selector(facet: &FacetSelector, layer: Layer) -> FacetSelector {
|
pub(super) fn target_selector(facet: &FacetSelector, layer: Layer) -> FacetSelector {
|
||||||
match facet {
|
match facet {
|
||||||
FacetSelector::Annotation(keys) => FacetSelector::Annotation(KeyPair {
|
FacetSelector::Annotation(keys) => FacetSelector::Annotation(KeyPair {
|
||||||
oa: (layer == Layer::Oa).then(|| keys.oa.clone()).flatten(),
|
oa: (layer == Layer::Oa).then(|| keys.oa.clone()).flatten(),
|
||||||
sa: (layer == Layer::Sa).then(|| keys.sa.clone()).flatten(),
|
sa: (layer == Layer::Sa).then(|| keys.sa.clone()).flatten(),
|
||||||
}),
|
}),
|
||||||
FacetSelector::Binding(keys) => FacetSelector::Binding(KeyPair {
|
FacetSelector::Binding(keys) => FacetSelector::Binding(KeyPair {
|
||||||
oa: if layer == Layer::Oa { keys.oa } else { None },
|
oa: if layer == Layer::Oa { keys.oa } else { None },
|
||||||
sa: if layer == Layer::Sa { keys.sa } else { None },
|
sa: if layer == Layer::Sa { keys.sa } else { None },
|
||||||
}),
|
}),
|
||||||
_ => facet.clone(),
|
_ => facet.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReviewRow {
|
impl ReviewRow {
|
||||||
pub(super) fn annotation_address(&self, layer: Layer) -> &AddressDraft {
|
pub(super) fn annotation_address(&self, layer: Layer) -> &AddressDraft {
|
||||||
if layer == Layer::Oa {
|
if layer == Layer::Oa {
|
||||||
&self.annotation_oa
|
&self.annotation_oa
|
||||||
} else {
|
} else {
|
||||||
&self.annotation_sa
|
&self.annotation_sa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn annotation_address_mut(&mut self, layer: Layer) -> &mut AddressDraft {
|
pub(super) fn annotation_address_mut(&mut self, layer: Layer) -> &mut AddressDraft {
|
||||||
if layer == Layer::Oa {
|
if layer == Layer::Oa {
|
||||||
&mut self.annotation_oa
|
&mut self.annotation_oa
|
||||||
} else {
|
} else {
|
||||||
&mut self.annotation_sa
|
&mut self.annotation_sa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn row_owner(&self, project: &Project, layer: Layer) -> Option<Element> {
|
fn row_owner(&self, project: &Project, layer: Layer) -> Option<Element> {
|
||||||
let trace = self.effective_trace(project)?;
|
let trace = self.effective_trace(project)?;
|
||||||
let id = if layer == Layer::Oa {
|
let id = if layer == Layer::Oa {
|
||||||
trace.oa
|
trace.oa
|
||||||
} else {
|
} else {
|
||||||
trace.sa
|
trace.sa
|
||||||
};
|
};
|
||||||
if layer == self.direction.target_layer()
|
if layer == self.direction.target_layer()
|
||||||
&& let Ok(value) = self.combined_value(project, true)
|
&& let Ok(value) = self.combined_value(project, true)
|
||||||
&& let Ok(element) = serde_json::from_value(value)
|
&& let Ok(element) = serde_json::from_value(value)
|
||||||
{
|
{
|
||||||
return Some(element);
|
return Some(element);
|
||||||
}
|
}
|
||||||
project.elements.get(&id).cloned()
|
project.elements.get(&id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn annotation_choices(
|
pub(super) fn annotation_choices(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
layer: Layer,
|
layer: Layer,
|
||||||
) -> Result<Vec<FieldPathChoice>, String> {
|
) -> Result<Vec<FieldPathChoice>, String> {
|
||||||
let element = self.row_owner(project, layer).ok_or("Choose a trace.")?;
|
let element = self.row_owner(project, layer).ok_or("Choose a trace.")?;
|
||||||
let Some(SharedBody::Occurrence {
|
let Some(SharedBody::Occurrence {
|
||||||
body:
|
body:
|
||||||
OccurrenceBody::Exchange {
|
OccurrenceBody::Exchange {
|
||||||
exchange: Some(exchange),
|
exchange: Some(exchange),
|
||||||
message_kind,
|
message_kind,
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
}) = element.shared()
|
}) = element.shared()
|
||||||
else {
|
else {
|
||||||
return Err(
|
return Err(
|
||||||
"Choose an exchange on this message before selecting a carried field.".into(),
|
"Choose an exchange on this message before selecting a carried field.".into(),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
let exchange = project
|
let exchange = project
|
||||||
.relations
|
.relations
|
||||||
.get(exchange)
|
.get(exchange)
|
||||||
.ok_or("The message exchange is unavailable.")?;
|
.ok_or("The message exchange is unavailable.")?;
|
||||||
crate::data_values::at_scope(
|
crate::data_values::at_scope(
|
||||||
project,
|
project,
|
||||||
exchange,
|
exchange,
|
||||||
message_kind,
|
message_kind,
|
||||||
self.annotation_address(layer).scope.as_ref(),
|
self.annotation_address(layer).scope.as_ref(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn binding_choices(
|
pub(super) fn binding_choices(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
layer: Layer,
|
layer: Layer,
|
||||||
referenced: bool,
|
referenced: bool,
|
||||||
) -> Vec<Choice> {
|
) -> Vec<Choice> {
|
||||||
let Some(element) = self.row_owner(project, layer) else {
|
let Some(element) = self.row_owner(project, layer) else {
|
||||||
return vec![];
|
return vec![];
|
||||||
};
|
};
|
||||||
let scenario = if referenced {
|
let scenario = if referenced {
|
||||||
match element.shared() {
|
match element.shared() {
|
||||||
Some(SharedBody::Occurrence {
|
Some(SharedBody::Occurrence {
|
||||||
body: OccurrenceBody::ScenarioUse { scenario, .. },
|
body: OccurrenceBody::ScenarioUse { scenario, .. },
|
||||||
..
|
..
|
||||||
}) => *scenario,
|
}) => *scenario,
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
element
|
element
|
||||||
.parent
|
.parent
|
||||||
.and_then(|parent| validation::ancestor_of_kind(project, parent, Kind::Scenario))
|
.and_then(|parent| validation::ancestor_of_kind(project, parent, Kind::Scenario))
|
||||||
};
|
};
|
||||||
let mut choices: Vec<_> = project
|
let mut choices: Vec<_> = project
|
||||||
.elements
|
.elements
|
||||||
.values()
|
.values()
|
||||||
.filter(|e| {
|
.filter(|e| {
|
||||||
e.kind() == Kind::Lifeline
|
e.kind() == Kind::Lifeline
|
||||||
&& e.layer() == layer
|
&& e.layer() == layer
|
||||||
&& scenario.is_some()
|
&& scenario.is_some()
|
||||||
&& e.parent == scenario
|
&& e.parent == scenario
|
||||||
})
|
})
|
||||||
.map(|e| view::choice(project, e.id))
|
.map(|e| view::choice(project, e.id))
|
||||||
.collect();
|
.collect();
|
||||||
choices.sort_by_cached_key(|c| c.label.to_lowercase());
|
choices.sort_by_cached_key(|c| c.label.to_lowercase());
|
||||||
choices
|
choices
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn stage_row_references(&mut self, facet: &FacetSelector) -> Result<(), String> {
|
pub(super) fn stage_row_references(&mut self, facet: &FacetSelector) -> Result<(), String> {
|
||||||
let pairs: Vec<_> = match facet {
|
let pairs: Vec<_> = match facet {
|
||||||
FacetSelector::Annotation(KeyPair {
|
FacetSelector::Annotation(KeyPair {
|
||||||
oa: Some(oa),
|
oa: Some(oa),
|
||||||
sa: Some(sa),
|
sa: Some(sa),
|
||||||
}) => {
|
}) => {
|
||||||
if oa.path.len() != sa.path.len() {
|
if oa.path.len() != sa.path.len() {
|
||||||
return Err("Paired annotation paths must have the same depth; review distinct addresses separately.".into());
|
return Err("Paired annotation paths must have the same depth; review distinct addresses separately.".into());
|
||||||
}
|
}
|
||||||
oa.path
|
oa.path
|
||||||
.iter()
|
.iter()
|
||||||
.chain(std::iter::once(&oa.field))
|
.chain(std::iter::once(&oa.field))
|
||||||
.copied()
|
.copied()
|
||||||
.zip(sa.path.iter().chain(std::iter::once(&sa.field)).copied())
|
.zip(sa.path.iter().chain(std::iter::once(&sa.field)).copied())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
FacetSelector::Binding(KeyPair {
|
FacetSelector::Binding(KeyPair {
|
||||||
oa: Some(oa),
|
oa: Some(oa),
|
||||||
sa: Some(sa),
|
sa: Some(sa),
|
||||||
}) => vec![(*oa, *sa)],
|
}) => vec![(*oa, *sa)],
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
};
|
};
|
||||||
// A repeated recursive field may occur several times in one address.
|
// A repeated recursive field may occur several times in one address.
|
||||||
// Never let the last occurrence silently replace a conflicting pair.
|
// Never let the last occurrence silently replace a conflicting pair.
|
||||||
let mut supplied = BTreeMap::new();
|
let mut supplied = BTreeMap::new();
|
||||||
for (oa, sa) in pairs {
|
for (oa, sa) in pairs {
|
||||||
if supplied.insert(oa, sa).is_some_and(|prior| prior != sa) {
|
if supplied.insert(oa, sa).is_some_and(|prior| prior != sa) {
|
||||||
return Err(
|
return Err(
|
||||||
"This address pairs the same Operational field with different System fields."
|
"This address pairs the same Operational field with different System fields."
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.references.extend(supplied);
|
self.references.extend(supplied);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn initialize_row(&mut self, project: &Project, facet: &FacetSelector) {
|
pub(super) fn initialize_row(&mut self, project: &Project, facet: &FacetSelector) {
|
||||||
if self.row_drafts.contains_key(facet) {
|
if self.row_drafts.contains_key(facet) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let value = self
|
let value = self
|
||||||
.effective_trace(project)
|
.effective_trace(project)
|
||||||
.and_then(|t| capture_facet(project, t.oa, t.sa, facet).ok())
|
.and_then(|t| capture_facet(project, t.oa, t.sa, facet).ok())
|
||||||
.and_then(|f| scalar::value(&f, self.direction.target_layer()));
|
.and_then(|f| scalar::value(&f, self.direction.target_layer()));
|
||||||
let present = value.as_ref().is_some_and(|v| !v.is_null());
|
let present = value.as_ref().is_some_and(|v| !v.is_null());
|
||||||
let mut draft = RowDraft::default();
|
let mut draft = RowDraft::default();
|
||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
draft.expression = value["expression"].as_str().unwrap_or_default().into();
|
draft.expression = value["expression"].as_str().unwrap_or_default().into();
|
||||||
draft.editor = text_editor::Content::with_text(&draft.expression);
|
draft.editor = text_editor::Content::with_text(&draft.expression);
|
||||||
draft.local = value["local"]
|
draft.local = value["local"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.and_then(|v| v.parse().ok())
|
.and_then(|v| v.parse().ok())
|
||||||
.map(|id| view::choice(project, id));
|
.map(|id| view::choice(project, id));
|
||||||
}
|
}
|
||||||
self.presence.entry(facet.clone()).or_insert(present);
|
self.presence.entry(facet.clone()).or_insert(present);
|
||||||
self.row_drafts.insert(facet.clone(), draft);
|
self.row_drafts.insert(facet.clone(), draft);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn edit_row(
|
pub(super) fn edit_row(
|
||||||
&mut self,
|
&mut self,
|
||||||
facet: &FacetSelector,
|
facet: &FacetSelector,
|
||||||
action: impl FnOnce(&mut RowDraft),
|
action: impl FnOnce(&mut RowDraft),
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !supported(facet)
|
if !supported(facet)
|
||||||
|| self.selected.as_ref() != Some(facet)
|
|| self.selected.as_ref() != Some(facet)
|
||||||
|| self.decisions.get(facet) != Some(&SemanticDecision::Candidate)
|
|| self.decisions.get(facet) != Some(&SemanticDecision::Candidate)
|
||||||
{
|
{
|
||||||
return Err("Select this row and choose Set final value before editing it.".into());
|
return Err("Select this row and choose Set final value before editing it.".into());
|
||||||
}
|
}
|
||||||
action(
|
action(
|
||||||
self.row_drafts
|
self.row_drafts
|
||||||
.get_mut(facet)
|
.get_mut(facet)
|
||||||
.ok_or("The row draft is unavailable.")?,
|
.ok_or("The row draft is unavailable.")?,
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn project_row(
|
pub(super) fn project_row(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
trace: &Trace,
|
trace: &Trace,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
facet: &FacetSelector,
|
facet: &FacetSelector,
|
||||||
decision: SemanticDecision,
|
decision: SemanticDecision,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if decision == SemanticDecision::KeepTarget {
|
if decision == SemanticDecision::KeepTarget {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let source_layer = self.direction.source_layer();
|
let source_layer = self.direction.source_layer();
|
||||||
let target_layer = self.direction.target_layer();
|
let target_layer = self.direction.target_layer();
|
||||||
if decision == SemanticDecision::TakeSource && keyed::key(facet, source_layer).is_none() {
|
if decision == SemanticDecision::TakeSource && keyed::key(facet, source_layer).is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let target_key = keyed::key(facet, target_layer)
|
let target_key = keyed::key(facet, target_layer)
|
||||||
.ok_or("Choose the destination row address before setting or taking its value.")?;
|
.ok_or("Choose the destination row address before setting or taking its value.")?;
|
||||||
let row = if decision == SemanticDecision::Candidate {
|
let row = if decision == SemanticDecision::Candidate {
|
||||||
if !self.presence.get(facet).copied().unwrap_or(false) {
|
if !self.presence.get(facet).copied().unwrap_or(false) {
|
||||||
Value::Null
|
Value::Null
|
||||||
} else {
|
} else {
|
||||||
let draft = self
|
let draft = self
|
||||||
.row_drafts
|
.row_drafts
|
||||||
.get(facet)
|
.get(facet)
|
||||||
.ok_or("Set the final row value.")?;
|
.ok_or("Set the final row value.")?;
|
||||||
match facet {
|
match facet {
|
||||||
FacetSelector::Annotation(_) => {
|
FacetSelector::Annotation(_) => {
|
||||||
let key: AnnotationKey = serde_json::from_value(target_key.clone())
|
let key: AnnotationKey = serde_json::from_value(target_key.clone())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
serde_json::to_value(MessageValue {
|
serde_json::to_value(MessageValue {
|
||||||
path: key.path,
|
path: key.path,
|
||||||
field: key.field,
|
field: key.field,
|
||||||
expression: draft.expression.clone(),
|
expression: draft.expression.clone(),
|
||||||
})
|
})
|
||||||
.expect("Message value")
|
.expect("Message value")
|
||||||
}
|
}
|
||||||
FacetSelector::Binding(_) => {
|
FacetSelector::Binding(_) => {
|
||||||
serde_json::json!({"referenced": target_key, "local": draft.local.as_ref().ok_or("Choose the destination scenario's local lifeline.")?.id})
|
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()),
|
_ => return Err("Unsupported row.".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let fact = capture_facet(project, trace.oa, trace.sa, facet)?;
|
let fact = capture_facet(project, trace.oa, trace.sa, facet)?;
|
||||||
let row = scalar::value(&fact, source_layer)
|
let row = scalar::value(&fact, source_layer)
|
||||||
.ok_or("Source row collection is not applicable; review its constructor first.")?;
|
.ok_or("Source row collection is not applicable; review its constructor first.")?;
|
||||||
if row.is_null() {
|
if row.is_null() {
|
||||||
row
|
row
|
||||||
} else {
|
} else {
|
||||||
let map = |id: Id| -> Result<Id, String> {
|
let map = |id: Id| -> Result<Id, String> {
|
||||||
if source_layer == Layer::Oa {
|
if source_layer == Layer::Oa {
|
||||||
self.references.get(&id).copied().ok_or_else(|| {
|
self.references.get(&id).copied().ok_or_else(|| {
|
||||||
format!("Choose a reference counterpart for {}.", project.named(id))
|
format!("Choose a reference counterpart for {}.", project.named(id))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let matches: Vec<_> = self
|
let matches: Vec<_> = self
|
||||||
.references
|
.references
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, sa)| **sa == id)
|
.filter(|(_, sa)| **sa == id)
|
||||||
.map(|(oa, _)| *oa)
|
.map(|(oa, _)| *oa)
|
||||||
.collect();
|
.collect();
|
||||||
if matches.len() == 1 {
|
if matches.len() == 1 {
|
||||||
Ok(matches[0])
|
Ok(matches[0])
|
||||||
} else {
|
} else {
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"Choose one unambiguous reference counterpart for {}.",
|
"Choose one unambiguous reference counterpart for {}.",
|
||||||
project.named(id)
|
project.named(id)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match facet {
|
match facet {
|
||||||
FacetSelector::Annotation(_) => {
|
FacetSelector::Annotation(_) => {
|
||||||
let mut row: MessageValue =
|
let mut row: MessageValue =
|
||||||
serde_json::from_value(row).map_err(|e| e.to_string())?;
|
serde_json::from_value(row).map_err(|e| e.to_string())?;
|
||||||
row.path = row.path.into_iter().map(map).collect::<Result<_, _>>()?;
|
row.path = row.path.into_iter().map(map).collect::<Result<_, _>>()?;
|
||||||
row.field = map(row.field)?;
|
row.field = map(row.field)?;
|
||||||
if serde_json::to_value(AnnotationKey::from(&row)).expect("key")
|
if serde_json::to_value(AnnotationKey::from(&row)).expect("key")
|
||||||
!= target_key
|
!= target_key
|
||||||
{
|
{
|
||||||
return Err("Mapped annotation address disagrees with the selected destination address.".into());
|
return Err("Mapped annotation address disagrees with the selected destination address.".into());
|
||||||
}
|
}
|
||||||
serde_json::to_value(row).expect("row")
|
serde_json::to_value(row).expect("row")
|
||||||
}
|
}
|
||||||
FacetSelector::Binding(_) => {
|
FacetSelector::Binding(_) => {
|
||||||
let row: LifelineBinding =
|
let row: LifelineBinding =
|
||||||
serde_json::from_value(row).map_err(|e| e.to_string())?;
|
serde_json::from_value(row).map_err(|e| e.to_string())?;
|
||||||
let mapped = LifelineBinding {
|
let mapped = LifelineBinding {
|
||||||
referenced: map(row.referenced)?,
|
referenced: map(row.referenced)?,
|
||||||
local: map(row.local)?,
|
local: map(row.local)?,
|
||||||
};
|
};
|
||||||
if serde_json::to_value(mapped.referenced).expect("Id") != target_key {
|
if serde_json::to_value(mapped.referenced).expect("Id") != target_key {
|
||||||
return Err("Mapped binding key disagrees with the selected destination lifeline.".into());
|
return Err("Mapped binding key disagrees with the selected destination lifeline.".into());
|
||||||
}
|
}
|
||||||
serde_json::to_value(mapped).expect("row")
|
serde_json::to_value(mapped).expect("row")
|
||||||
}
|
}
|
||||||
_ => return Err("Unsupported row.".into()),
|
_ => return Err("Unsupported row.".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let family = keyed::family(facet).ok_or("Unsupported row.")?;
|
let family = keyed::family(facet).ok_or("Unsupported row.")?;
|
||||||
let path = keyed::path(value, family)
|
let path = keyed::path(value, family)
|
||||||
.ok_or("The destination constructor has no collection for this row.")?;
|
.ok_or("The destination constructor has no collection for this row.")?;
|
||||||
let rows = value
|
let rows = value
|
||||||
.pointer_mut(&path)
|
.pointer_mut(&path)
|
||||||
.and_then(Value::as_array_mut)
|
.and_then(Value::as_array_mut)
|
||||||
.ok_or("Destination rows are unavailable.")?;
|
.ok_or("Destination rows are unavailable.")?;
|
||||||
let position = rows.iter().position(|row| match facet {
|
let position = rows.iter().position(|row| match facet {
|
||||||
FacetSelector::Annotation(_) => {
|
FacetSelector::Annotation(_) => {
|
||||||
serde_json::json!({"path": row["path"], "field": row["field"]}) == target_key
|
serde_json::json!({"path": row["path"], "field": row["field"]}) == target_key
|
||||||
}
|
}
|
||||||
FacetSelector::Binding(_) => row["referenced"] == target_key,
|
FacetSelector::Binding(_) => row["referenced"] == target_key,
|
||||||
_ => false,
|
_ => false,
|
||||||
});
|
});
|
||||||
match (position, row.is_null()) {
|
match (position, row.is_null()) {
|
||||||
(Some(index), false) => rows[index] = row,
|
(Some(index), false) => rows[index] = row,
|
||||||
(Some(index), true) => {
|
(Some(index), true) => {
|
||||||
rows.remove(index);
|
rows.remove(index);
|
||||||
}
|
}
|
||||||
(None, false) => rows.push(row),
|
(None, false) => rows.push(row),
|
||||||
(None, true) => {}
|
(None, true) => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,254 +1,254 @@
|
|||||||
//! Independent membership and complete final order; the core validates every proposal.
|
//! Independent membership and complete final order; the core validates every proposal.
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(super) fn collection(which: OrderedCollection) -> ReferenceCollection {
|
pub(super) fn collection(which: OrderedCollection) -> ReferenceCollection {
|
||||||
match which {
|
match which {
|
||||||
OrderedCollection::Steps => ReferenceCollection::Steps,
|
OrderedCollection::Steps => ReferenceCollection::Steps,
|
||||||
OrderedCollection::FragmentBranches => ReferenceCollection::FragmentBranches,
|
OrderedCollection::FragmentBranches => ReferenceCollection::FragmentBranches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(super) fn ordered(which: ReferenceCollection) -> Option<OrderedCollection> {
|
pub(super) fn ordered(which: ReferenceCollection) -> Option<OrderedCollection> {
|
||||||
match which {
|
match which {
|
||||||
ReferenceCollection::Steps => Some(OrderedCollection::Steps),
|
ReferenceCollection::Steps => Some(OrderedCollection::Steps),
|
||||||
ReferenceCollection::FragmentBranches => Some(OrderedCollection::FragmentBranches),
|
ReferenceCollection::FragmentBranches => Some(OrderedCollection::FragmentBranches),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub(super) fn read(value: &Value, which: OrderedCollection) -> Result<Vec<Id>, String> {
|
pub(super) fn read(value: &Value, which: OrderedCollection) -> Result<Vec<Id>, String> {
|
||||||
let path = keyed::path(value, keyed::Family::Member(collection(which)))
|
let path = keyed::path(value, keyed::Family::Member(collection(which)))
|
||||||
.ok_or("This ordered collection is unavailable on the destination.")?;
|
.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())
|
serde_json::from_value(value.pointer(&path).unwrap().clone()).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
fn write(value: &mut Value, which: OrderedCollection, ids: Vec<Id>) -> Result<(), String> {
|
fn write(value: &mut Value, which: OrderedCollection, ids: Vec<Id>) -> Result<(), String> {
|
||||||
let path = keyed::path(value, keyed::Family::Member(collection(which)))
|
let path = keyed::path(value, keyed::Family::Member(collection(which)))
|
||||||
.ok_or("This ordered collection is unavailable on the destination.")?;
|
.ok_or("This ordered collection is unavailable on the destination.")?;
|
||||||
*value.pointer_mut(&path).unwrap() = serde_json::to_value(ids).expect("Ordered identities");
|
*value.pointer_mut(&path).unwrap() = serde_json::to_value(ids).expect("Ordered identities");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReviewRow {
|
impl ReviewRow {
|
||||||
pub(super) fn ordered_members(
|
pub(super) fn ordered_members(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
which: OrderedCollection,
|
which: OrderedCollection,
|
||||||
) -> Result<Vec<Id>, String> {
|
) -> Result<Vec<Id>, String> {
|
||||||
read(&self.combine(project, true, true)?, which)
|
read(&self.combine(project, true, true)?, which)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn initialize_order(
|
pub(super) fn initialize_order(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
which: OrderedCollection,
|
which: OrderedCollection,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
if !self.order_drafts.contains_key(&which) {
|
if !self.order_drafts.contains_key(&which) {
|
||||||
let ids = self.ordered_members(project, which)?;
|
let ids = self.ordered_members(project, which)?;
|
||||||
self.order_drafts.insert(which, ids);
|
self.order_drafts.insert(which, ids);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn reconcile_order(
|
pub(super) fn reconcile_order(
|
||||||
&mut self,
|
&mut self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
which: OrderedCollection,
|
which: OrderedCollection,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
self.require_order_candidate(which)?;
|
self.require_order_candidate(which)?;
|
||||||
let members = self.ordered_members(project, which)?;
|
let members = self.ordered_members(project, which)?;
|
||||||
let draft = self
|
let draft = self
|
||||||
.order_drafts
|
.order_drafts
|
||||||
.get_mut(&which)
|
.get_mut(&which)
|
||||||
.ok_or("Choose a final order first.")?;
|
.ok_or("Choose a final order first.")?;
|
||||||
// This is an explicit user action. Ordinary refresh never discards an authored order.
|
// This is an explicit user action. Ordinary refresh never discards an authored order.
|
||||||
let mut seen = BTreeSet::new();
|
let mut seen = BTreeSet::new();
|
||||||
draft.retain(|id| members.contains(id) && seen.insert(*id));
|
draft.retain(|id| members.contains(id) && seen.insert(*id));
|
||||||
for id in members {
|
for id in members {
|
||||||
if seen.insert(id) {
|
if seen.insert(id) {
|
||||||
draft.push(id);
|
draft.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn require_order_candidate(&self, which: OrderedCollection) -> Result<(), String> {
|
fn require_order_candidate(&self, which: OrderedCollection) -> Result<(), String> {
|
||||||
let facet = FacetSelector::Order(which);
|
let facet = FacetSelector::Order(which);
|
||||||
if self.selected.as_ref() != Some(&facet)
|
if self.selected.as_ref() != Some(&facet)
|
||||||
|| self.decisions.get(&facet) != Some(&SemanticDecision::Candidate)
|
|| self.decisions.get(&facet) != Some(&SemanticDecision::Candidate)
|
||||||
{
|
{
|
||||||
return Err("Select Set final value for this order before changing it.".into());
|
return Err("Select Set final value for this order before changing it.".into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn move_order(
|
pub(super) fn move_order(
|
||||||
&mut self,
|
&mut self,
|
||||||
which: OrderedCollection,
|
which: OrderedCollection,
|
||||||
index: usize,
|
index: usize,
|
||||||
delta: i32,
|
delta: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
self.require_order_candidate(which)?;
|
self.require_order_candidate(which)?;
|
||||||
let ids = self
|
let ids = self
|
||||||
.order_drafts
|
.order_drafts
|
||||||
.get_mut(&which)
|
.get_mut(&which)
|
||||||
.ok_or("Choose a final order first.")?;
|
.ok_or("Choose a final order first.")?;
|
||||||
let destination = match delta {
|
let destination = match delta {
|
||||||
-1 => index.checked_sub(1),
|
-1 => index.checked_sub(1),
|
||||||
1 => index.checked_add(1),
|
1 => index.checked_add(1),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let destination = destination
|
let destination = destination
|
||||||
.filter(|i| index < ids.len() && *i < ids.len())
|
.filter(|i| index < ids.len() && *i < ids.len())
|
||||||
.ok_or("Choose an existing adjacent position in the final order.")?;
|
.ok_or("Choose an existing adjacent position in the final order.")?;
|
||||||
ids.swap(index, destination);
|
ids.swap(index, destination);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn project_order(
|
pub(super) fn project_order(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
trace: &Trace,
|
trace: &Trace,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
which: OrderedCollection,
|
which: OrderedCollection,
|
||||||
decision: SemanticDecision,
|
decision: SemanticDecision,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let current = read(value, which)?;
|
let current = read(value, which)?;
|
||||||
let supplied = match decision {
|
let supplied = match decision {
|
||||||
SemanticDecision::KeepTarget => return Ok(()),
|
SemanticDecision::KeepTarget => return Ok(()),
|
||||||
SemanticDecision::Candidate => self
|
SemanticDecision::Candidate => self
|
||||||
.order_drafts
|
.order_drafts
|
||||||
.get(&which)
|
.get(&which)
|
||||||
.cloned()
|
.cloned()
|
||||||
.ok_or("Choose a final order.")?,
|
.ok_or("Choose a final order.")?,
|
||||||
SemanticDecision::TakeSource => {
|
SemanticDecision::TakeSource => {
|
||||||
let fact =
|
let fact =
|
||||||
capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?;
|
capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?;
|
||||||
let source: Vec<Id> = serde_json::from_value(
|
let source: Vec<Id> = serde_json::from_value(
|
||||||
scalar::value(&fact, self.direction.source_layer())
|
scalar::value(&fact, self.direction.source_layer())
|
||||||
.ok_or("The source has no applicable order.")?,
|
.ok_or("The source has no applicable order.")?,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let map = self.order_reference_map()?;
|
let map = self.order_reference_map()?;
|
||||||
source
|
source
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|id| {
|
.map(|id| {
|
||||||
map.get(&id).copied().ok_or_else(|| {
|
map.get(&id).copied().ok_or_else(|| {
|
||||||
format!(
|
format!(
|
||||||
"Pair source order member {} explicitly in Reference pairs.",
|
"Pair source order member {} explicitly in Reference pairs.",
|
||||||
project.named(id)
|
project.named(id)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let actual: BTreeSet<_> = current.iter().copied().collect();
|
let actual: BTreeSet<_> = current.iter().copied().collect();
|
||||||
let desired: BTreeSet<_> = supplied.iter().copied().collect();
|
let desired: BTreeSet<_> = supplied.iter().copied().collect();
|
||||||
if desired.len() != supplied.len() {
|
if desired.len() != supplied.len() {
|
||||||
return Err(
|
return Err(
|
||||||
"The final order contains a duplicate identity; reconcile its members explicitly."
|
"The final order contains a duplicate identity; reconcile its members explicitly."
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if desired != actual {
|
if desired != actual {
|
||||||
if decision == SemanticDecision::TakeSource
|
if decision == SemanticDecision::TakeSource
|
||||||
&& desired.is_subset(&actual)
|
&& desired.is_subset(&actual)
|
||||||
&& current
|
&& current
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|id| desired.contains(id))
|
.filter(|id| desired.contains(id))
|
||||||
.copied()
|
.copied()
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
== supplied
|
== supplied
|
||||||
{
|
{
|
||||||
return Ok(());
|
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());
|
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)
|
write(value, which, supplied)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn order_reference_map(&self) -> Result<BTreeMap<Id, Id>, String> {
|
fn order_reference_map(&self) -> Result<BTreeMap<Id, Id>, String> {
|
||||||
let mut map = BTreeMap::new();
|
let mut map = BTreeMap::new();
|
||||||
for (oa, sa) in &self.references {
|
for (oa, sa) in &self.references {
|
||||||
let (a, b) = if self.direction == TransitionDirection::OaToSa {
|
let (a, b) = if self.direction == TransitionDirection::OaToSa {
|
||||||
(*oa, *sa)
|
(*oa, *sa)
|
||||||
} else {
|
} else {
|
||||||
(*sa, *oa)
|
(*sa, *oa)
|
||||||
};
|
};
|
||||||
if map.insert(a, b).is_some_and(|old| old != b) {
|
if map.insert(a, b).is_some_and(|old| old != b) {
|
||||||
return Err("Reverse reference correspondence is ambiguous.".into());
|
return Err("Reverse reference correspondence is ambiguous.".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn project_ordered_member(
|
pub(super) fn project_ordered_member(
|
||||||
&self,
|
&self,
|
||||||
project: &Project,
|
project: &Project,
|
||||||
trace: &Trace,
|
trace: &Trace,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
facet: &FacetSelector,
|
facet: &FacetSelector,
|
||||||
decision: SemanticDecision,
|
decision: SemanticDecision,
|
||||||
form_context: bool,
|
form_context: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let FacetSelector::Member {
|
let FacetSelector::Member {
|
||||||
collection: member_collection,
|
collection: member_collection,
|
||||||
keys,
|
keys,
|
||||||
} = facet
|
} = facet
|
||||||
else {
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let which = ordered(*member_collection).expect("Ordered member");
|
let which = ordered(*member_collection).expect("Ordered member");
|
||||||
let before = read(value, which)?;
|
let before = read(value, which)?;
|
||||||
self.keyed_value(project, trace, value, facet, decision)?;
|
self.keyed_value(project, trace, value, facet, decision)?;
|
||||||
let after = read(value, which)?;
|
let after = read(value, which)?;
|
||||||
let explicit = self
|
let explicit = self
|
||||||
.decisions
|
.decisions
|
||||||
.get(&FacetSelector::Order(which))
|
.get(&FacetSelector::Order(which))
|
||||||
.is_some_and(|d| *d != SemanticDecision::KeepTarget);
|
.is_some_and(|d| *d != SemanticDecision::KeepTarget);
|
||||||
if decision != SemanticDecision::TakeSource || explicit || after.len() <= before.len() {
|
if decision != SemanticDecision::TakeSource || explicit || after.len() <= before.len() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let (source_key, target_key) = if self.direction == TransitionDirection::OaToSa {
|
let (source_key, target_key) = if self.direction == TransitionDirection::OaToSa {
|
||||||
(keys.oa, keys.sa)
|
(keys.oa, keys.sa)
|
||||||
} else {
|
} else {
|
||||||
(keys.sa, keys.oa)
|
(keys.sa, keys.oa)
|
||||||
};
|
};
|
||||||
let (Some(source_key), Some(target_key)) = (source_key, target_key) else {
|
let (Some(source_key), Some(target_key)) = (source_key, target_key) else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
let fact = capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?;
|
let fact = capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?;
|
||||||
let source: Vec<Id> = serde_json::from_value(
|
let source: Vec<Id> = serde_json::from_value(
|
||||||
scalar::value(&fact, self.direction.source_layer())
|
scalar::value(&fact, self.direction.source_layer())
|
||||||
.ok_or("Source order is unavailable.")?,
|
.ok_or("Source order is unavailable.")?,
|
||||||
)
|
)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let position = source
|
let position = source
|
||||||
.iter()
|
.iter()
|
||||||
.position(|id| *id == source_key)
|
.position(|id| *id == source_key)
|
||||||
.ok_or("Source member is absent.")?;
|
.ok_or("Source member is absent.")?;
|
||||||
let map = self.order_reference_map()?;
|
let map = self.order_reference_map()?;
|
||||||
let anchor = |id: &Id| {
|
let anchor = |id: &Id| {
|
||||||
map.get(id)
|
map.get(id)
|
||||||
.and_then(|mapped| before.iter().position(|id| id == mapped))
|
.and_then(|mapped| before.iter().position(|id| id == mapped))
|
||||||
};
|
};
|
||||||
let left = source[..position].iter().rev().find_map(anchor);
|
let left = source[..position].iter().rev().find_map(anchor);
|
||||||
let right = source[position + 1..].iter().find_map(anchor);
|
let right = source[position + 1..].iter().find_map(anchor);
|
||||||
let at = match (left, right) {
|
let at = match (left, right) {
|
||||||
(Some(a), Some(b)) if a + 1 == b => Some(b),
|
(Some(a), Some(b)) if a + 1 == b => Some(b),
|
||||||
(Some(a), None) if a + 1 == before.len() => Some(a + 1),
|
(Some(a), None) if a + 1 == before.len() => Some(a + 1),
|
||||||
(None, Some(0)) => Some(0),
|
(None, Some(0)) => Some(0),
|
||||||
(None, None) if before.is_empty() => Some(0),
|
(None, None) if before.is_empty() => Some(0),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if let Some(at) = at {
|
if let Some(at) = at {
|
||||||
let mut ordered = before;
|
let mut ordered = before;
|
||||||
ordered.insert(at, target_key);
|
ordered.insert(at, target_key);
|
||||||
write(value, which, ordered)
|
write(value, which, ordered)
|
||||||
} else if form_context {
|
} else if form_context {
|
||||||
// An uncommitted membership list lets the user author a final order.
|
// An uncommitted membership list lets the user author a final order.
|
||||||
// The actual request still rejects ambiguous placement until explicitly decided.
|
// The actual request still rejects ambiguous placement until explicitly decided.
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err("Ordered member insertion has no unique unchanged anchor; select an explicit final Order in Properties.".into())
|
Err("Ordered member insertion has no unique unchanged anchor; select an explicit final Order in Properties.".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1346
-1346
File diff suppressed because it is too large
Load Diff
+782
-782
File diff suppressed because it is too large
Load Diff
+2844
-2844
File diff suppressed because it is too large
Load Diff
+12837
-12837
File diff suppressed because it is too large
Load Diff
@@ -1,58 +1,58 @@
|
|||||||
{
|
{
|
||||||
"incomplete": {
|
"incomplete": {
|
||||||
"entry": "project.arcadia",
|
"entry": "project.arcadia",
|
||||||
"files": {
|
"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; }",
|
"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}",
|
"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\"; }"
|
"project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repaired": {
|
"repaired": {
|
||||||
"entry": "project.arcadia",
|
"entry": "project.arcadia",
|
||||||
"files": {
|
"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; }",
|
"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; }",
|
"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\"; }"
|
"project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"invalid": {
|
"invalid": {
|
||||||
"entry": "project.arcadia",
|
"entry": "project.arcadia",
|
||||||
"files": {
|
"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; }",
|
"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}",
|
"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\"; }"
|
"project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"expected": [
|
"expected": [
|
||||||
{
|
{
|
||||||
"object_id": "f0660906-0000-4000-8000-000000000051",
|
"object_id": "f0660906-0000-4000-8000-000000000051",
|
||||||
"path": "work.arcadia",
|
"path": "work.arcadia",
|
||||||
"code": "non_leaf_work_exchange",
|
"code": "non_leaf_work_exchange",
|
||||||
"field": "source"
|
"field": "source"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"object_id": "f0660906-0000-4000-8000-000000000013",
|
"object_id": "f0660906-0000-4000-8000-000000000013",
|
||||||
"path": "work.arcadia",
|
"path": "work.arcadia",
|
||||||
"code": "work_without_behavior_context",
|
"code": "work_without_behavior_context",
|
||||||
"field": null
|
"field": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"object_id": "f0660906-0000-4000-8000-000000000052",
|
"object_id": "f0660906-0000-4000-8000-000000000052",
|
||||||
"path": "work.arcadia",
|
"path": "work.arcadia",
|
||||||
"code": "exchange_without_behavior_context",
|
"code": "exchange_without_behavior_context",
|
||||||
"field": null
|
"field": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"object_id": "f0660906-0000-4000-8000-000000000021",
|
"object_id": "f0660906-0000-4000-8000-000000000021",
|
||||||
"path": "work.arcadia",
|
"path": "work.arcadia",
|
||||||
"code": "mission_without_capability",
|
"code": "mission_without_capability",
|
||||||
"field": null
|
"field": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"object_id": "f0660906-0000-4000-8000-000000000020",
|
"object_id": "f0660906-0000-4000-8000-000000000020",
|
||||||
"path": "work.arcadia",
|
"path": "work.arcadia",
|
||||||
"code": "capability_without_behavior_context",
|
"code": "capability_without_behavior_context",
|
||||||
"field": null
|
"field": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user