Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
684061ec0f |
@@ -1,5 +1,4 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.rs text eol=lf
|
||||
*.toml text eol=lf
|
||||
*.lock text eol=lf
|
||||
@@ -13,6 +12,7 @@
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.zip binary
|
||||
harness/prerelease37-independent-9e8r6t-20260924/** -text whitespace=cr-at-eol
|
||||
|
||||
# Exact source/recovery oracles and archived ledgers retain their measured bytes.
|
||||
# Keep ordinary text diffs; only disable checkout/check-in newline conversion.
|
||||
@@ -90,7 +90,3 @@ harness/experiments/analysis-source-s2a-windows-20260913/** -text whitespace=-bl
|
||||
|
||||
# Preserve the pinned Iced dependency bytes and upstream manifest.
|
||||
crates/vendor/iced_widget/** -text whitespace=cr-at-eol
|
||||
|
||||
# Final overrides preserve original validation bytes after generic text rules.
|
||||
harness/experiments/navigation-validation-20261002/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
harness/reviews/navigation-validation-packet-20261002/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
|
||||
|
||||
@@ -340,18 +340,6 @@ pub(crate) fn diagnostic(source: &str, error: Error) -> Diagnostic {
|
||||
},
|
||||
}
|
||||
}
|
||||
pub(crate) fn diagnostic_with_issue(
|
||||
source: &str,
|
||||
error: Error,
|
||||
issue: Option<validation::Issue>,
|
||||
) -> Diagnostic {
|
||||
let mut result = diagnostic(source, error);
|
||||
if let Some(issue) = issue {
|
||||
result.object_id = issue.element;
|
||||
result.field = issue.field;
|
||||
}
|
||||
result
|
||||
}
|
||||
fn reserved(word: &str) -> bool {
|
||||
crate::reserved_alias(word)
|
||||
|| declaration_kind(word).is_some()
|
||||
@@ -634,7 +622,6 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
|
||||
if tokens.get(1).is_some_and(|t| t.text(source) == "2") {
|
||||
return v2_source::parse(source, tokens, limits);
|
||||
}
|
||||
let mut migration_issue = None;
|
||||
Parser {
|
||||
source,
|
||||
tokens,
|
||||
@@ -642,8 +629,8 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
|
||||
value_depth: 0,
|
||||
limits,
|
||||
}
|
||||
.document(&mut migration_issue)
|
||||
.map_err(|e| vec![diagnostic_with_issue(source, e, migration_issue)])
|
||||
.document()
|
||||
.map_err(|e| vec![diagnostic(source, e)])
|
||||
}
|
||||
fn offset_for(source: &str, line: usize, column: usize) -> usize {
|
||||
let mut offset = 0;
|
||||
@@ -1462,10 +1449,7 @@ impl Parser<'_> {
|
||||
},
|
||||
}
|
||||
}
|
||||
fn document(
|
||||
&mut self,
|
||||
migration_issue: &mut Option<validation::Issue>,
|
||||
) -> Result<ParsedDocument, Error> {
|
||||
fn document(&mut self) -> Result<ParsedDocument, Error> {
|
||||
let unit = self.unit(true, false)?;
|
||||
let header = unit.header.expect("entry header");
|
||||
let mut aliases = BTreeMap::from([(header.project.alias.clone(), header.project.id)]);
|
||||
@@ -1492,14 +1476,9 @@ impl Parser<'_> {
|
||||
.map(|(key, span)| ((d.id, key.clone()), span.start)),
|
||||
);
|
||||
}
|
||||
let decoded = resolve_declarations(
|
||||
header.project,
|
||||
&unit.declarations,
|
||||
&aliases,
|
||||
self.limits,
|
||||
migration_issue,
|
||||
)
|
||||
.map_err(|(_, error)| error)?;
|
||||
let decoded =
|
||||
resolve_declarations(header.project, &unit.declarations, &aliases, self.limits)
|
||||
.map_err(|(_, error)| error)?;
|
||||
Ok(ParsedDocument {
|
||||
project: decoded.project,
|
||||
migration: decoded.migration,
|
||||
@@ -1703,7 +1682,6 @@ pub(crate) fn resolve_declarations(
|
||||
declarations: &[Declaration],
|
||||
aliases: &BTreeMap<String, Id>,
|
||||
limits: ParseLimits,
|
||||
migration_issue: &mut Option<validation::Issue>,
|
||||
) -> Result<crate::persistence::DecodeResult, (usize, Error)> {
|
||||
let legacy = project.schema_version == 3;
|
||||
let mut legacy_elements = serde_json::Map::new();
|
||||
@@ -2011,38 +1989,6 @@ pub(crate) fn resolve_declarations(
|
||||
crate::persistence::migrate_v6(project, limits)
|
||||
})
|
||||
.map_err(|error| {
|
||||
// These frozen admission checks formerly ran in final validation.
|
||||
// Keep their diagnostic code and reference location after moving
|
||||
// them before schema normalization.
|
||||
if let crate::persistence::FileError::Model { issues, .. } = &error
|
||||
&& let Some(issue) = issues.first()
|
||||
&& let Some(code) = match issue.code.as_str() {
|
||||
"diagram_layer" => Some("diagram_layer"),
|
||||
"view_options" => Some("view_options"),
|
||||
_ => None,
|
||||
}
|
||||
{
|
||||
let index = issue
|
||||
.element
|
||||
.and_then(|id| declarations.iter().position(|d| d.id == id))
|
||||
.unwrap_or(usize::MAX);
|
||||
let offset = declarations.get(index).map_or(0, |d| {
|
||||
issue
|
||||
.field
|
||||
.as_ref()
|
||||
.and_then(|field| d.field_spans.get(field))
|
||||
.map_or(d.offset, |span| span.start)
|
||||
});
|
||||
*migration_issue = Some(issue.clone());
|
||||
return (
|
||||
index,
|
||||
Error {
|
||||
code,
|
||||
offset,
|
||||
message: issue.message.clone(),
|
||||
},
|
||||
);
|
||||
}
|
||||
(
|
||||
usize::MAX,
|
||||
Error {
|
||||
|
||||
@@ -69,8 +69,8 @@ impl Project4 {
|
||||
}
|
||||
crate::persistence::check_pre6_relations(self.relations.values())?;
|
||||
crate::persistence::check_pre6_elements(self.elements.values())?;
|
||||
let mut project = Project {
|
||||
schema_version: self.schema_version,
|
||||
Ok(Project {
|
||||
schema_version: SCHEMA_VERSION,
|
||||
id: self.id,
|
||||
alias: self.alias,
|
||||
name: self.name,
|
||||
@@ -84,11 +84,7 @@ impl Project4 {
|
||||
.into_iter()
|
||||
.map(|(id, trace)| (id, trace.into()))
|
||||
.collect(),
|
||||
};
|
||||
// Schema3's adapter also enters here, without calling `finish`.
|
||||
crate::persistence::check_pre7_diagram_admission(&project)?;
|
||||
project.schema_version = SCHEMA_VERSION;
|
||||
Ok(project)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +104,6 @@ pub(crate) fn review_defaults(project: &Project, report: &mut MigrationReport) {
|
||||
}
|
||||
|
||||
pub(crate) fn finish(mut project: Project, limits: ParseLimits) -> Result<DecodeResult, FileError> {
|
||||
// Source4 enters here directly rather than through `Project4`.
|
||||
crate::persistence::check_pre7_diagram_admission(&project)?;
|
||||
crate::persistence::check_pre6_relations(project.relations.values())?;
|
||||
crate::persistence::check_pre6_elements(project.elements.values())?;
|
||||
project.schema_version = SCHEMA_VERSION;
|
||||
|
||||
@@ -341,50 +341,6 @@ pub(crate) fn check_pre6_baseline(baseline: &SemanticBaseline) -> Result<(), Fil
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Frozen sources 3–6 admit opposite-layer placements and visibility references
|
||||
/// only in Traceability. Check before migration discards the advertised version;
|
||||
/// current ordinary projections may retain these records as latent presentation.
|
||||
pub(crate) fn check_pre7_diagram_admission(project: &Project) -> Result<(), FileError> {
|
||||
let mut issues = Vec::new();
|
||||
for diagram in project.diagrams.values() {
|
||||
let option_issues = if diagram.projection == ProjectionKind::Traceability {
|
||||
crate::view_options::validate_options_cross_layer(project, &diagram.view_options)
|
||||
} else {
|
||||
crate::view_options::validate_options(project, diagram.layer, &diagram.view_options)
|
||||
};
|
||||
for issue in option_issues {
|
||||
validation::error(
|
||||
&mut issues,
|
||||
diagram.id,
|
||||
"view_options",
|
||||
issue.field,
|
||||
issue.message,
|
||||
);
|
||||
}
|
||||
for node in diagram.nodes.values() {
|
||||
if !project.elements.get(&node.element).is_some_and(|element| {
|
||||
diagram.projection == ProjectionKind::Traceability
|
||||
|| element.layer() == diagram.layer
|
||||
}) {
|
||||
validation::error(
|
||||
&mut issues,
|
||||
node.id,
|
||||
"diagram_layer",
|
||||
"element",
|
||||
"Diagram nodes reference existing elements in the selected layer.",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(issue) = issues.into_iter().next() {
|
||||
return Err(FileError::Model {
|
||||
message: issue.message.clone(),
|
||||
issues: vec![issue],
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn migrate_v5(
|
||||
mut project: Project,
|
||||
limits: crate::language::ParseLimits,
|
||||
@@ -392,7 +348,6 @@ pub(crate) fn migrate_v5(
|
||||
if project.schema_version != 5 {
|
||||
return Err(FileError::Invalid("Expected schema_version 5.".into()));
|
||||
}
|
||||
check_pre7_diagram_admission(&project)?;
|
||||
check_pre6_relations(project.relations.values())?;
|
||||
check_pre6_elements(project.elements.values())?;
|
||||
for baseline in project
|
||||
@@ -465,7 +420,6 @@ pub(crate) fn migrate_v6(
|
||||
if project.schema_version != 6 {
|
||||
return Err(FileError::Invalid("Expected schema_version 6.".into()));
|
||||
}
|
||||
check_pre7_diagram_admission(&project)?;
|
||||
reject_source7_fields(&serde_json::to_value(&project)?).map_err(FileError::Invalid)?;
|
||||
limits.check_model(&project).map_err(FileError::Limit)?;
|
||||
project.schema_version = SCHEMA_VERSION;
|
||||
|
||||
@@ -85,7 +85,6 @@ pub(crate) fn parents(p: &Project, d: &Diagram) -> BTreeMap<Id, Option<Id>> {
|
||||
.filter(|n| {
|
||||
p.elements.get(&n.element).is_some_and(|e| {
|
||||
e.kind() != Kind::Port
|
||||
&& (d.projection == ProjectionKind::Traceability || e.layer() == d.layer)
|
||||
&& d.projection.includes(e.kind())
|
||||
&& d.projection
|
||||
.state_machine()
|
||||
|
||||
@@ -589,17 +589,6 @@ fn apply_command(
|
||||
.map(|d| d.id)
|
||||
.ok_or_else(missing)?;
|
||||
if source != *diagram {
|
||||
let destination = &p.diagrams[diagram];
|
||||
let element = p.diagrams[&source].nodes[node].element;
|
||||
if destination.projection != ProjectionKind::Traceability
|
||||
&& p.elements
|
||||
.get(&element)
|
||||
.is_none_or(|e| e.layer() != destination.layer)
|
||||
{
|
||||
return Err(ModelError(
|
||||
"Move the placement to a view in its layer or a traceability view.".into(),
|
||||
));
|
||||
}
|
||||
let placement = p
|
||||
.diagrams
|
||||
.get_mut(&source)
|
||||
|
||||
@@ -573,26 +573,16 @@ fn compile_once(
|
||||
span,
|
||||
}),
|
||||
};
|
||||
let mut migration_issue = None;
|
||||
let decoded = language::resolve_declarations(
|
||||
header.project,
|
||||
&declarations,
|
||||
&aliases,
|
||||
limits.model,
|
||||
&mut migration_issue,
|
||||
)
|
||||
.map_err(|(index, error)| {
|
||||
let path = declaration_paths.get(index).unwrap_or(&bundle.entry);
|
||||
vec![SourceDiagnostic {
|
||||
path: path.clone(),
|
||||
diagnostic: language::diagnostic_with_issue(
|
||||
&bundle.files[path],
|
||||
error,
|
||||
migration_issue,
|
||||
),
|
||||
related: Vec::new(),
|
||||
}]
|
||||
})?;
|
||||
let decoded =
|
||||
language::resolve_declarations(header.project, &declarations, &aliases, limits.model)
|
||||
.map_err(|(index, error)| {
|
||||
let path = declaration_paths.get(index).unwrap_or(&bundle.entry);
|
||||
vec![SourceDiagnostic {
|
||||
path: path.clone(),
|
||||
diagnostic: language::diagnostic(&bundle.files[path], error),
|
||||
related: Vec::new(),
|
||||
}]
|
||||
})?;
|
||||
let diagnostics = model_diagnostics(bundle, &index, &decoded.project);
|
||||
let errors: Vec<_> = diagnostics
|
||||
.iter()
|
||||
|
||||
@@ -620,14 +620,11 @@ pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
text(&mut out, d.id, "name", &d.name);
|
||||
text(&mut out, d.id, "description", &d.description);
|
||||
crate::scenario_layout::validate_diagram(p, d, &mut out);
|
||||
// Ordinary projections are reversible filters over the same authored view.
|
||||
// Retain opposite-layer visibility choices when leaving Traceability.
|
||||
let option_issues =
|
||||
if d.projection.state_machine().is_none() && d.projection.scenario().is_none() {
|
||||
crate::view_options::validate_options_cross_layer(p, &d.view_options)
|
||||
} else {
|
||||
crate::view_options::validate_options(p, d.layer, &d.view_options)
|
||||
};
|
||||
let option_issues = if d.projection == ProjectionKind::Traceability {
|
||||
crate::view_options::validate_options_cross_layer(p, &d.view_options)
|
||||
} else {
|
||||
crate::view_options::validate_options(p, d.layer, &d.view_options)
|
||||
};
|
||||
for issue in option_issues {
|
||||
error(&mut out, d.id, "view_options", issue.field, issue.message);
|
||||
}
|
||||
@@ -801,10 +798,8 @@ pub fn validate(p: &Project) -> Vec<Issue> {
|
||||
);
|
||||
}
|
||||
match p.elements.get(&n.element) {
|
||||
Some(e)
|
||||
if (d.projection.state_machine().is_none()
|
||||
&& d.projection.scenario().is_none())
|
||||
|| e.layer() == d.layer => {}
|
||||
Some(e) if d.projection == ProjectionKind::Traceability || e.layer() == d.layer => {
|
||||
}
|
||||
_ => error(
|
||||
&mut out,
|
||||
n.id,
|
||||
|
||||
@@ -218,9 +218,7 @@ pub fn validate_options(p: &Project, layer: Layer, options: &ViewOptions) -> Vec
|
||||
validate_options_in(p, Some(layer), options)
|
||||
}
|
||||
|
||||
/// Ordinary saved views retain references from either architecture layer so
|
||||
/// projection changes preserve authored visibility choices. Projection decides
|
||||
/// which layer is visible; typed State/Scenario views use layer-scoped validation.
|
||||
/// Traceability views admit references from either architecture layer.
|
||||
pub fn validate_options_cross_layer(p: &Project, options: &ViewOptions) -> Vec<OptionError> {
|
||||
validate_options_in(p, None, options)
|
||||
}
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
//! Current latent presentation must not broaden frozen source/JSON admission.
|
||||
use oa_core::{view_options::*, *};
|
||||
|
||||
fn legacy_json(project: &Project, version: u32) -> Vec<u8> {
|
||||
let mut value = serde_json::to_value(project).unwrap();
|
||||
value["schema_version"] = version.into();
|
||||
for diagram in value["diagrams"].as_object_mut().unwrap().values_mut() {
|
||||
let fields = diagram.as_object_mut().unwrap();
|
||||
fields.remove("layout");
|
||||
if version < 6 {
|
||||
fields.remove("view_options");
|
||||
}
|
||||
}
|
||||
serde_json::to_vec(&value).unwrap()
|
||||
}
|
||||
|
||||
fn model_issue(error: persistence::FileError) -> validation::Issue {
|
||||
match error {
|
||||
persistence::FileError::Model { mut issues, .. } => issues.remove(0),
|
||||
other => panic!("Expected a located model issue, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_3_through_6_reject_cross_layer_nodes_but_keep_mixed_traceability() {
|
||||
let node_id = Id::parse_str("00000000-0000-4000-8000-000000000004").unwrap();
|
||||
for version in 3..=6 {
|
||||
let source = format!(
|
||||
"arcadia {version}; project compat \"Compatibility\" id \"00000000-0000-4000-8000-000000000001\" {{\n\
|
||||
sa_function work \"Counterpart\" id \"00000000-0000-4000-8000-000000000002\" {{}}\n\
|
||||
diagram view \"OA concern\" id \"00000000-0000-4000-8000-000000000003\" {{ layer Oa; projection Traceability; }}\n\
|
||||
node occurrence id \"00000000-0000-4000-8000-000000000004\" {{ diagram @view; element @work; }}\n}}"
|
||||
);
|
||||
let mixed = language::parse(&source).unwrap();
|
||||
assert_eq!(
|
||||
persistence::decode(&legacy_json(&mixed, version)).unwrap(),
|
||||
mixed
|
||||
);
|
||||
|
||||
let ordinary = source.replace("projection Traceability", "projection Overview");
|
||||
let same_layer = ordinary.replace("layer Oa", "layer Sa");
|
||||
let ordinary_project = language::parse(&same_layer).unwrap();
|
||||
assert_eq!(
|
||||
persistence::decode(&legacy_json(&ordinary_project, version)).unwrap(),
|
||||
ordinary_project
|
||||
);
|
||||
let errors = language::parse(&ordinary).unwrap_err();
|
||||
// Source3 already reported migration errors with the offending location.
|
||||
let code = if version == 3 {
|
||||
"migration_error"
|
||||
} else {
|
||||
"diagram_layer"
|
||||
};
|
||||
assert!(
|
||||
errors.iter().any(|error| error.code == code
|
||||
&& error.line == 4
|
||||
&& error.message.contains("selected layer")),
|
||||
"{version}: {errors:?}"
|
||||
);
|
||||
|
||||
let mut latent = mixed.clone();
|
||||
let diagram = latent.diagrams.values_mut().next().unwrap();
|
||||
diagram.projection = ProjectionKind::Overview;
|
||||
let node = diagram.nodes.values().next().unwrap().id;
|
||||
assert_eq!(node, node_id);
|
||||
if version >= 4 {
|
||||
let diagnostic = errors
|
||||
.iter()
|
||||
.find(|error| error.code == "diagram_layer")
|
||||
.unwrap();
|
||||
assert_eq!(diagnostic.object_id, Some(node_id));
|
||||
assert_eq!(diagnostic.field.as_deref(), Some("element"));
|
||||
let bundle = source_project::SourceBundle {
|
||||
entry: "model.arcadia".into(),
|
||||
files: std::collections::BTreeMap::from([("model.arcadia".into(), ordinary)]),
|
||||
};
|
||||
let errors =
|
||||
source_project::compile_bundle(&bundle, source_project::ProjectLimits::default())
|
||||
.unwrap_err();
|
||||
let diagnostic = &errors
|
||||
.iter()
|
||||
.find(|error| error.diagnostic.code == "diagram_layer")
|
||||
.unwrap()
|
||||
.diagnostic;
|
||||
assert_eq!(diagnostic.object_id, Some(node_id));
|
||||
assert_eq!(diagnostic.field.as_deref(), Some("element"));
|
||||
}
|
||||
let issue = model_issue(persistence::decode(&legacy_json(&latent, version)).unwrap_err());
|
||||
assert_eq!(issue.code, "diagram_layer");
|
||||
assert_eq!(issue.element, Some(node));
|
||||
assert_eq!(issue.field.as_deref(), Some("element"));
|
||||
// The exact same record is valid under the current presentation contract.
|
||||
assert_eq!(language::parse(&language::format(&latent)).unwrap(), latent);
|
||||
assert_eq!(
|
||||
persistence::decode(&serde_json::to_vec(&latent).unwrap()).unwrap(),
|
||||
latent
|
||||
);
|
||||
let mut dangling = mixed;
|
||||
dangling
|
||||
.diagrams
|
||||
.values_mut()
|
||||
.next()
|
||||
.unwrap()
|
||||
.nodes
|
||||
.values_mut()
|
||||
.next()
|
||||
.unwrap()
|
||||
.element = Id::parse_str("00000000-0000-4000-8000-000000000005").unwrap();
|
||||
let issue = model_issue(persistence::decode(&legacy_json(&dangling, version)).unwrap_err());
|
||||
assert_eq!(issue.code, "diagram_layer");
|
||||
assert_eq!(issue.element, Some(node));
|
||||
assert_eq!(issue.field.as_deref(), Some("element"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frozen_6_checks_every_layer_sensitive_saved_selector_before_upgrade() {
|
||||
let mut project = Project::empty();
|
||||
let a = project
|
||||
.add_element(Kind::Activity, Layer::Oa, "A", None)
|
||||
.unwrap();
|
||||
let b = project
|
||||
.add_element(Kind::Activity, Layer::Oa, "B", None)
|
||||
.unwrap();
|
||||
let edge = project.relate(RelationKind::Interaction, "Interaction", a, b);
|
||||
let category = project
|
||||
.add_element(Kind::ExchangeCategory, Layer::Oa, "Work category", None)
|
||||
.unwrap();
|
||||
let participant_category = project
|
||||
.add_element(
|
||||
Kind::ExchangeCategory,
|
||||
Layer::Oa,
|
||||
"Participant category",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
*project
|
||||
.elements
|
||||
.get_mut(&participant_category)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::ExchangeCategory {
|
||||
family: ExchangeFamily::Participant,
|
||||
exchanges: vec![],
|
||||
};
|
||||
let context = project
|
||||
.add_element(Kind::Process, Layer::Oa, "Process", None)
|
||||
.unwrap();
|
||||
let diagram = project.ensure_diagram(Layer::Sa);
|
||||
for (field, options) in [
|
||||
(
|
||||
"hidden_edges",
|
||||
ViewOptions {
|
||||
hidden_edges: vec![edge],
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"work_categories",
|
||||
ViewOptions {
|
||||
work_categories: CategorySelection::Any(vec![category]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"participant_categories",
|
||||
ViewOptions {
|
||||
participant_categories: CategorySelection::Any(vec![participant_category]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
"work_contexts",
|
||||
ViewOptions {
|
||||
work_contexts: ContextSelection::Any(vec![context]),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
] {
|
||||
project.diagrams.get_mut(&diagram).unwrap().view_options = options;
|
||||
let issue = model_issue(persistence::decode(&legacy_json(&project, 6)).unwrap_err());
|
||||
assert_eq!(issue.code, "view_options");
|
||||
assert_eq!(issue.element, Some(diagram));
|
||||
assert_eq!(issue.field.as_deref(), Some(field));
|
||||
let current_source = language::format(&project);
|
||||
assert_eq!(language::parse(¤t_source).unwrap(), project);
|
||||
let source6 = current_source.replacen("arcadia 7;", "arcadia 6;", 1);
|
||||
let errors = language::parse(&source6).unwrap_err();
|
||||
let diagnostic = errors
|
||||
.iter()
|
||||
.find(|error| error.code == "view_options")
|
||||
.unwrap();
|
||||
assert_eq!(diagnostic.object_id, Some(diagram));
|
||||
assert_eq!(diagnostic.field.as_deref(), Some(field));
|
||||
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Traceability;
|
||||
assert_eq!(
|
||||
persistence::decode(&legacy_json(&project, 6)).unwrap(),
|
||||
project
|
||||
);
|
||||
let source6 = language::format(&project).replacen("arcadia 7;", "arcadia 6;", 1);
|
||||
assert_eq!(language::parse(&source6).unwrap(), project);
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Overview;
|
||||
}
|
||||
}
|
||||
@@ -1,304 +0,0 @@
|
||||
//! Populated presentation survives mixed-layer projection switches.
|
||||
//! Complements projection_switching.rs without external fixture dependencies.
|
||||
use oa_core::{
|
||||
projection::*, routing::route_diagram, source_project::*, view_options::ViewIndex, *,
|
||||
};
|
||||
|
||||
struct Populated {
|
||||
project: Project,
|
||||
diagram: Id,
|
||||
groups: [Id; 2],
|
||||
children: [[Id; 2]; 2],
|
||||
edges: [Id; 2],
|
||||
ports: [Id; 2],
|
||||
trace: Id,
|
||||
}
|
||||
|
||||
fn xy(x: f32, y: f32) -> Position {
|
||||
Position { x, y }
|
||||
}
|
||||
|
||||
fn apply(project: &mut Project, command: Command) {
|
||||
*project = prepare_command(project, &command).unwrap().project;
|
||||
}
|
||||
|
||||
fn fixture(layer: Layer, collapsed: bool) -> Populated {
|
||||
let mut p = Project::empty();
|
||||
let oa_group = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Operational response", None)
|
||||
.unwrap();
|
||||
let oa_send = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Request repair", Some(oa_group))
|
||||
.unwrap();
|
||||
let oa_receive = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Perform repair", Some(oa_group))
|
||||
.unwrap();
|
||||
let sa_group = p
|
||||
.add_element(Kind::Function, Layer::Sa, "System response", None)
|
||||
.unwrap();
|
||||
let sa_send = p
|
||||
.add_element(Kind::Function, Layer::Sa, "Dispatch repair", Some(sa_group))
|
||||
.unwrap();
|
||||
let sa_receive = p
|
||||
.add_element(Kind::Function, Layer::Sa, "Execute repair", Some(sa_group))
|
||||
.unwrap();
|
||||
let oa_edge = p.relate(
|
||||
RelationKind::Interaction,
|
||||
"Repair request",
|
||||
oa_send,
|
||||
oa_receive,
|
||||
);
|
||||
let sa_edge = p.relate(
|
||||
RelationKind::FunctionalExchange,
|
||||
"Repair dispatch",
|
||||
sa_send,
|
||||
sa_receive,
|
||||
);
|
||||
let RelationBody::FunctionalExchange {
|
||||
source_port,
|
||||
target_port,
|
||||
..
|
||||
} = p.relations[&sa_edge].body
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let trace = Id::new_v4();
|
||||
let refinement = Trace {
|
||||
id: trace,
|
||||
alias: p.fresh_alias("dispatch_refinement", "trace", trace),
|
||||
name: "Dispatch refines repair request".into(),
|
||||
description: "Keep this cross-layer trace and its authored route.".into(),
|
||||
oa: oa_send,
|
||||
sa: sa_send,
|
||||
kind: TraceKind::Refinement,
|
||||
baseline: Some(TraceBaseline {
|
||||
oa: p.properties(oa_send).unwrap(),
|
||||
sa: p.properties(sa_send).unwrap(),
|
||||
semantic: None,
|
||||
}),
|
||||
};
|
||||
apply(&mut p, Command::CreateTrace(refinement));
|
||||
let d = p.ensure_diagram(layer);
|
||||
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Traceability;
|
||||
let oa_group_node = p.place(d, oa_group, xy(0., 0.));
|
||||
let oa_send_node = p.place(d, oa_send, xy(40., 120.));
|
||||
let oa_receive_node = p.place(d, oa_receive, xy(360., 120.));
|
||||
let sa_group_node = p.place(d, sa_group, xy(0., 500.));
|
||||
let sa_send_node = p.place(d, sa_send, xy(40., 620.));
|
||||
let sa_receive_node = p.place(d, sa_receive, xy(360., 620.));
|
||||
for (node, port, side, offset) in [
|
||||
(sa_send_node, source_port, PortAnchorSide::Bottom, 0.25),
|
||||
(sa_receive_node, target_port, PortAnchorSide::Top, 0.75),
|
||||
] {
|
||||
apply(
|
||||
&mut p,
|
||||
Command::SetPortAnchor {
|
||||
diagram: d,
|
||||
node,
|
||||
anchor: PortAnchor { port, side, offset },
|
||||
},
|
||||
);
|
||||
}
|
||||
for (edge, y, port_backed) in [
|
||||
(oa_edge, 260., false),
|
||||
(sa_edge, 860., true),
|
||||
(trace, 420., false),
|
||||
] {
|
||||
apply(
|
||||
&mut p,
|
||||
Command::SetEdgeRoute {
|
||||
diagram: d,
|
||||
route: EdgeRoute {
|
||||
edge,
|
||||
source_attachment: (!port_backed).then_some(EdgeAttachment {
|
||||
side: PortAnchorSide::Right,
|
||||
offset: 0.3,
|
||||
}),
|
||||
target_attachment: (!port_backed).then_some(EdgeAttachment {
|
||||
side: PortAnchorSide::Left,
|
||||
offset: 0.7,
|
||||
}),
|
||||
waypoints: vec![xy(680., y), xy(740., y)],
|
||||
label: Some(EdgeLabel {
|
||||
fraction: 0.4,
|
||||
offset: xy(13., -27.),
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
for node in [oa_group_node, sa_group_node] {
|
||||
let mut placement = p.diagrams[&d].nodes[&node].clone();
|
||||
placement.collapsed = collapsed;
|
||||
placement.size = Size {
|
||||
width: 310.,
|
||||
height: 170.,
|
||||
};
|
||||
apply(
|
||||
&mut p,
|
||||
Command::EditNode {
|
||||
diagram: d,
|
||||
original: node,
|
||||
node: placement,
|
||||
},
|
||||
);
|
||||
}
|
||||
validate_candidate(&p).unwrap();
|
||||
Populated {
|
||||
project: p,
|
||||
diagram: d,
|
||||
groups: [oa_group_node, sa_group_node],
|
||||
children: [
|
||||
[oa_send_node, oa_receive_node],
|
||||
[sa_send_node, sa_receive_node],
|
||||
],
|
||||
edges: [oa_edge, sa_edge],
|
||||
ports: [source_port, target_port],
|
||||
trace,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_round_trips(p: &Project) {
|
||||
assert_eq!(language::parse(&language::format(p)).unwrap(), *p);
|
||||
assert_eq!(
|
||||
persistence::decode(&serde_json::to_vec(p).unwrap()).unwrap(),
|
||||
*p
|
||||
);
|
||||
assert_eq!(
|
||||
compile_bundle(&format_bundle(p), ProjectLimits::default())
|
||||
.unwrap()
|
||||
.decoded
|
||||
.project,
|
||||
*p
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_layer_only(p: &Project, d: Id, view: &ProjectedView) {
|
||||
let layer = p.diagrams[&d].layer;
|
||||
for node in &view.nodes {
|
||||
assert_eq!(p.elements[&node.element].layer(), layer);
|
||||
if let Some(parent) = node.visual_parent {
|
||||
let parent = view.nodes.iter().find(|n| n.id == parent).unwrap();
|
||||
assert_eq!(p.elements[&parent.element].layer(), layer);
|
||||
}
|
||||
}
|
||||
for port in &view.ports {
|
||||
assert_eq!(p.elements[&port.element].layer(), layer);
|
||||
let owner = view.nodes.iter().find(|n| n.id == port.owner).unwrap();
|
||||
assert_eq!(p.elements[&owner.element].layer(), layer);
|
||||
}
|
||||
for edge in &view.edges {
|
||||
assert!(
|
||||
!edge.trace,
|
||||
"cross-layer trace leaked into ordinary projection"
|
||||
);
|
||||
assert_eq!(p.layer_of(edge.id), Some(layer));
|
||||
for endpoint in [edge.source, edge.target] {
|
||||
let owner = view.nodes.iter().find(|n| n.id == endpoint).unwrap();
|
||||
assert_eq!(p.elements[&owner.element].layer(), layer);
|
||||
}
|
||||
for port in [edge.source_port, edge.target_port].into_iter().flatten() {
|
||||
assert_eq!(p.elements[&port].layer(), layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn populated_mixed_projection_switch_conserves_authorship_and_restores_geometry() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
for collapsed in [false, true] {
|
||||
let f = fixture(layer, collapsed);
|
||||
let baseline = &f.project;
|
||||
let d = f.diagram;
|
||||
let before_view = project_view(baseline, d);
|
||||
let before_routes = route_diagram(baseline, &baseline.diagrams[&d], &before_view);
|
||||
// Guard against a vacuous fixture: Traceability deliberately flattens parents.
|
||||
assert_eq!(before_view.nodes.len(), 6);
|
||||
assert!(before_view.nodes.iter().all(|n| n.visual_parent.is_none()));
|
||||
for id in [f.edges[0], f.edges[1], f.trace] {
|
||||
assert!(before_view.edges.iter().any(|e| e.id == id));
|
||||
let route = baseline.diagrams[&d]
|
||||
.routes
|
||||
.iter()
|
||||
.find(|r| r.edge == id)
|
||||
.unwrap();
|
||||
assert_eq!(route.waypoints.len(), 2);
|
||||
assert!(route.label.is_some());
|
||||
}
|
||||
for port in f.ports {
|
||||
assert!(before_view.ports.iter().any(|p| p.element == port));
|
||||
}
|
||||
assert_round_trips(baseline);
|
||||
for projection in [
|
||||
ProjectionKind::Overview,
|
||||
ProjectionKind::Decomposition,
|
||||
ProjectionKind::Allocation,
|
||||
ProjectionKind::FunctionalDataflow,
|
||||
ProjectionKind::Capabilities,
|
||||
] {
|
||||
let mut expected = baseline.clone();
|
||||
expected.diagrams.get_mut(&d).unwrap().projection = projection;
|
||||
let mut session = Session::new(baseline.clone());
|
||||
session
|
||||
.execute(Command::EditDiagram(d, expected.diagrams[&d].clone()))
|
||||
.unwrap();
|
||||
assert_eq!(session.project(), &expected);
|
||||
assert_round_trips(session.project());
|
||||
let view = project_view(session.project(), d);
|
||||
assert_eq!(
|
||||
view,
|
||||
project_view_with_index(&expected, d, &ViewIndex::new(&expected))
|
||||
);
|
||||
assert_layer_only(session.project(), d, &view);
|
||||
if projection == ProjectionKind::Overview {
|
||||
let own = if layer == Layer::Oa { 0 } else { 1 };
|
||||
assert!(view.nodes.iter().any(|n| n.id == f.groups[own]));
|
||||
for child in f.children[own] {
|
||||
if collapsed {
|
||||
assert!(!view.nodes.iter().any(|n| n.id == child));
|
||||
} else {
|
||||
assert_eq!(
|
||||
view.nodes
|
||||
.iter()
|
||||
.find(|n| n.id == child)
|
||||
.unwrap()
|
||||
.visual_parent,
|
||||
Some(f.groups[own])
|
||||
);
|
||||
}
|
||||
}
|
||||
if !collapsed {
|
||||
assert!(view.edges.iter().any(|e| e.id == f.edges[own]));
|
||||
if layer == Layer::Sa {
|
||||
assert_eq!(view.ports.len(), 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
session.undo();
|
||||
assert_eq!(session.project(), baseline);
|
||||
assert!(
|
||||
!session.can_undo(),
|
||||
"projection change must be one model history step"
|
||||
);
|
||||
assert_round_trips(session.project());
|
||||
session.redo();
|
||||
assert_eq!(session.project(), &expected);
|
||||
session
|
||||
.execute(Command::EditDiagram(d, baseline.diagrams[&d].clone()))
|
||||
.unwrap();
|
||||
assert_eq!(session.project(), baseline);
|
||||
let restored = project_view(session.project(), d);
|
||||
assert_eq!(restored, before_view);
|
||||
assert_eq!(
|
||||
route_diagram(
|
||||
session.project(),
|
||||
&session.project().diagrams[&d],
|
||||
&restored
|
||||
),
|
||||
before_routes
|
||||
);
|
||||
assert_round_trips(session.project());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
use oa_core::{source_project::*, view_options::*, *};
|
||||
|
||||
fn fixture(layer: Layer) -> (Project, Id, Id, Id, Id) {
|
||||
let mut p = Project::empty();
|
||||
let oa = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Assess alarm", None)
|
||||
.unwrap();
|
||||
let oa2 = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Respond", None)
|
||||
.unwrap();
|
||||
let sa = p
|
||||
.add_element(Kind::Function, Layer::Sa, "Assess alarm", None)
|
||||
.unwrap();
|
||||
let edge = p.relate(RelationKind::Interaction, "Alarm", oa, oa2);
|
||||
let category = p
|
||||
.add_element(Kind::ExchangeCategory, Layer::Oa, "Alarm data", None)
|
||||
.unwrap();
|
||||
*p.elements.get_mut(&category).unwrap().shared_mut().unwrap() = SharedBody::ExchangeCategory {
|
||||
family: ExchangeFamily::Work,
|
||||
exchanges: vec![edge],
|
||||
};
|
||||
let context = p
|
||||
.add_element(Kind::Process, Layer::Oa, "Alarm handling", None)
|
||||
.unwrap();
|
||||
let d = p.ensure_diagram(layer);
|
||||
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Traceability;
|
||||
for (i, element) in [oa, oa2, sa].into_iter().enumerate() {
|
||||
p.place(
|
||||
d,
|
||||
element,
|
||||
Position {
|
||||
x: 300. * i as f32,
|
||||
y: 40.,
|
||||
},
|
||||
);
|
||||
}
|
||||
p.diagrams.get_mut(&d).unwrap().view_options = ViewOptions {
|
||||
hidden_edges: vec![edge],
|
||||
work_categories: CategorySelection::Any(vec![category]),
|
||||
work_contexts: ContextSelection::Any(vec![context]),
|
||||
..Default::default()
|
||||
};
|
||||
validate_candidate(&p).unwrap();
|
||||
(p, d, oa, sa, category)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projection_switching_preserves_complete_model_history_and_all_serializations() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let (baseline, diagram, _, _, _) = fixture(layer);
|
||||
for projection in [
|
||||
ProjectionKind::Overview,
|
||||
ProjectionKind::Decomposition,
|
||||
ProjectionKind::Allocation,
|
||||
ProjectionKind::FunctionalDataflow,
|
||||
ProjectionKind::Capabilities,
|
||||
] {
|
||||
let mut expected = baseline.clone();
|
||||
expected.diagrams.get_mut(&diagram).unwrap().projection = projection;
|
||||
let mut session = Session::new(baseline.clone());
|
||||
session
|
||||
.execute(Command::EditDiagram(
|
||||
diagram,
|
||||
expected.diagrams[&diagram].clone(),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(session.project(), &expected);
|
||||
let view = projection::project_view(session.project(), diagram);
|
||||
assert!(
|
||||
view.nodes
|
||||
.iter()
|
||||
.all(|n| expected.elements[&n.element].layer() == layer)
|
||||
);
|
||||
assert_eq!(
|
||||
view,
|
||||
projection::project_view_with_index(&expected, diagram, &ViewIndex::new(&expected))
|
||||
);
|
||||
let source = language::format(&expected);
|
||||
assert_eq!(language::parse(&source).unwrap(), expected);
|
||||
assert_eq!(
|
||||
persistence::decode(&serde_json::to_vec(&expected).unwrap()).unwrap(),
|
||||
expected
|
||||
);
|
||||
let bundle = format_bundle(&expected);
|
||||
assert_eq!(
|
||||
compile_bundle(&bundle, ProjectLimits::default())
|
||||
.unwrap()
|
||||
.decoded
|
||||
.project,
|
||||
expected
|
||||
);
|
||||
session.undo();
|
||||
assert_eq!(session.project(), &baseline);
|
||||
assert!(!session.can_undo());
|
||||
session.redo();
|
||||
assert_eq!(session.project(), &expected);
|
||||
session
|
||||
.execute(Command::EditDiagram(
|
||||
diagram,
|
||||
baseline.diagrams[&diagram].clone(),
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(session.project(), &baseline);
|
||||
assert_eq!(
|
||||
projection::project_view(session.project(), diagram),
|
||||
projection::project_view(&baseline, diagram)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projection_switching_does_not_admit_missing_elements_or_invalid_filter_kinds() {
|
||||
let (mut p, d, oa, _, category) = fixture(Layer::Sa);
|
||||
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Allocation;
|
||||
validate_candidate(&p).unwrap();
|
||||
let valid = p.clone();
|
||||
let node = p.diagrams[&d]
|
||||
.nodes
|
||||
.values()
|
||||
.find(|n| n.element == oa)
|
||||
.unwrap()
|
||||
.id;
|
||||
p.diagrams
|
||||
.get_mut(&d)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&node)
|
||||
.unwrap()
|
||||
.element = Id::new_v4();
|
||||
assert!(validate_candidate(&p).is_err());
|
||||
p = valid.clone();
|
||||
p.diagrams.get_mut(&d).unwrap().view_options.work_categories = CategorySelection::Any(vec![oa]);
|
||||
assert!(validate_candidate(&p).is_err());
|
||||
p = valid;
|
||||
p.elements.remove(&category);
|
||||
assert!(validate_candidate(&p).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projection_switching_keeps_typed_state_membership_strict() {
|
||||
let (mut p, d, _, _, _) = fixture(Layer::Sa);
|
||||
let system = p
|
||||
.add_element(Kind::System, Layer::Sa, "System", None)
|
||||
.unwrap();
|
||||
let machine = p
|
||||
.add_element(Kind::StateMachine, Layer::Sa, "Modes", Some(system))
|
||||
.unwrap();
|
||||
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::StateMachine { machine };
|
||||
assert!(
|
||||
validation::validate(&p)
|
||||
.iter()
|
||||
.any(|i| i.code == "state_diagram_member")
|
||||
);
|
||||
assert!(validate_candidate(&p).is_err());
|
||||
}
|
||||
@@ -67,18 +67,7 @@ fn source6_traceability_default_keeps_both_layers_and_indexed_projection_matches
|
||||
.any(|e| e.id == interaction)
|
||||
);
|
||||
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Overview;
|
||||
validate_candidate(&p).unwrap();
|
||||
let ordinary = projection::project_view_with_index(&p, d, &index);
|
||||
assert!(
|
||||
ordinary
|
||||
.nodes
|
||||
.iter()
|
||||
.all(|n| p.elements[&n.element].layer() == Layer::Oa)
|
||||
);
|
||||
assert!(ordinary.edges.iter().any(|e| e.id == interaction));
|
||||
assert!(!ordinary.edges.iter().any(|e| e.id == fe));
|
||||
assert_eq!(p.diagrams[&d].nodes.len(), 4);
|
||||
assert_eq!(p.diagrams[&d].view_options.hidden_edges, vec![fe]);
|
||||
assert!(validate_candidate(&p).is_err());
|
||||
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Traceability;
|
||||
p.diagrams.get_mut(&d).unwrap().view_options.relation_kinds = RelationKinds::Only(vec![]);
|
||||
assert!(
|
||||
|
||||
@@ -4,12 +4,8 @@ pub(crate) mod architecture_authoring;
|
||||
mod architecture_authoring_tests;
|
||||
#[cfg(test)]
|
||||
mod behavior_removal_tests;
|
||||
#[cfg(test)]
|
||||
mod canvas_selection_tests;
|
||||
mod checkpoints;
|
||||
#[cfg(test)]
|
||||
mod connection_begin_regressions;
|
||||
#[cfg(test)]
|
||||
mod diagram_focus_tests;
|
||||
pub(crate) mod diagram_removal;
|
||||
#[cfg(test)]
|
||||
@@ -23,8 +19,6 @@ mod hosted;
|
||||
pub(crate) mod multiselect;
|
||||
#[cfg(test)]
|
||||
mod multiselect_tests;
|
||||
#[cfg(test)]
|
||||
mod placement_relocation_regressions;
|
||||
mod reference_authoring;
|
||||
#[cfg(test)]
|
||||
mod review_navigation_tests;
|
||||
@@ -387,7 +381,6 @@ pub enum Message {
|
||||
Behavior(crate::behavior::Event),
|
||||
Layer(Layer),
|
||||
Select(Id),
|
||||
CanvasSelect(crate::route_edit::Context, Id),
|
||||
EditProperties(Id),
|
||||
SelectNode(Id),
|
||||
DiagramSelection(multiselect::Event),
|
||||
@@ -954,16 +947,6 @@ impl App {
|
||||
return true;
|
||||
}
|
||||
let original = self.selected;
|
||||
let canvas_surface = self.architecture_surface();
|
||||
let placement_owner = self.draft.as_ref().and_then(|draft| match draft.kind {
|
||||
crate::forms::RecordKind::Node(owner) => self
|
||||
.project()
|
||||
.diagrams
|
||||
.get(&owner)
|
||||
.filter(|diagram| diagram.nodes.contains_key(&draft.original))
|
||||
.map(|diagram| diagram.id),
|
||||
_ => None,
|
||||
});
|
||||
let result = self
|
||||
.draft
|
||||
.as_ref()
|
||||
@@ -1018,16 +1001,13 @@ impl App {
|
||||
if let Some(layer) = self.project().layer_of(id) {
|
||||
self.layer = layer;
|
||||
}
|
||||
let selected_surface = self
|
||||
if let Some((diagram, layer, node)) = self
|
||||
.project()
|
||||
.diagrams
|
||||
.values()
|
||||
.find(|d| d.id == id || d.nodes.contains_key(&id))
|
||||
.map(|d| (d.id, d.layer, d.nodes.contains_key(&id).then_some(id)));
|
||||
let relocated_placement = placement_owner
|
||||
.zip(selected_surface.and_then(|(diagram, _, node)| node.map(|_| diagram)))
|
||||
.is_some_and(|(before, after)| before != after);
|
||||
if let Some((diagram, layer, node)) = selected_surface {
|
||||
.map(|d| (d.id, d.layer, d.nodes.contains_key(&id).then_some(id)))
|
||||
{
|
||||
self.layer = layer;
|
||||
if self.page == Page::Behavior
|
||||
&& let Some(machine) =
|
||||
@@ -1041,20 +1021,6 @@ impl App {
|
||||
self.selected_node = node;
|
||||
}
|
||||
}
|
||||
// Ordinary Properties edits retain their saved architecture view.
|
||||
// Follow an explicitly edited diagram identity/layer, never an
|
||||
// inferred replacement for an unrelated queued canvas click.
|
||||
// An accepted placement move explicitly chooses its new view.
|
||||
// Same-owner edits (including a new UUID) still retain inspection.
|
||||
if !relocated_placement && let Some((diagram, _, _)) = canvas_surface {
|
||||
let diagram = if diagram == original { id } else { diagram };
|
||||
if let Some(view) = self.project().diagrams.get(&diagram)
|
||||
&& view.projection.is_architecture()
|
||||
{
|
||||
self.layer = view.layer;
|
||||
self.active_diagram = Some(diagram);
|
||||
}
|
||||
}
|
||||
self.sync();
|
||||
self.status = "Properties applied".into();
|
||||
true
|
||||
@@ -1205,13 +1171,7 @@ impl App {
|
||||
if id != self.selected {
|
||||
self.selected_attachment = None;
|
||||
}
|
||||
if self.selected == id
|
||||
&& !process_preview
|
||||
&& self
|
||||
.project()
|
||||
.layer_of(id)
|
||||
.is_none_or(|layer| layer == self.layer)
|
||||
{
|
||||
if self.selected == id && !process_preview {
|
||||
return;
|
||||
}
|
||||
if !self.apply() {
|
||||
@@ -1257,59 +1217,6 @@ impl App {
|
||||
}
|
||||
self.refresh_draft();
|
||||
}
|
||||
/// The saved architecture surface, independent of the Inspector's object.
|
||||
fn architecture_surface(&self) -> Option<(Id, Layer, ProjectionKind)> {
|
||||
if self.page != Page::Diagram {
|
||||
return None;
|
||||
}
|
||||
let diagram = self.project().diagrams.get(&self.active_diagram?)?;
|
||||
diagram.projection.is_architecture().then_some((
|
||||
diagram.id,
|
||||
diagram.layer,
|
||||
diagram.projection,
|
||||
))
|
||||
}
|
||||
fn canvas_target_visible(&self, diagram: Id, id: Id) -> bool {
|
||||
let view = project_view(self.project(), diagram);
|
||||
view.nodes
|
||||
.iter()
|
||||
.any(|node| node.id == id || node.element == id)
|
||||
|| view.ports.iter().any(|port| port.element == id)
|
||||
|| view.edges.iter().any(|edge| edge.id == id)
|
||||
}
|
||||
/// Inspect a contextual target without following its model layer. An edit
|
||||
/// may invalidate a previously visible occurrence; accept the edit but
|
||||
/// refuse that old hit. Invisible Show-in-view targets remain intentional.
|
||||
fn inspect_canvas_target(&mut self, id: Id) -> bool {
|
||||
if self.selected == id {
|
||||
return true;
|
||||
}
|
||||
let surface = self.architecture_surface();
|
||||
let accepted_draft = self.draft_dirty();
|
||||
let was_visible = accepted_draft
|
||||
&& surface.is_some_and(|(diagram, _, _)| self.canvas_target_visible(diagram, id));
|
||||
if !self.project().contains(id) || !self.apply() {
|
||||
return false;
|
||||
}
|
||||
if !self.project().contains(id)
|
||||
|| (was_visible
|
||||
&& (self.architecture_surface() != surface
|
||||
|| !surface
|
||||
.is_some_and(|(diagram, _, _)| self.canvas_target_visible(diagram, id))))
|
||||
{
|
||||
if accepted_draft {
|
||||
self.status =
|
||||
"Properties applied. The view changed; select a visible target again.".into();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
self.selected = id;
|
||||
self.selected_node = None;
|
||||
self.selected_attachment = None;
|
||||
self.selected_bend = None;
|
||||
self.refresh_draft();
|
||||
true
|
||||
}
|
||||
fn reveal_explorer_selection(&mut self) {
|
||||
if let Some(group) = self.explorer_index.group(self.selected) {
|
||||
self.explorer_groups_collapsed.remove(&group);
|
||||
@@ -1456,11 +1363,6 @@ impl App {
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
if let Message::CanvasSelect(captured, _) = &message
|
||||
&& (self.page != Page::Diagram || self.route_context() != Some(*captured))
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
// Selection can change without advancing the captured canvas context.
|
||||
if let Message::Route(
|
||||
crate::route_edit::Event::Commit(captured, changed, ..)
|
||||
@@ -2216,7 +2118,6 @@ impl App {
|
||||
Message::Command(Command::Layout(diagram)) => self.saved_scenario_diagram(*diagram),
|
||||
Message::Projection(projection) => active || projection.scenario().is_some(),
|
||||
Message::SelectNode(_)
|
||||
| Message::CanvasSelect(..)
|
||||
| Message::PlaceSelected
|
||||
| Message::HideSelected
|
||||
| Message::ToggleCollapse
|
||||
@@ -2817,26 +2718,10 @@ impl App {
|
||||
e.kind() != Kind::Port && d.projection.includes(e.kind())
|
||||
})
|
||||
});
|
||||
// Draw acts on the visible current view even when opened from
|
||||
// Explorer; explicit Select/Open actions still navigate.
|
||||
let draw_in_current_view = match &*action {
|
||||
Message::Connection(crate::connection_edit::Event::Begin(tool, source)) => {
|
||||
*source == menu.request.object
|
||||
&& self.active_diagram.is_some_and(|diagram| {
|
||||
crate::connection_edit::can_begin(
|
||||
self.project(),
|
||||
diagram,
|
||||
*tool,
|
||||
*source,
|
||||
)
|
||||
})
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
if show_in_mixed_view || draw_in_current_view || menu.request.canvas.is_some() {
|
||||
if !self.inspect_canvas_target(menu.request.object) {
|
||||
return Task::none();
|
||||
}
|
||||
if show_in_mixed_view {
|
||||
self.selected = menu.request.object;
|
||||
self.selected_attachment = None;
|
||||
self.refresh_draft();
|
||||
} else {
|
||||
self.select(menu.request.object);
|
||||
}
|
||||
@@ -2848,15 +2733,7 @@ impl App {
|
||||
}
|
||||
self.reconcile_multiselect();
|
||||
self.selected_bend = menu.request.bend.map(|i| (menu.request.object, i));
|
||||
let action = match *action {
|
||||
Message::EditProperties(id)
|
||||
if menu.request.canvas.is_some() && id == menu.request.object =>
|
||||
{
|
||||
Message::InspectorTab(crate::navigation::InspectorTab::Properties)
|
||||
}
|
||||
action => action,
|
||||
};
|
||||
return self.update(action);
|
||||
return self.update(*action);
|
||||
}
|
||||
Message::CreateContextElement(kind, child) => {
|
||||
self.create_kind = kind;
|
||||
@@ -3012,14 +2889,6 @@ impl App {
|
||||
self.status = "Property draft reverted".into();
|
||||
}
|
||||
Message::Select(id) => self.select(id),
|
||||
Message::CanvasSelect(context, id) => {
|
||||
let view = project_view(self.project(), context.diagram);
|
||||
if view.ports.iter().any(|port| port.element == id)
|
||||
|| view.edges.iter().any(|edge| edge.id == id)
|
||||
{
|
||||
self.inspect_canvas_target(id);
|
||||
}
|
||||
}
|
||||
Message::DiagramSelection(event) => self.multiselect_event(event),
|
||||
Message::EditProperties(id) => {
|
||||
self.select(id);
|
||||
@@ -3039,23 +2908,10 @@ impl App {
|
||||
return Task::none();
|
||||
}
|
||||
let original = self.selected;
|
||||
let surface = self.architecture_surface();
|
||||
let accepted_draft = self.draft_dirty();
|
||||
if !self.apply() {
|
||||
return Task::none();
|
||||
}
|
||||
let id = if id == original { self.selected } else { id };
|
||||
if let Some((diagram, _, _)) = surface
|
||||
&& (self.architecture_surface() != surface
|
||||
|| !self.canvas_target_visible(diagram, id))
|
||||
{
|
||||
if accepted_draft {
|
||||
self.status =
|
||||
"Properties applied. The view changed; select a visible target again."
|
||||
.into();
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
if let Some(d) = self
|
||||
.active_diagram
|
||||
.and_then(|d| self.project().diagrams.get(&d))
|
||||
@@ -3480,7 +3336,7 @@ impl App {
|
||||
self.workflow = Some(crate::workflow::Workflow::relationship_kind(
|
||||
self.project(),
|
||||
self.selected,
|
||||
self.project().layer_of(self.selected).unwrap_or(self.layer),
|
||||
self.layer,
|
||||
kind,
|
||||
incoming,
|
||||
));
|
||||
@@ -4506,18 +4362,13 @@ impl App {
|
||||
self.status = "Select an SA function or participant, then choose a typed port action in Properties.".into();
|
||||
return;
|
||||
}
|
||||
let creation_layer = if child {
|
||||
self.project().layer_of(self.selected).unwrap_or(self.layer)
|
||||
} else {
|
||||
self.layer
|
||||
};
|
||||
let allocated = child
|
||||
&& self.create_kind.is_work()
|
||||
&& self
|
||||
.project()
|
||||
.elements
|
||||
.get(&self.selected)
|
||||
.is_some_and(|e| e.layer() == creation_layer && e.kind().is_performer());
|
||||
.is_some_and(|e| e.layer() == self.layer && e.kind().is_performer());
|
||||
let parent = if child && !allocated {
|
||||
Some(self.selected)
|
||||
} else {
|
||||
@@ -4525,7 +4376,7 @@ impl App {
|
||||
};
|
||||
let Some(e) = self.project().make_element(
|
||||
self.create_kind,
|
||||
creation_layer,
|
||||
self.layer,
|
||||
&format!("New {}", self.create_kind.label().to_lowercase()),
|
||||
parent,
|
||||
) else {
|
||||
@@ -4554,16 +4405,16 @@ impl App {
|
||||
.project()
|
||||
.diagrams
|
||||
.values()
|
||||
.find(|d| d.layer == creation_layer && d.projection == preferred)
|
||||
.find(|d| d.layer == self.layer && d.projection == preferred)
|
||||
.map(|d| d.id);
|
||||
if diagram_target.is_none() {
|
||||
let view = Id::new_v4();
|
||||
commands.push(Command::CreateDiagram(Diagram {
|
||||
id: view,
|
||||
alias: self.project().fresh_alias("engineering_view", "view", view),
|
||||
name: format!("{} {preferred}", creation_layer),
|
||||
name: format!("{} {preferred}", self.layer),
|
||||
description: String::new(),
|
||||
layer: creation_layer,
|
||||
layer: self.layer,
|
||||
projection: preferred,
|
||||
layout: None,
|
||||
routes: Vec::new(),
|
||||
@@ -4696,13 +4547,7 @@ impl App {
|
||||
));
|
||||
}
|
||||
if self.execute(Command::Batch(commands)) {
|
||||
let changed_view = self.active_diagram != diagram_target;
|
||||
self.active_diagram = diagram_target;
|
||||
if changed_view
|
||||
&& let Some(view) = diagram_target.and_then(|id| self.project().diagrams.get(&id))
|
||||
{
|
||||
self.layer = view.layer;
|
||||
}
|
||||
self.selected = id;
|
||||
self.inspector_tab = crate::navigation::InspectorTab::Properties;
|
||||
self.refresh_draft();
|
||||
@@ -4716,7 +4561,7 @@ impl App {
|
||||
self.workflow = Some(crate::workflow::Workflow::relationship(
|
||||
self.project(),
|
||||
self.selected,
|
||||
self.project().layer_of(self.selected).unwrap_or(self.layer),
|
||||
self.layer,
|
||||
));
|
||||
}
|
||||
fn add_trace(&mut self) {
|
||||
@@ -5137,30 +4982,8 @@ impl App {
|
||||
epoch: self.canvas_epoch,
|
||||
})
|
||||
}
|
||||
// Admission belongs to the caller: palette choice, contextual source, or
|
||||
// an already admitted session whose complete context is still current.
|
||||
fn arm_connection(
|
||||
&mut self,
|
||||
context: crate::route_edit::Context,
|
||||
tool: crate::connection_tool::Tool,
|
||||
) -> crate::route_edit::Context {
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
let context = crate::route_edit::Context {
|
||||
epoch: self.canvas_epoch,
|
||||
..context
|
||||
};
|
||||
self.connection = Some(crate::connection_edit::Session::new(
|
||||
self.project(),
|
||||
&project_view(self.project(), context.diagram),
|
||||
context,
|
||||
tool,
|
||||
));
|
||||
self.selected_bend = None;
|
||||
self.status = tool.hint().into();
|
||||
context
|
||||
}
|
||||
fn edit_connection(&mut self, event: crate::connection_edit::Event) {
|
||||
use crate::connection_edit::Event;
|
||||
use crate::connection_edit::{Event, Session};
|
||||
let Some(context) = self.route_context() else {
|
||||
return;
|
||||
};
|
||||
@@ -5176,13 +4999,10 @@ impl App {
|
||||
}
|
||||
match event {
|
||||
Event::Begin(tool, source) => {
|
||||
if !crate::connection_edit::can_begin(self.project(), context.diagram, tool, source)
|
||||
{
|
||||
self.status = "Choose a visible compatible source in this diagram.".into();
|
||||
return;
|
||||
self.edit_connection(Event::Choose(Some(tool)));
|
||||
if let Some(context) = self.route_context() {
|
||||
self.edit_connection(Event::Endpoint(context, source));
|
||||
}
|
||||
let context = self.arm_connection(context, tool);
|
||||
self.edit_connection(Event::Endpoint(context, source));
|
||||
}
|
||||
Event::Choose(Some(tool)) => {
|
||||
if !crate::connection_tool::Tool::for_layer(self.layer).contains(&tool) {
|
||||
@@ -5196,7 +5016,16 @@ impl App {
|
||||
{
|
||||
return;
|
||||
}
|
||||
self.arm_connection(context, tool);
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
let context = self.route_context().unwrap();
|
||||
self.connection = Some(Session::new(
|
||||
self.project(),
|
||||
&project_view(self.project(), context.diagram),
|
||||
context,
|
||||
tool,
|
||||
));
|
||||
self.selected_bend = None;
|
||||
self.status = tool.hint().into();
|
||||
}
|
||||
Event::Endpoint(captured, id) => {
|
||||
if captured != context {
|
||||
@@ -5257,9 +5086,17 @@ impl App {
|
||||
&project_view(self.project(), context.diagram),
|
||||
id,
|
||||
);
|
||||
if !self.inspect_canvas_target(id) {
|
||||
self.connection = Some(session);
|
||||
return;
|
||||
if session.tool == crate::connection_tool::Tool::Allocation
|
||||
&& self.project().diagrams[&context.diagram].projection
|
||||
== ProjectionKind::Traceability
|
||||
{
|
||||
// Mixed views can allocate either perspective. Choosing
|
||||
// a visible source must not navigate to its preferred view.
|
||||
self.selected = id;
|
||||
self.selected_attachment = None;
|
||||
self.refresh_draft();
|
||||
} else {
|
||||
self.select(id);
|
||||
}
|
||||
self.status = if session.candidates.is_empty() {
|
||||
"No compatible targets are visible. Expand containers or show a compatible endpoint in this view; Esc cancels drawing.".into()
|
||||
@@ -5275,11 +5112,8 @@ impl App {
|
||||
Event::Restart(captured) => {
|
||||
if captured == context
|
||||
&& let Some(session) = &self.connection
|
||||
&& session.context == context
|
||||
{
|
||||
// A contextual mixed-layer tool need not be in this layer's
|
||||
// palette. Its unchanged session context retains admission.
|
||||
self.arm_connection(context, session.tool);
|
||||
self.edit_connection(Event::Choose(Some(session.tool)));
|
||||
}
|
||||
}
|
||||
Event::Choose(None) => {}
|
||||
|
||||
@@ -89,10 +89,7 @@ async fn refused_click_keeps_field(mounted: &mut Mounted, f: &mut Fixture, field
|
||||
);
|
||||
assert!(!messages.iter().any(|m| matches!(
|
||||
m,
|
||||
Message::Select(_)
|
||||
| Message::CanvasSelect(..)
|
||||
| Message::SelectNode(_)
|
||||
| Message::MoveNode(..)
|
||||
Message::Select(_) | Message::SelectNode(_) | Message::MoveNode(..)
|
||||
)));
|
||||
assert_eq!(f.app.document.bundle(), &before);
|
||||
assert_eq!((f.app.selected, f.app.selected_node), selected);
|
||||
|
||||
@@ -237,10 +237,7 @@ fn mounted_dirty_property_press_does_not_select_apply_or_destroy_draft() {
|
||||
));
|
||||
assert!(!events.iter().any(|m| matches!(
|
||||
m,
|
||||
Message::Select(_)
|
||||
| Message::CanvasSelect(..)
|
||||
| Message::SelectNode(_)
|
||||
| Message::MoveNode(..)
|
||||
Message::Select(_) | Message::SelectNode(_) | Message::MoveNode(..)
|
||||
)));
|
||||
send(&mut f.app, events);
|
||||
assert_eq!((f.app.selected, f.app.selected_node), selection);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Compact neutral tool palette. Tool state never changes its allocated width.
|
||||
use super::*;
|
||||
use crate::connection_tool::Tool;
|
||||
use iced::{
|
||||
Point, Rectangle, Size, Theme, mouse,
|
||||
widget::{
|
||||
@@ -12,7 +11,7 @@ use iced::{
|
||||
#[derive(Clone, Copy)]
|
||||
enum Icon {
|
||||
Element(Kind),
|
||||
Connection(Tool),
|
||||
Arrow,
|
||||
Existing,
|
||||
Select,
|
||||
Collapse(bool),
|
||||
@@ -77,59 +76,10 @@ impl canvas::Program<Message> for Icon {
|
||||
);
|
||||
}
|
||||
}
|
||||
Self::Connection(connection) => match connection {
|
||||
// The OA and SA work-flow tools share a visual family; they
|
||||
// never coexist in a palette. Keep their small glyph legible.
|
||||
Tool::Interaction | Tool::FunctionalExchange => {
|
||||
for x in [1., 11.] {
|
||||
f.stroke(
|
||||
&Path::rounded_rectangle(
|
||||
Point::new(x, 5.),
|
||||
Size::new(4., 6.),
|
||||
1.5.into(),
|
||||
),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
f.stroke(&line(&[(5., 8.), (10., 8.)]), stroke);
|
||||
f.stroke(&line(&[(7.5, 5.5), (10., 8.), (7.5, 10.5)]), stroke);
|
||||
}
|
||||
Tool::CommunicationMean => {
|
||||
// Participant endpoints, without implying a fixed direction.
|
||||
for x in [1., 11.] {
|
||||
f.stroke(
|
||||
&Path::rectangle(Point::new(x, 4.), Size::new(4., 8.)),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
f.stroke(&line(&[(5., 8.), (11., 8.)]), stroke);
|
||||
}
|
||||
Tool::BoundaryExchange => {
|
||||
// Ports on facing participant boundaries. Standard exchanges
|
||||
// are unoriented, so the generic tool has no arrowhead.
|
||||
for x in [2., 14.] {
|
||||
f.stroke(&line(&[(x, 2.), (x, 14.)]), stroke);
|
||||
}
|
||||
for x in [1., 12.] {
|
||||
f.stroke(
|
||||
&Path::rectangle(Point::new(x, 6.), Size::new(3., 4.)),
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
f.stroke(&line(&[(4., 8.), (12., 8.)]), stroke);
|
||||
}
|
||||
Tool::Allocation => {
|
||||
// Work enters a performer: allocation can appear as nesting
|
||||
// rather than a relationship line in allocated-work views.
|
||||
f.stroke(&line(&[(1., 2.), (1., 14.), (15., 14.), (15., 2.)]), stroke);
|
||||
f.stroke(
|
||||
&Path::rounded_rectangle(Point::new(5., 9.), Size::new(6., 3.), 1.into()),
|
||||
stroke,
|
||||
);
|
||||
f.stroke(&line(&[(8., 1.), (8., 7.)]), stroke);
|
||||
f.stroke(&line(&[(5., 4.), (8., 7.), (11., 4.)]), stroke);
|
||||
}
|
||||
},
|
||||
Self::Arrow => {
|
||||
f.stroke(&line(&[(1., 8.), (14., 8.), (10., 4.)]), stroke);
|
||||
f.stroke(&line(&[(14., 8.), (10., 12.)]), stroke);
|
||||
}
|
||||
Self::Existing => {
|
||||
f.stroke(
|
||||
&Path::rectangle(Point::new(1., 1.), Size::new(10., 10.)),
|
||||
@@ -284,7 +234,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
|
||||
}
|
||||
content = content.push(tool(
|
||||
app,
|
||||
Icon::Connection(*connection),
|
||||
Icon::Arrow,
|
||||
connection.label(),
|
||||
Message::Connection(crate::connection_edit::Event::Choose(Some(*connection))),
|
||||
app.connection
|
||||
|
||||
@@ -1,904 +0,0 @@
|
||||
//! Canvas inspection retains the displayed mixed-layer surface; navigation follows the model.
|
||||
use super::*;
|
||||
use crate::diagram_usage::Target;
|
||||
use crate::model_menu::{Event as MenuEvent, Request};
|
||||
|
||||
struct Fixture {
|
||||
app: App,
|
||||
diagram: Id,
|
||||
process: Id,
|
||||
placement: Id,
|
||||
edge: Id,
|
||||
}
|
||||
|
||||
fn fixture() -> Fixture {
|
||||
let mut p = Project::empty();
|
||||
let process = p
|
||||
.add_element(Kind::Process, Layer::Oa, "Operational process", None)
|
||||
.unwrap();
|
||||
let capability = p
|
||||
.add_element(Kind::Capability, Layer::Oa, "Operational capability", None)
|
||||
.unwrap();
|
||||
let edge = p.relate(
|
||||
RelationKind::ChainInvolvement,
|
||||
"Enables",
|
||||
process,
|
||||
capability,
|
||||
);
|
||||
p.ensure_diagram(Layer::Oa);
|
||||
let diagram = p.ensure_diagram(Layer::Sa);
|
||||
p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Traceability;
|
||||
let placement = p.place(diagram, process, Position { x: 0., y: 0. });
|
||||
p.place(diagram, capability, Position { x: 500., y: 0. });
|
||||
let mut app = App::with_project(p);
|
||||
app.page = Page::Diagram;
|
||||
app.layer = Layer::Sa;
|
||||
app.active_diagram = Some(diagram);
|
||||
app.selected = diagram;
|
||||
app.refresh_draft();
|
||||
app.reconcile_multiselect();
|
||||
assert!(
|
||||
project_view(app.project(), diagram)
|
||||
.edges
|
||||
.iter()
|
||||
.any(|e| e.id == edge)
|
||||
);
|
||||
Fixture {
|
||||
app,
|
||||
diagram,
|
||||
process,
|
||||
placement,
|
||||
edge,
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_surface(f: &Fixture) {
|
||||
assert_eq!(f.app.page, Page::Diagram);
|
||||
assert_eq!(f.app.layer, Layer::Sa);
|
||||
assert_eq!(f.app.active_diagram, Some(f.diagram));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_projection_switch_restores_all_authored_content_and_one_step_history() {
|
||||
let mut f = fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let mut expected = before.decoded.project.clone();
|
||||
expected.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::Allocation;
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Projection(ProjectionKind::Allocation));
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert!(project_view(f.app.project(), f.diagram).nodes.is_empty());
|
||||
assert_eq!(f.app.project().diagrams[&f.diagram].nodes.len(), 2);
|
||||
let switched = f.app.document.snapshot();
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
let _ = f.app.update(Message::Redo);
|
||||
assert_eq!(f.app.project(), &switched.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &switched.bundle);
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Projection(ProjectionKind::Traceability));
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert!(
|
||||
project_view(f.app.project(), f.diagram)
|
||||
.edges
|
||||
.iter()
|
||||
.any(|e| e.id == f.edge)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_edge_and_group_inspection_preserve_surface_and_history() {
|
||||
for grouped in [false, true] {
|
||||
let mut f = fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
if grouped {
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::DiagramSelection(multiselect::Event::Choose(
|
||||
f.app.multiselect_context().unwrap(),
|
||||
Target::Placement(f.placement),
|
||||
multiselect::Choice::Replace,
|
||||
)));
|
||||
}
|
||||
let message = if grouped {
|
||||
Message::DiagramSelection(multiselect::Event::Choose(
|
||||
f.app.multiselect_context().unwrap(),
|
||||
Target::Edge(f.edge),
|
||||
multiselect::Choice::Add,
|
||||
))
|
||||
} else {
|
||||
Message::CanvasSelect(f.app.route_context().unwrap(), f.edge)
|
||||
};
|
||||
let _ = f.app.update(message);
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, f.edge);
|
||||
assert_eq!(f.app.draft.as_ref().unwrap().original, f.edge);
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().primary(),
|
||||
Some(&Target::Edge(f.edge))
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().targets().len(),
|
||||
if grouped { 2 } else { 1 }
|
||||
);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
}
|
||||
|
||||
fn invoke(f: &mut Fixture, object: Id, placement: Option<Id>, action: Message) {
|
||||
let mut request = Request::object(object, iced::Point::ORIGIN);
|
||||
request.canvas = f.app.route_context();
|
||||
request.placement = placement;
|
||||
let _ = f.app.update(Message::Menu(MenuEvent::Open(request)));
|
||||
let token = f.app.model_menu.as_ref().unwrap().token;
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Menu(MenuEvent::Invoke(token, Box::new(action))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_context_properties_and_hide_keep_the_captured_surface() {
|
||||
let mut f = fixture();
|
||||
let edge = f.edge;
|
||||
invoke(&mut f, edge, None, Message::EditProperties(edge));
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, edge);
|
||||
assert!(!f.app.document.can_undo());
|
||||
let before = f.app.document.snapshot();
|
||||
let process = f.process;
|
||||
let placement = f.placement;
|
||||
invoke(&mut f, process, Some(placement), Message::HideSelected);
|
||||
assert_surface(&f);
|
||||
assert!(
|
||||
!f.app.project().diagrams[&f.diagram]
|
||||
.nodes
|
||||
.contains_key(&placement)
|
||||
);
|
||||
assert!(f.app.project().elements.contains_key(&process));
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_explicit_navigation_still_follows_the_target_layer() {
|
||||
let mut f = fixture();
|
||||
let process = f.process;
|
||||
let edge = f.edge;
|
||||
invoke(&mut f, edge, None, Message::Select(process));
|
||||
assert_eq!(f.app.selected, process);
|
||||
assert_eq!(f.app.layer, Layer::Oa);
|
||||
assert_ne!(f.app.active_diagram, Some(f.diagram));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_navigation_to_the_inspected_cross_layer_object_follows_its_layer() {
|
||||
let mut f = fixture();
|
||||
let _ = f.app.update(Message::CanvasSelect(
|
||||
f.app.route_context().unwrap(),
|
||||
f.edge,
|
||||
));
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, f.edge);
|
||||
let before = f.app.document.snapshot();
|
||||
let _ = f.app.update(Message::Select(f.edge));
|
||||
assert_eq!(f.app.selected, f.edge);
|
||||
assert_eq!(f.app.layer, Layer::Oa);
|
||||
assert_ne!(f.app.active_diagram, Some(f.diagram));
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_invalid_draft_and_stale_packets_preserve_selection() {
|
||||
let mut f = fixture();
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::Field("/id".into(), "-".into()));
|
||||
let draft = serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap();
|
||||
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
|
||||
assert_eq!(f.app.selected, f.diagram);
|
||||
assert_surface(&f);
|
||||
assert_eq!(
|
||||
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
|
||||
draft
|
||||
);
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::DiagramSelection(multiselect::Event::Choose(
|
||||
f.app.multiselect_context().unwrap(),
|
||||
Target::Edge(f.edge),
|
||||
multiselect::Choice::Replace,
|
||||
)));
|
||||
assert_eq!(f.app.selected, f.diagram);
|
||||
assert_eq!(
|
||||
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
|
||||
draft
|
||||
);
|
||||
let _ = f.app.update(Message::RevertProperties);
|
||||
f.app.canvas_epoch += 1;
|
||||
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
|
||||
assert_eq!(f.app.selected, f.diagram);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_layer_port_inspection_and_connection_start_retain_the_view() {
|
||||
let mut p = Project::empty();
|
||||
let owner = p
|
||||
.add_element(Kind::Function, Layer::Sa, "Send", None)
|
||||
.unwrap();
|
||||
let port = Id::new_v4();
|
||||
let element = make_port(
|
||||
&p,
|
||||
port,
|
||||
owner,
|
||||
"Output",
|
||||
PortContract::FunctionOutput {
|
||||
outgoing_items: vec![],
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
p.elements.insert(port, element);
|
||||
p.ensure_diagram(Layer::Sa);
|
||||
let diagram = p.ensure_diagram(Layer::Oa);
|
||||
p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Traceability;
|
||||
let placement = p.place(diagram, owner, Position { x: 0., y: 0. });
|
||||
let mut app = App::with_project(p);
|
||||
app.page = Page::Diagram;
|
||||
app.layer = Layer::Oa;
|
||||
app.active_diagram = Some(diagram);
|
||||
app.selected = diagram;
|
||||
app.refresh_draft();
|
||||
app.reconcile_multiselect();
|
||||
assert!(
|
||||
project_view(app.project(), diagram)
|
||||
.ports
|
||||
.iter()
|
||||
.any(|p| p.element == port)
|
||||
);
|
||||
let _ = app.update(Message::CanvasSelect(app.route_context().unwrap(), port));
|
||||
assert_eq!(app.selected, port);
|
||||
assert_eq!(app.active_diagram, Some(diagram));
|
||||
assert_eq!(app.layer, Layer::Oa);
|
||||
assert_eq!(
|
||||
app.multiselect_snapshot().unwrap().primary(),
|
||||
Some(&Target::Port { port, placement })
|
||||
);
|
||||
// Reset inspection so the endpoint path cannot pass by selecting the same ID.
|
||||
app.selected = diagram;
|
||||
app.refresh_draft();
|
||||
let context = app.route_context().unwrap();
|
||||
// Mixed canvases can retain a tool from either perspective.
|
||||
app.connection = Some(crate::connection_edit::Session::new(
|
||||
app.project(),
|
||||
&project_view(app.project(), diagram),
|
||||
context,
|
||||
crate::connection_tool::Tool::FunctionalExchange,
|
||||
));
|
||||
let _ = app.update(Message::Connection(
|
||||
crate::connection_edit::Event::Endpoint(context, port),
|
||||
));
|
||||
assert_eq!(app.selected, port);
|
||||
assert_eq!(app.layer, Layer::Oa);
|
||||
assert_eq!(app.active_diagram, Some(diagram));
|
||||
assert_eq!(app.connection.as_ref().unwrap().source, Some(port));
|
||||
assert!(!app.document.can_undo());
|
||||
}
|
||||
|
||||
fn authoring_fixture(performer: bool) -> (Fixture, Id) {
|
||||
let mut f = fixture();
|
||||
let mut project = f.app.project().clone();
|
||||
let owner = project
|
||||
.add_element(
|
||||
if performer {
|
||||
Kind::Entity
|
||||
} else {
|
||||
Kind::Activity
|
||||
},
|
||||
Layer::Oa,
|
||||
"Operational owner",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let target = project
|
||||
.add_element(Kind::Activity, Layer::Oa, "Operational target", None)
|
||||
.unwrap();
|
||||
let placement = project.place(f.diagram, owner, Position { x: 0., y: 400. });
|
||||
project.place(f.diagram, target, Position { x: 500., y: 400. });
|
||||
f.app = App::with_project(project);
|
||||
f.app.page = Page::Diagram;
|
||||
f.app.layer = Layer::Sa;
|
||||
f.app.active_diagram = Some(f.diagram);
|
||||
f.app.selected = f.diagram;
|
||||
f.app.refresh_draft();
|
||||
f.app.reconcile_multiselect();
|
||||
f.process = owner;
|
||||
f.placement = placement;
|
||||
(f, target)
|
||||
}
|
||||
|
||||
fn assert_source_pair(app: &App) {
|
||||
let compiled = oa_core::source_project::compile_bundle(
|
||||
app.document.bundle(),
|
||||
oa_core::source_project::ProjectLimits::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&compiled.decoded.project, app.project());
|
||||
assert_eq!(&compiled.bundle, app.document.bundle());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_relationship_actions_keep_operational_source_and_commit_one_step() {
|
||||
for context_action in [false, true] {
|
||||
let (mut f, target) = authoring_fixture(false);
|
||||
let before = f.app.document.snapshot();
|
||||
let owner = f.process;
|
||||
let placement = f.placement;
|
||||
if context_action {
|
||||
invoke(
|
||||
&mut f,
|
||||
owner,
|
||||
Some(placement),
|
||||
Message::AddRelationKind(RelationKind::Interaction, false),
|
||||
);
|
||||
} else {
|
||||
let _ = f.app.update(Message::SelectNode(placement));
|
||||
let _ = f.app.update(Message::AddRelation);
|
||||
}
|
||||
assert_surface(&f);
|
||||
let Some(crate::workflow::Workflow::Relation(relation)) = &f.app.workflow else {
|
||||
panic!("relationship form did not open: {}", f.app.status);
|
||||
};
|
||||
assert_eq!(
|
||||
relation.source.as_ref().map(|source| source.id),
|
||||
Some(owner)
|
||||
);
|
||||
assert_eq!(relation.kind, RelationKind::Interaction);
|
||||
let saved = f
|
||||
.app
|
||||
.workflow
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.recovery_snapshot()
|
||||
.unwrap();
|
||||
assert_eq!(saved["Relation"]["layer"], "Oa");
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Workflow(crate::workflow::Event::Target(Choice {
|
||||
id: target,
|
||||
label: "Operational target".into(),
|
||||
})));
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Workflow(crate::workflow::Event::Apply));
|
||||
assert!(f.app.workflow.is_none(), "{}", f.app.status);
|
||||
let created: Vec<_> = f
|
||||
.app
|
||||
.project()
|
||||
.relations
|
||||
.values()
|
||||
.filter(|relation| !before.decoded.project.relations.contains_key(&relation.id))
|
||||
.collect();
|
||||
assert_eq!(created.len(), 1);
|
||||
assert_eq!(
|
||||
(created[0].kind(), created[0].source(), created[0].target()),
|
||||
(RelationKind::Interaction, owner, target)
|
||||
);
|
||||
assert_source_pair(&f.app);
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_add_child_command_uses_parent_layer_for_containment_and_allocation() {
|
||||
for performer in [false, true] {
|
||||
let (mut f, _) = authoring_fixture(performer);
|
||||
let before = f.app.document.snapshot();
|
||||
let owner = f.process;
|
||||
let placement = f.placement;
|
||||
invoke(
|
||||
&mut f,
|
||||
owner,
|
||||
Some(placement),
|
||||
Message::CreateContextElement(Kind::Activity, true),
|
||||
);
|
||||
assert_surface(&f);
|
||||
let created: Vec<_> = f
|
||||
.app
|
||||
.project()
|
||||
.elements
|
||||
.values()
|
||||
.filter(|element| !before.decoded.project.elements.contains_key(&element.id))
|
||||
.collect();
|
||||
assert_eq!(created.len(), 1, "{}", f.app.status);
|
||||
let child = created[0].id;
|
||||
assert_eq!(created[0].kind(), Kind::Activity);
|
||||
assert_eq!(created[0].layer(), Layer::Oa);
|
||||
assert_eq!(
|
||||
created[0].parent,
|
||||
if performer { None } else { Some(owner) }
|
||||
);
|
||||
let allocations: Vec<_> = f
|
||||
.app
|
||||
.project()
|
||||
.relations
|
||||
.values()
|
||||
.filter(|relation| !before.decoded.project.relations.contains_key(&relation.id))
|
||||
.collect();
|
||||
assert_eq!(allocations.len(), usize::from(performer));
|
||||
if performer {
|
||||
assert_eq!(
|
||||
(
|
||||
allocations[0].kind(),
|
||||
allocations[0].source(),
|
||||
allocations[0].target()
|
||||
),
|
||||
(RelationKind::Allocation, owner, child)
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
f.app.project().diagrams[&f.diagram]
|
||||
.nodes
|
||||
.values()
|
||||
.any(|node| node.element == child)
|
||||
);
|
||||
assert_source_pair(&f.app);
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_canvas_root_creation_still_uses_the_active_perspective() {
|
||||
let (mut f, _) = authoring_fixture(false);
|
||||
let _ = f.app.update(Message::SelectNode(f.placement));
|
||||
let before = f.app.document.snapshot();
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::CreateContextElement(Kind::Function, false));
|
||||
assert_surface(&f);
|
||||
let created: Vec<_> = f
|
||||
.app
|
||||
.project()
|
||||
.elements
|
||||
.values()
|
||||
.filter(|element| !before.decoded.project.elements.contains_key(&element.id))
|
||||
.collect();
|
||||
assert_eq!(created.len(), 1, "{}", f.app.status);
|
||||
assert_eq!(created[0].kind(), Kind::Function);
|
||||
assert_eq!(created[0].layer(), Layer::Sa);
|
||||
assert_eq!(created[0].parent, None);
|
||||
assert_source_pair(&f.app);
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_creation_navigates_to_a_parent_layer_preferred_view_only_when_needed() {
|
||||
for existing_preferred in [false, true] {
|
||||
let (mut f, _) = authoring_fixture(false);
|
||||
let mut project = f.app.project().clone();
|
||||
for diagram in project
|
||||
.diagrams
|
||||
.values_mut()
|
||||
.filter(|d| d.layer == Layer::Oa)
|
||||
{
|
||||
diagram.projection = if existing_preferred {
|
||||
ProjectionKind::Allocation
|
||||
} else {
|
||||
ProjectionKind::Overview
|
||||
};
|
||||
}
|
||||
project.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::Capabilities;
|
||||
f.app = App::with_project(project);
|
||||
let _ = f.app.update(Message::Select(f.process));
|
||||
let _ = f.app.update(Message::Diagram(Choice {
|
||||
id: f.diagram,
|
||||
label: "System capabilities".into(),
|
||||
}));
|
||||
assert_eq!(f.app.selected, f.process);
|
||||
assert_eq!(f.app.layer, Layer::Sa);
|
||||
assert_eq!(f.app.active_diagram, Some(f.diagram));
|
||||
let before = f.app.document.snapshot();
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::CreateContextElement(Kind::Activity, true));
|
||||
assert_eq!(f.app.layer, Layer::Oa, "{}", f.app.status);
|
||||
let diagram = f.app.active_diagram.unwrap();
|
||||
assert_ne!(diagram, f.diagram);
|
||||
assert_eq!(f.app.project().diagrams[&diagram].layer, Layer::Oa);
|
||||
assert_eq!(
|
||||
f.app.project().diagrams[&diagram].projection,
|
||||
ProjectionKind::Allocation
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.project().diagrams.len(),
|
||||
before.decoded.project.diagrams.len() + usize::from(!existing_preferred)
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.project().elements[&f.app.selected].parent,
|
||||
Some(f.process)
|
||||
);
|
||||
assert_source_pair(&f.app);
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
}
|
||||
}
|
||||
|
||||
// Regression scope: a contextual click may accept a valid property draft, but
|
||||
// must not inherit the draft object's semantic-layer navigation. If acceptance
|
||||
// changes the originating diagram identity or hides the clicked target, retain
|
||||
// that one accepted edit and refuse the old click. Do not infer a new target or
|
||||
// reinterpret an old surface packet on a replacement diagram.
|
||||
fn draft_click_fixture() -> (Fixture, Id) {
|
||||
let mut f = fixture();
|
||||
let mut project = f.app.project().clone();
|
||||
let capability = project
|
||||
.add_element(Kind::Capability, Layer::Oa, "Second capability", None)
|
||||
.unwrap();
|
||||
let other_edge = project.relate(
|
||||
RelationKind::ChainInvolvement,
|
||||
"Also enables",
|
||||
f.process,
|
||||
capability,
|
||||
);
|
||||
project.place(f.diagram, capability, Position { x: 500., y: 300. });
|
||||
f.app = App::with_project(project);
|
||||
f.app.page = Page::Diagram;
|
||||
f.app.layer = Layer::Sa;
|
||||
f.app.active_diagram = Some(f.diagram);
|
||||
f.app.selected = f.diagram;
|
||||
f.app.refresh_draft();
|
||||
f.app.reconcile_multiselect();
|
||||
assert!(
|
||||
project_view(f.app.project(), f.diagram)
|
||||
.edges
|
||||
.iter()
|
||||
.any(|edge| edge.id == other_edge)
|
||||
);
|
||||
(f, other_edge)
|
||||
}
|
||||
|
||||
fn expected_draft_commit(app: &App, command: Command) -> Document {
|
||||
let mut expected = Document::from_bundle(
|
||||
app.document.bundle().clone(),
|
||||
oa_core::source_project::ProjectLimits::default(),
|
||||
)
|
||||
.unwrap();
|
||||
expected.execute(command).unwrap();
|
||||
expected
|
||||
}
|
||||
|
||||
fn assert_accepted_draft(app: &App, expected: &Document, revision: u64) {
|
||||
assert_eq!(app.project(), expected.project());
|
||||
assert_eq!(app.document.bundle(), expected.bundle());
|
||||
assert_eq!(app.document.revision(), revision + 1);
|
||||
assert!(!app.draft_dirty());
|
||||
assert!(app.document.can_undo());
|
||||
assert!(!app.document.can_redo());
|
||||
assert_source_pair(app);
|
||||
}
|
||||
|
||||
fn assert_one_draft_history(
|
||||
app: &mut App,
|
||||
before: &oa_core::source_project::CompiledSourceProject,
|
||||
expected: &Document,
|
||||
) {
|
||||
let _ = app.update(Message::Undo);
|
||||
assert_eq!(app.project(), &before.decoded.project);
|
||||
assert_eq!(app.document.bundle(), &before.bundle);
|
||||
assert!(
|
||||
!app.document.can_undo(),
|
||||
"the click must not add a second edit"
|
||||
);
|
||||
assert!(app.document.can_redo());
|
||||
let _ = app.update(Message::Redo);
|
||||
assert_eq!(app.project(), expected.project());
|
||||
assert_eq!(app.document.bundle(), expected.bundle());
|
||||
assert!(!app.document.can_redo());
|
||||
}
|
||||
|
||||
fn pending_edge_name(f: &mut Fixture) {
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
|
||||
assert_surface(f);
|
||||
assert_eq!(f.app.selected, f.edge);
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Field("/name".into(), "Reviewed enables".into()));
|
||||
assert!(f.app.draft_dirty());
|
||||
assert!(f.app.draft.as_ref().unwrap().command().is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_cross_layer_draft_then_canvas_edge_retains_surface_and_one_edit() {
|
||||
let (mut f, other_edge) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let expected =
|
||||
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
|
||||
pending_edge_name(&mut f);
|
||||
// Capture after drafting, so this exercises successful draft application,
|
||||
// not refusal of a packet made stale by the property edit itself.
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::CanvasSelect(context, other_edge));
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, other_edge);
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().primary(),
|
||||
Some(&Target::Edge(other_edge))
|
||||
);
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_cross_layer_draft_then_node_click_retains_surface_and_one_edit() {
|
||||
let (mut f, _) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let expected =
|
||||
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
|
||||
pending_edge_name(&mut f);
|
||||
let _ = f.app.update(Message::SelectNode(f.placement));
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, f.process);
|
||||
assert_eq!(f.app.selected_node, Some(f.placement));
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().primary(),
|
||||
Some(&Target::Placement(f.placement))
|
||||
);
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diagram_reidentity_draft_commits_but_refuses_the_old_surface_click() {
|
||||
let (mut f, other_edge) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let replacement = Id::new_v4();
|
||||
let expected = expected_draft_commit(&f.app, Command::Reidentify(f.diagram, replacement));
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Field("/id".into(), replacement.to_string()));
|
||||
assert!(f.app.draft_dirty());
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::CanvasSelect(context, other_edge));
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert!(!f.app.project().diagrams.contains_key(&context.diagram));
|
||||
assert!(
|
||||
f.app
|
||||
.active_diagram
|
||||
.is_some_and(|id| f.app.project().diagrams.contains_key(&id)),
|
||||
"never restore a removed diagram identity"
|
||||
);
|
||||
assert_ne!(f.app.selected, other_edge, "refuse the old surface click");
|
||||
assert!(
|
||||
f.app
|
||||
.multiselect_snapshot()
|
||||
.is_none_or(|s| !s.targets().contains(&Target::Edge(other_edge)))
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.status,
|
||||
"Properties applied. The view changed; select a visible target again."
|
||||
);
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn projection_draft_commits_but_refuses_the_now_hidden_canvas_target() {
|
||||
let (mut f, other_edge) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let mut diagram = f.app.project().diagrams[&f.diagram].clone();
|
||||
diagram.projection = ProjectionKind::Allocation;
|
||||
let expected = expected_draft_commit(&f.app, Command::EditDiagram(f.diagram, diagram));
|
||||
let _ = f.app.update(Message::FieldShape(
|
||||
"/projection".into(),
|
||||
serde_json::to_value(ProjectionKind::Allocation).unwrap(),
|
||||
));
|
||||
assert!(f.app.draft_dirty());
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::CanvasSelect(context, other_edge));
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_surface(&f);
|
||||
assert!(
|
||||
!project_view(f.app.project(), f.diagram)
|
||||
.edges
|
||||
.iter()
|
||||
.any(|edge| edge.id == other_edge)
|
||||
);
|
||||
assert_ne!(
|
||||
f.app.selected, other_edge,
|
||||
"do not inspect a no-longer-visible hit"
|
||||
);
|
||||
assert!(
|
||||
f.app
|
||||
.multiselect_snapshot()
|
||||
.is_none_or(|s| !s.targets().contains(&Target::Edge(other_edge)))
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.status,
|
||||
"Properties applied. The view changed; select a visible target again."
|
||||
);
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_cross_layer_draft_blocks_contextual_clicks_without_changing_history() {
|
||||
for click in 0..3 {
|
||||
let (mut f, other_edge) = draft_click_fixture();
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Field("/id".into(), "not-a-uuid".into()));
|
||||
assert!(f.app.draft_dirty());
|
||||
let expected_error = f.app.draft.as_ref().unwrap().command().unwrap_err();
|
||||
let draft =
|
||||
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap();
|
||||
let selection = f.app.multiselect_snapshot().unwrap();
|
||||
let message = match click {
|
||||
0 => Message::CanvasSelect(f.app.route_context().unwrap(), other_edge),
|
||||
1 => Message::SelectNode(f.placement),
|
||||
_ => Message::DiagramSelection(multiselect::Event::Choose(
|
||||
f.app.multiselect_context().unwrap(),
|
||||
Target::Edge(other_edge),
|
||||
multiselect::Choice::Add,
|
||||
)),
|
||||
};
|
||||
let _ = f.app.update(message);
|
||||
if click < 2 {
|
||||
assert_eq!(f.app.status, expected_error);
|
||||
} else {
|
||||
assert_eq!(
|
||||
f.app.status,
|
||||
"Finish or cancel the active edit before changing the diagram selection."
|
||||
);
|
||||
}
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, f.edge);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert!(!f.app.document.can_undo());
|
||||
assert!(!f.app.document.can_redo());
|
||||
assert_eq!(
|
||||
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
|
||||
draft
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().targets(),
|
||||
selection.targets()
|
||||
);
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().primary(),
|
||||
selection.primary()
|
||||
);
|
||||
if click == 0 {
|
||||
// Pre-dispatch stale rejection applies no draft and must not emit
|
||||
// either success feedback or a new validation message.
|
||||
let stale = f.app.route_context().unwrap();
|
||||
f.app.canvas_epoch += 1;
|
||||
f.app.status = "Keep previous feedback".into();
|
||||
let _ = f.app.update(Message::CanvasSelect(stale, other_edge));
|
||||
assert_eq!(f.app.status, "Keep previous feedback");
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert_eq!(
|
||||
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
|
||||
draft
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_select_after_valid_cross_layer_draft_still_navigates_and_commits_once() {
|
||||
let (mut f, other_edge) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let expected =
|
||||
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
|
||||
pending_edge_name(&mut f);
|
||||
let _ = f.app.update(Message::Select(other_edge));
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_eq!(f.app.selected, other_edge);
|
||||
assert_eq!(f.app.layer, Layer::Oa);
|
||||
assert_ne!(f.app.active_diagram, Some(f.diagram));
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn properties_apply_on_cross_layer_edge_retains_surface_and_one_edit() {
|
||||
let (mut f, _) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let expected =
|
||||
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
|
||||
pending_edge_name(&mut f);
|
||||
let _ = f.app.update(Message::Apply);
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_eq!(f.app.status, "Properties applied");
|
||||
assert_surface(&f);
|
||||
assert_eq!(f.app.selected, f.edge);
|
||||
assert_eq!(f.app.draft.as_ref().unwrap().original, f.edge);
|
||||
assert_eq!(
|
||||
f.app.multiselect_snapshot().unwrap().primary(),
|
||||
Some(&Target::Edge(f.edge))
|
||||
);
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn properties_apply_honors_an_intentional_active_diagram_layer_edit() {
|
||||
let (mut f, _) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let mut diagram = f.app.project().diagrams[&f.diagram].clone();
|
||||
diagram.layer = Layer::Oa;
|
||||
let expected = expected_draft_commit(&f.app, Command::EditDiagram(f.diagram, diagram));
|
||||
let _ = f.app.update(Message::FieldShape(
|
||||
"/layer".into(),
|
||||
serde_json::to_value(Layer::Oa).unwrap(),
|
||||
));
|
||||
assert!(f.app.draft_dirty());
|
||||
let _ = f.app.update(Message::Apply);
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_eq!(f.app.page, Page::Diagram);
|
||||
assert_eq!(
|
||||
f.app.layer,
|
||||
Layer::Oa,
|
||||
"honor the edited diagram's own layer"
|
||||
);
|
||||
assert_eq!(f.app.active_diagram, Some(f.diagram));
|
||||
assert_eq!(f.app.selected, f.diagram);
|
||||
assert_eq!(f.app.draft.as_ref().unwrap().original, f.diagram);
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn properties_apply_follows_the_known_active_diagram_reidentity_once() {
|
||||
let (mut f, _) = draft_click_fixture();
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let replacement = Id::new_v4();
|
||||
let expected = expected_draft_commit(&f.app, Command::Reidentify(f.diagram, replacement));
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Field("/id".into(), replacement.to_string()));
|
||||
assert!(f.app.draft_dirty());
|
||||
let _ = f.app.update(Message::Apply);
|
||||
assert_accepted_draft(&f.app, &expected, revision);
|
||||
assert_eq!(f.app.page, Page::Diagram);
|
||||
assert_eq!(f.app.layer, Layer::Sa);
|
||||
// This identity was explicitly authored in the diagram's own draft. No
|
||||
// queued click is being remapped or inferred from semantic similarity.
|
||||
assert_eq!(f.app.active_diagram, Some(replacement));
|
||||
assert_eq!(f.app.selected, replacement);
|
||||
assert_eq!(f.app.draft.as_ref().unwrap().original, replacement);
|
||||
assert!(!f.app.project().diagrams.contains_key(&f.diagram));
|
||||
assert_one_draft_history(&mut f.app, &before, &expected);
|
||||
}
|
||||
@@ -1,366 +0,0 @@
|
||||
// Test-only proposal pinned to peer 6520a89dc042dd433454f6cc63cb23b0e6df7911.
|
||||
// Independent App test module; real popup messages retain menu tokens.
|
||||
use super::*;
|
||||
use crate::model_menu::{Event as MenuEvent, Request};
|
||||
|
||||
struct BeginFixture {
|
||||
app: App,
|
||||
diagram: Id,
|
||||
surface_layer: Layer,
|
||||
source: Id,
|
||||
target: Id,
|
||||
wrong_layer: Id,
|
||||
hidden: Id,
|
||||
tool: crate::connection_tool::Tool,
|
||||
}
|
||||
|
||||
fn begin_fixture(source_layer: Layer, allocation: bool, mixed: bool) -> BeginFixture {
|
||||
use crate::connection_tool::Tool;
|
||||
let mut p = Project::empty();
|
||||
let (work, performer, exchange) = match source_layer {
|
||||
Layer::Oa => (Kind::Activity, Kind::Entity, Tool::Interaction),
|
||||
Layer::Sa => (Kind::Function, Kind::System, Tool::FunctionalExchange),
|
||||
};
|
||||
let opposite = if source_layer == Layer::Oa {
|
||||
Layer::Sa
|
||||
} else {
|
||||
Layer::Oa
|
||||
};
|
||||
let surface_layer = if mixed { opposite } else { source_layer };
|
||||
let source = p
|
||||
.add_element(
|
||||
if allocation { performer } else { work },
|
||||
source_layer,
|
||||
"Source",
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let target = p.add_element(work, source_layer, "Target", None).unwrap();
|
||||
let hidden = p
|
||||
.add_element(work, source_layer, "Hidden target", None)
|
||||
.unwrap();
|
||||
let other_work = if opposite == Layer::Oa {
|
||||
Kind::Activity
|
||||
} else {
|
||||
Kind::Function
|
||||
};
|
||||
let wrong_layer = p
|
||||
.add_element(other_work, opposite, "Wrong layer target", None)
|
||||
.unwrap();
|
||||
p.ensure_diagram(source_layer);
|
||||
let diagram = p.ensure_diagram(surface_layer);
|
||||
p.diagrams.get_mut(&diagram).unwrap().projection = if mixed {
|
||||
ProjectionKind::Traceability
|
||||
} else {
|
||||
ProjectionKind::Capabilities
|
||||
};
|
||||
p.place(diagram, source, Position { x: 0., y: 0. });
|
||||
p.place(diagram, target, Position { x: 400., y: 0. });
|
||||
if mixed {
|
||||
p.place(diagram, wrong_layer, Position { x: 800., y: 0. });
|
||||
}
|
||||
let mut app = App::with_project(p);
|
||||
app.page = Page::Diagram;
|
||||
app.layer = surface_layer;
|
||||
app.active_diagram = Some(diagram);
|
||||
app.selected = diagram;
|
||||
app.refresh_draft();
|
||||
app.reconcile_multiselect();
|
||||
assert!(!app.document.can_undo());
|
||||
BeginFixture {
|
||||
app,
|
||||
diagram,
|
||||
surface_layer,
|
||||
source,
|
||||
target,
|
||||
wrong_layer,
|
||||
hidden,
|
||||
tool: if allocation {
|
||||
Tool::Allocation
|
||||
} else {
|
||||
exchange
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_surface(f: &BeginFixture) {
|
||||
assert_eq!(f.app.page, Page::Diagram);
|
||||
assert_eq!(f.app.layer, f.surface_layer);
|
||||
assert_eq!(f.app.active_diagram, Some(f.diagram));
|
||||
}
|
||||
|
||||
fn begin_popup_action(f: &mut BeginFixture) -> Option<Message> {
|
||||
begin_popup_action_from(f, true)
|
||||
}
|
||||
|
||||
fn begin_popup_action_from(f: &mut BeginFixture, canvas: bool) -> Option<Message> {
|
||||
let mut request = Request::object(f.source, iced::Point::ORIGIN);
|
||||
if canvas {
|
||||
request.canvas = f.app.route_context();
|
||||
request.placement = f.app.project().diagrams[&f.diagram]
|
||||
.nodes
|
||||
.values()
|
||||
.find(|node| node.element == f.source)
|
||||
.map(|node| node.id);
|
||||
}
|
||||
let _ = f.app.update(Message::Menu(MenuEvent::Open(request)));
|
||||
let popup = crate::model_menu::popup(&f.app).expect("real model popup");
|
||||
let expected = format!("Draw {}", f.tool.label().to_lowercase());
|
||||
for entry in popup.entries {
|
||||
if let crate::context_menu::Entry::Action { label, message, .. } = entry
|
||||
&& label == expected
|
||||
{
|
||||
if let Some(Message::Menu(MenuEvent::Invoke(token, _))) = &message {
|
||||
assert_eq!(*token, popup.token);
|
||||
} else {
|
||||
assert!(message.is_none(), "Draw must use the menu token envelope");
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
None // Omission is also a valid unavailable-action policy.
|
||||
}
|
||||
|
||||
fn begin_from_popup(f: &mut BeginFixture) -> Message {
|
||||
begin_from_popup_origin(f, true)
|
||||
}
|
||||
|
||||
fn begin_from_popup_origin(f: &mut BeginFixture, canvas: bool) -> Message {
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let action =
|
||||
begin_popup_action_from(f, canvas).expect("visible semantic source offers enabled Draw");
|
||||
let _ = f.app.update(action.clone());
|
||||
begin_surface(f);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert!(!f.app.document.can_undo());
|
||||
let session = f
|
||||
.app
|
||||
.connection
|
||||
.as_ref()
|
||||
.expect("menu Draw must arm its tool");
|
||||
assert_eq!(session.tool, f.tool);
|
||||
assert_eq!(session.source, Some(f.source));
|
||||
assert_eq!(session.context, f.app.route_context().unwrap());
|
||||
assert!(session.candidates.contains(&f.target));
|
||||
assert!(!session.candidates.contains(&f.wrong_layer));
|
||||
assert!(!session.candidates.contains(&f.hidden));
|
||||
action
|
||||
}
|
||||
|
||||
fn begin_complete_and_check_history(f: &mut BeginFixture) {
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let context = f.app.route_context().unwrap();
|
||||
let _ = f.app.update(Message::Connection(
|
||||
crate::connection_edit::Event::Endpoint(context, f.target),
|
||||
));
|
||||
begin_surface(f);
|
||||
assert!(f.app.connection.is_none());
|
||||
assert_eq!(f.app.document.revision(), revision + 1);
|
||||
let relations: Vec<_> = f
|
||||
.app
|
||||
.project()
|
||||
.relations
|
||||
.values()
|
||||
.filter(|r| !before.decoded.project.relations.contains_key(&r.id))
|
||||
.collect();
|
||||
assert_eq!(relations.len(), 1);
|
||||
let expected = match f.tool {
|
||||
crate::connection_tool::Tool::Interaction => RelationKind::Interaction,
|
||||
crate::connection_tool::Tool::FunctionalExchange => RelationKind::FunctionalExchange,
|
||||
crate::connection_tool::Tool::Allocation => RelationKind::Allocation,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
assert_eq!(relations[0].kind(), expected);
|
||||
assert_eq!(
|
||||
(relations[0].source(), relations[0].target()),
|
||||
(f.source, f.target)
|
||||
);
|
||||
// Random identities are accepted only for the precise command delta.
|
||||
// Everything else, including traces, metadata and view options, stays exact.
|
||||
let mut expected_project = before.decoded.project.clone();
|
||||
let created = relations[0];
|
||||
assert!(
|
||||
expected_project
|
||||
.relations
|
||||
.insert(created.id, created.clone())
|
||||
.is_none()
|
||||
);
|
||||
if let RelationBody::FunctionalExchange {
|
||||
source_port,
|
||||
target_port,
|
||||
items,
|
||||
..
|
||||
} = &created.body
|
||||
{
|
||||
assert_eq!(f.tool, crate::connection_tool::Tool::FunctionalExchange);
|
||||
assert!(items.is_empty());
|
||||
assert_ne!(source_port, target_port);
|
||||
let output = &f.app.project().elements[source_port];
|
||||
let input = &f.app.project().elements[target_port];
|
||||
assert_eq!(
|
||||
(output.kind(), output.layer(), output.parent),
|
||||
(Kind::Port, Layer::Sa, Some(f.source))
|
||||
);
|
||||
assert_eq!(
|
||||
(input.kind(), input.layer(), input.parent),
|
||||
(Kind::Port, Layer::Sa, Some(f.target))
|
||||
);
|
||||
assert!(
|
||||
matches!(port_contract(output), Some(PortContract::FunctionOutput { outgoing_items }) if outgoing_items.is_empty())
|
||||
);
|
||||
assert!(
|
||||
matches!(port_contract(input), Some(PortContract::FunctionInput { incoming_items }) if incoming_items.is_empty())
|
||||
);
|
||||
assert!(!before.decoded.project.elements.contains_key(source_port));
|
||||
assert!(!before.decoded.project.elements.contains_key(target_port));
|
||||
assert_eq!(output.id, *source_port);
|
||||
assert_eq!(input.id, *target_port);
|
||||
expected_project
|
||||
.elements
|
||||
.insert(*source_port, output.clone());
|
||||
expected_project
|
||||
.elements
|
||||
.insert(*target_port, input.clone());
|
||||
assert_eq!(
|
||||
f.app.project().elements.len(),
|
||||
before.decoded.project.elements.len() + 2
|
||||
);
|
||||
} else {
|
||||
assert_ne!(f.tool, crate::connection_tool::Tool::FunctionalExchange);
|
||||
assert_eq!(f.app.project().elements, before.decoded.project.elements);
|
||||
}
|
||||
assert_eq!(f.app.project(), &expected_project);
|
||||
assert_eq!(f.app.project().diagrams, before.decoded.project.diagrams);
|
||||
for (id, element) in &before.decoded.project.elements {
|
||||
assert_eq!(&f.app.project().elements[id], element);
|
||||
}
|
||||
begin_assert_source_pair(&f.app);
|
||||
let after = f.app.document.snapshot();
|
||||
let _ = f.app.update(Message::Undo);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(
|
||||
!f.app.document.can_undo(),
|
||||
"creation is a single history step"
|
||||
);
|
||||
let _ = f.app.update(Message::Redo);
|
||||
assert_eq!(f.app.project(), &after.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &after.bundle);
|
||||
begin_assert_source_pair(&f.app);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contextual_begin_exchange_uses_actual_menu_in_both_mixed_directions() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = begin_fixture(layer, false, true);
|
||||
begin_from_popup(&mut f);
|
||||
begin_complete_and_check_history(&mut f);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contextual_begin_cancel_restart_and_stale_menu_preserve_session_ownership() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = begin_fixture(layer, false, true);
|
||||
let original = f.app.document.snapshot();
|
||||
let stale_menu = begin_from_popup(&mut f);
|
||||
let first = f.app.route_context().unwrap();
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Connection(crate::connection_edit::Event::Restart(
|
||||
first,
|
||||
)));
|
||||
begin_surface(&f);
|
||||
let restarted = f
|
||||
.app
|
||||
.connection
|
||||
.as_ref()
|
||||
.expect("restart retains contextual tool");
|
||||
assert_eq!(restarted.tool, f.tool);
|
||||
assert_eq!(restarted.source, None);
|
||||
assert!(restarted.candidates.contains(&f.source));
|
||||
let current = restarted.context;
|
||||
assert_ne!(current, first);
|
||||
let _ = f.app.update(stale_menu);
|
||||
let _ = f.app.update(Message::Connection(
|
||||
crate::connection_edit::Event::Endpoint(first, f.target),
|
||||
));
|
||||
assert_eq!(f.app.connection.as_ref().unwrap().context, current);
|
||||
assert_eq!(f.app.connection.as_ref().unwrap().source, None);
|
||||
let _ = f.app.update(Message::Connection(
|
||||
crate::connection_edit::Event::Endpoint(current, f.source),
|
||||
));
|
||||
assert_eq!(f.app.connection.as_ref().unwrap().source, Some(f.source));
|
||||
let _ = f.app.update(Message::Escape);
|
||||
let _ = f.app.update(Message::Connection(
|
||||
crate::connection_edit::Event::Endpoint(current, f.target),
|
||||
));
|
||||
assert!(f.app.connection.is_none());
|
||||
begin_surface(&f);
|
||||
assert_eq!(f.app.project(), &original.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &original.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contextual_begin_allocation_uses_actual_menu_in_both_mixed_directions() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = begin_fixture(layer, true, true);
|
||||
begin_from_popup(&mut f);
|
||||
begin_complete_and_check_history(&mut f);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contextual_begin_allocation_is_not_enabled_when_projection_hides_work() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = begin_fixture(layer, true, false);
|
||||
let view = project_view(f.app.project(), f.diagram);
|
||||
assert!(view.nodes.iter().any(|n| n.element == f.source));
|
||||
assert!(!view.nodes.iter().any(|n| n.element == f.target));
|
||||
assert!(!f.tool.available_in(layer, ProjectionKind::Capabilities));
|
||||
let before = f.app.document.snapshot();
|
||||
assert!(
|
||||
begin_popup_action(&mut f).is_none(),
|
||||
"Draw allocation must be disabled or omitted when work is not projected"
|
||||
);
|
||||
let _ = f.app.update(Message::Menu(MenuEvent::Close));
|
||||
// Defensive handler refusal complements actual-menu enabledness above.
|
||||
let _ = f
|
||||
.app
|
||||
.update(Message::Connection(crate::connection_edit::Event::Begin(
|
||||
f.tool, f.source,
|
||||
)));
|
||||
assert!(f.app.connection.is_none());
|
||||
begin_surface(&f);
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert!(!f.app.document.can_undo());
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_assert_source_pair(app: &App) {
|
||||
let compiled = oa_core::source_project::compile_bundle(
|
||||
app.document.bundle(),
|
||||
oa_core::source_project::ProjectLimits::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&compiled.decoded.project, app.project());
|
||||
assert_eq!(&compiled.bundle, app.document.bundle());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn contextual_begin_explorer_draw_retains_visible_mixed_diagram_in_both_directions() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
let mut f = begin_fixture(layer, false, true);
|
||||
// A real Explorer Request::object has no canvas/placement envelope,
|
||||
// but its generated Draw action still targets the visible current view.
|
||||
begin_from_popup_origin(&mut f, false);
|
||||
begin_complete_and_check_history(&mut f);
|
||||
}
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
// Proposed tests against c7065db76ecca3faa733ccb5f09b87fd0a0958be.
|
||||
use super::*;
|
||||
|
||||
struct RelocationFixture {
|
||||
app: App,
|
||||
source: Id,
|
||||
target: Id,
|
||||
node: Id,
|
||||
other_node: Id,
|
||||
edge: Id,
|
||||
}
|
||||
|
||||
fn relocation_fixture(source_layer: Layer, target_layer: Layer) -> RelocationFixture {
|
||||
let mut p = Project::empty();
|
||||
let work = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Observed work", None)
|
||||
.unwrap();
|
||||
let other = p
|
||||
.add_element(Kind::Activity, Layer::Oa, "Other work", None)
|
||||
.unwrap();
|
||||
let edge = p.relate(RelationKind::Interaction, "Observed exchange", work, other);
|
||||
let source = p.ensure_diagram(source_layer);
|
||||
p.diagrams.get_mut(&source).unwrap().projection = ProjectionKind::Traceability;
|
||||
let node = p.place(source, work, Position { x: 20., y: 20. });
|
||||
let other_node = p.place(source, other, Position { x: 500., y: 20. });
|
||||
let mut destination = p.diagrams[&source].clone();
|
||||
destination.id = Id::new_v4();
|
||||
destination.alias = "relocation_destination".into();
|
||||
destination.name = "Relocation destination".into();
|
||||
destination.layer = target_layer;
|
||||
destination.nodes.clear();
|
||||
let target = destination.id;
|
||||
p.diagrams.insert(target, destination);
|
||||
let mut app = App::with_project(p);
|
||||
app.page = Page::Diagram;
|
||||
app.layer = source_layer;
|
||||
app.active_diagram = Some(source);
|
||||
app.selected = node;
|
||||
app.refresh_draft();
|
||||
app.reconcile_multiselect();
|
||||
assert!(
|
||||
matches!(app.draft.as_ref().unwrap().kind, crate::forms::RecordKind::Node(d) if d == source)
|
||||
);
|
||||
assert!(!app.document.can_undo());
|
||||
RelocationFixture {
|
||||
app,
|
||||
source,
|
||||
target,
|
||||
node,
|
||||
other_node,
|
||||
edge,
|
||||
}
|
||||
}
|
||||
|
||||
fn relocation_field(app: &mut App, path: &str, value: impl ToString) {
|
||||
let _ = app.update(Message::Field(path.into(), value.to_string()));
|
||||
}
|
||||
|
||||
fn relocation_source_pair(app: &App) {
|
||||
let compiled = oa_core::source_project::compile_bundle(
|
||||
app.document.bundle(),
|
||||
oa_core::source_project::ProjectLimits::default(),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&compiled.decoded.project, app.project());
|
||||
assert_eq!(&compiled.bundle, app.document.bundle());
|
||||
}
|
||||
|
||||
fn relocation_selection(app: &App, diagram: Id, placement: Id) {
|
||||
assert_eq!(app.page, Page::Diagram);
|
||||
assert_eq!(app.active_diagram, Some(diagram));
|
||||
assert_eq!(app.layer, app.project().diagrams[&diagram].layer);
|
||||
assert_eq!(app.selected, placement);
|
||||
assert_eq!(app.selected_node, Some(placement));
|
||||
let draft = app.draft.as_ref().unwrap();
|
||||
assert_eq!(draft.original, placement);
|
||||
assert!(matches!(draft.kind, crate::forms::RecordKind::Node(d) if d == diagram));
|
||||
}
|
||||
|
||||
fn relocation_expected(app: &App, command: Command) -> Document {
|
||||
let mut expected = Document::from_bundle(
|
||||
app.document.bundle().clone(),
|
||||
oa_core::source_project::ProjectLimits::default(),
|
||||
)
|
||||
.unwrap();
|
||||
expected.execute(command).unwrap();
|
||||
expected
|
||||
}
|
||||
|
||||
fn relocation_history(
|
||||
app: &mut App,
|
||||
before: &oa_core::source_project::CompiledSourceProject,
|
||||
expected: &Document,
|
||||
) {
|
||||
assert_eq!(app.project(), expected.project());
|
||||
assert_eq!(app.document.bundle(), expected.bundle());
|
||||
assert!(app.document.can_undo());
|
||||
assert!(!app.document.can_redo());
|
||||
let _ = app.update(Message::Undo);
|
||||
assert_eq!(app.project(), &before.decoded.project);
|
||||
assert_eq!(app.document.bundle(), &before.bundle);
|
||||
assert!(!app.document.can_undo());
|
||||
assert!(app.document.can_redo());
|
||||
let _ = app.update(Message::Redo);
|
||||
assert_eq!(app.project(), expected.project());
|
||||
assert_eq!(app.document.bundle(), expected.bundle());
|
||||
assert!(!app.document.can_redo());
|
||||
relocation_source_pair(app);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_placement_relocation_follows_actual_owner_with_optional_new_identity() {
|
||||
for layer in [Layer::Oa, Layer::Sa] {
|
||||
for reidentify in [false, true] {
|
||||
let mut f = relocation_fixture(Layer::Oa, layer);
|
||||
let before = f.app.document.snapshot();
|
||||
let before_project = before.decoded.project.clone();
|
||||
let revision = f.app.document.revision();
|
||||
let id = if reidentify { Id::new_v4() } else { f.node };
|
||||
let mut expected = before_project.clone();
|
||||
let mut placement = expected
|
||||
.diagrams
|
||||
.get_mut(&f.source)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.remove(&f.node)
|
||||
.unwrap();
|
||||
placement.id = id;
|
||||
expected
|
||||
.diagrams
|
||||
.get_mut(&f.target)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.insert(id, placement);
|
||||
let expected_document = relocation_expected(
|
||||
&f.app,
|
||||
Command::Batch(vec![
|
||||
Command::EditNode {
|
||||
diagram: f.source,
|
||||
original: f.node,
|
||||
node: expected.diagrams[&f.target].nodes[&id].clone(),
|
||||
},
|
||||
Command::RelocateNode {
|
||||
node: id,
|
||||
diagram: f.target,
|
||||
},
|
||||
]),
|
||||
);
|
||||
assert_eq!(expected_document.project(), &expected);
|
||||
relocation_field(&mut f.app, "/diagram", f.target);
|
||||
if reidentify {
|
||||
relocation_field(&mut f.app, "/id", id);
|
||||
}
|
||||
assert!(f.app.apply(), "{}", f.app.status);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.revision(), revision + 1);
|
||||
relocation_selection(&f.app, f.target, id);
|
||||
relocation_source_pair(&f.app);
|
||||
assert_eq!(f.app.document.bundle(), expected_document.bundle());
|
||||
relocation_history(&mut f.app, &before, &expected_document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_owner_placement_position_alias_and_identity_keep_mixed_surface() {
|
||||
for different_current_view in [false, true] {
|
||||
for field in ["/position/x", "/alias", "/id"] {
|
||||
let mut f = relocation_fixture(Layer::Sa, Layer::Oa);
|
||||
// Ordinary Select plus the diagram picker can retain an independent
|
||||
// placement Inspector while showing a different saved view. This is
|
||||
// an App-handler workflow, not a claim of native menu execution.
|
||||
let _ = f.app.update(Message::Select(f.app.project().id));
|
||||
let _ = f.app.update(Message::Select(f.node));
|
||||
let current = if different_current_view {
|
||||
f.target
|
||||
} else {
|
||||
f.source
|
||||
};
|
||||
if different_current_view {
|
||||
let _ = f.app.update(Message::Diagram(Choice {
|
||||
id: current,
|
||||
label: "Other current view".into(),
|
||||
}));
|
||||
}
|
||||
assert_eq!(f.app.selected, f.node);
|
||||
assert_eq!(f.app.active_diagram, Some(current));
|
||||
assert!(
|
||||
matches!(f.app.draft.as_ref().unwrap().kind, crate::forms::RecordKind::Node(d) if d == f.source)
|
||||
);
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
let mut expected = before.decoded.project.clone();
|
||||
let mut placement = expected
|
||||
.diagrams
|
||||
.get_mut(&f.source)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.remove(&f.node)
|
||||
.unwrap();
|
||||
let value = match field {
|
||||
"/position/x" => {
|
||||
placement.position.x = 123.;
|
||||
"123".to_owned()
|
||||
}
|
||||
"/alias" => {
|
||||
placement.alias = "renamed_placement".into();
|
||||
placement.alias.clone()
|
||||
}
|
||||
_ => {
|
||||
placement.id = Id::new_v4();
|
||||
placement.id.to_string()
|
||||
}
|
||||
};
|
||||
let id = placement.id;
|
||||
let expected_document = relocation_expected(
|
||||
&f.app,
|
||||
Command::EditNode {
|
||||
diagram: f.source,
|
||||
original: f.node,
|
||||
node: placement.clone(),
|
||||
},
|
||||
);
|
||||
expected
|
||||
.diagrams
|
||||
.get_mut(&f.source)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.insert(id, placement);
|
||||
assert_eq!(expected_document.project(), &expected);
|
||||
relocation_field(&mut f.app, field, value);
|
||||
assert!(f.app.apply(), "{}", f.app.status);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.bundle(), expected_document.bundle());
|
||||
assert_eq!(f.app.document.revision(), revision + 1);
|
||||
assert_eq!(f.app.page, Page::Diagram);
|
||||
assert_eq!(f.app.active_diagram, Some(current));
|
||||
assert_eq!(f.app.layer, f.app.project().diagrams[¤t].layer);
|
||||
assert_eq!(f.app.selected, id);
|
||||
assert_eq!(
|
||||
f.app.selected_node,
|
||||
if different_current_view {
|
||||
None
|
||||
} else {
|
||||
Some(id)
|
||||
}
|
||||
);
|
||||
let draft = f.app.draft.as_ref().unwrap();
|
||||
assert_eq!(draft.original, id);
|
||||
assert!(matches!(draft.kind, crate::forms::RecordKind::Node(d) if d == f.source));
|
||||
relocation_source_pair(&f.app);
|
||||
relocation_history(&mut f.app, &before, &expected_document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepted_relocation_refuses_queued_old_canvas_target_with_feedback() {
|
||||
for edge_hit in [false, true] {
|
||||
let mut f = relocation_fixture(Layer::Oa, Layer::Sa);
|
||||
let before = f.app.document.snapshot();
|
||||
let expected_document = relocation_expected(
|
||||
&f.app,
|
||||
Command::Batch(vec![
|
||||
Command::EditNode {
|
||||
diagram: f.source,
|
||||
original: f.node,
|
||||
node: f.app.project().diagrams[&f.source].nodes[&f.node].clone(),
|
||||
},
|
||||
Command::RelocateNode {
|
||||
node: f.node,
|
||||
diagram: f.target,
|
||||
},
|
||||
]),
|
||||
);
|
||||
let mut expected = before.decoded.project.clone();
|
||||
let placement = expected
|
||||
.diagrams
|
||||
.get_mut(&f.source)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.remove(&f.node)
|
||||
.unwrap();
|
||||
expected
|
||||
.diagrams
|
||||
.get_mut(&f.target)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.insert(f.node, placement);
|
||||
let revision = f.app.document.revision();
|
||||
let captured = f.app.route_context().unwrap();
|
||||
relocation_field(&mut f.app, "/diagram", f.target);
|
||||
let message = if edge_hit {
|
||||
Message::CanvasSelect(captured, f.edge)
|
||||
} else {
|
||||
Message::SelectNode(f.other_node)
|
||||
};
|
||||
let _ = f.app.update(message);
|
||||
assert_eq!(f.app.project(), &expected);
|
||||
assert_eq!(f.app.document.revision(), revision + 1);
|
||||
relocation_selection(&f.app, f.target, f.node);
|
||||
assert_eq!(
|
||||
f.app.status,
|
||||
"Properties applied. The view changed; select a visible target again."
|
||||
);
|
||||
assert!(
|
||||
f.app.multiselect_snapshot().is_none_or(|snapshot| {
|
||||
!snapshot.targets().iter().any(|target| {
|
||||
matches!(target, crate::diagram_usage::Target::Placement(id) if *id == f.other_node)
|
||||
|| matches!(target, crate::diagram_usage::Target::Edge(id) if *id == f.edge)
|
||||
})
|
||||
}),
|
||||
"the rejected old hit must not enter the destination selection"
|
||||
);
|
||||
assert_eq!(expected_document.project(), &expected);
|
||||
assert_eq!(f.app.document.bundle(), expected_document.bundle());
|
||||
relocation_source_pair(&f.app);
|
||||
relocation_history(&mut f.app, &before, &expected_document);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejected_placement_destination_preserves_owner_model_source_and_draft() {
|
||||
let mut f = relocation_fixture(Layer::Oa, Layer::Sa);
|
||||
let before = f.app.document.snapshot();
|
||||
let revision = f.app.document.revision();
|
||||
relocation_field(&mut f.app, "/diagram", Id::new_v4());
|
||||
let draft = serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap();
|
||||
assert!(!f.app.apply());
|
||||
assert_eq!(f.app.project(), &before.decoded.project);
|
||||
assert_eq!(f.app.document.bundle(), &before.bundle);
|
||||
assert_eq!(f.app.document.revision(), revision);
|
||||
assert!(!f.app.document.can_undo());
|
||||
relocation_selection(&f.app, f.source, f.node);
|
||||
assert_eq!(
|
||||
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
|
||||
draft
|
||||
);
|
||||
}
|
||||
@@ -39,24 +39,6 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
/// Contextual drawing follows the visible semantic source, not the palette layer.
|
||||
/// The caller separately validates the menu/context lifetime and pending drafts.
|
||||
pub fn can_begin(project: &Project, diagram: Id, tool: Tool, source: Id) -> bool {
|
||||
let Some(diagram) = project.diagrams.get(&diagram) else {
|
||||
return false;
|
||||
};
|
||||
let Some(element) = project.elements.get(&source) else {
|
||||
return false;
|
||||
};
|
||||
diagram.projection.is_architecture()
|
||||
&& (element.layer() == diagram.layer
|
||||
|| diagram.projection == oa_core::ProjectionKind::Traceability)
|
||||
&& Tool::for_layer(element.layer()).contains(&tool)
|
||||
&& tool.can_start(project, source)
|
||||
&& (tool != Tool::Allocation || tool.available_in(element.layer(), diagram.projection))
|
||||
&& visible(&oa_core::projection::project_view(project, diagram.id)).any(|id| id == source)
|
||||
}
|
||||
|
||||
pub fn visible(view: &ProjectedView) -> impl Iterator<Item = Id> + '_ {
|
||||
view.nodes
|
||||
.iter()
|
||||
|
||||
@@ -993,10 +993,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
world,
|
||||
));
|
||||
}
|
||||
return Some(
|
||||
Action::publish(Message::CanvasSelect(self.context, port.element))
|
||||
.and_capture(),
|
||||
);
|
||||
return Some(Action::publish(Message::Select(port.element)).and_capture());
|
||||
}
|
||||
if let Some(id) = self.hit(view, p) {
|
||||
let position = self
|
||||
@@ -1013,7 +1010,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
.hit_label(&scene.routed, p)
|
||||
.or_else(|| self.hit_association_label(&scene.routed, p))
|
||||
{
|
||||
Some(Action::publish(Message::CanvasSelect(self.context, id)).and_capture())
|
||||
Some(Action::publish(Message::Select(id)).and_capture())
|
||||
} else if let Some((id, segment)) = self.hit_edge(&scene.routed, p) {
|
||||
if id == self.selected
|
||||
&& self.editable
|
||||
@@ -1029,7 +1026,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
|
||||
self.begin_route(state, world, Handle::Bend(index), route);
|
||||
Some(Action::capture())
|
||||
} else {
|
||||
Some(Action::publish(Message::CanvasSelect(self.context, id)).and_capture())
|
||||
Some(Action::publish(Message::Select(id)).and_capture())
|
||||
}
|
||||
} else {
|
||||
state.pan_start = Some((mouse::Button::Left, p));
|
||||
@@ -2444,7 +2441,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.0;
|
||||
assert!(matches!(selected,Some(Message::CanvasSelect(_, id)) if id==port.element));
|
||||
assert!(matches!(selected,Some(Message::Select(id)) if id==port.element));
|
||||
let request = canvas.context_request(
|
||||
&state,
|
||||
click,
|
||||
@@ -3057,7 +3054,7 @@ mod tests {
|
||||
let mut state = State::default();
|
||||
assert_eq!(canvas.hit_association_label(&routed, at), Some(edge));
|
||||
assert!(
|
||||
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),at),Some(Message::CanvasSelect(_, id)) if id==edge)
|
||||
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),at),Some(Message::Select(id)) if id==edge)
|
||||
);
|
||||
assert!(
|
||||
state.route_drag.is_none(),
|
||||
@@ -3107,7 +3104,7 @@ mod tests {
|
||||
let center = scene.routed.edges[&edge].label.center;
|
||||
let screen = point(center) + canvas.pan;
|
||||
assert!(
|
||||
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),screen),Some(Message::CanvasSelect(_, id)) if id==edge)
|
||||
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),screen),Some(Message::Select(id)) if id==edge)
|
||||
);
|
||||
canvas.selected = edge;
|
||||
pointer(
|
||||
|
||||
@@ -179,7 +179,7 @@ fn coalesced_port_drag_commits_once_through_app_and_source_history() {
|
||||
zoom,
|
||||
);
|
||||
assert_eq!(messages.len(), 2);
|
||||
assert!(matches!(&messages[0],Message::CanvasSelect(_, id)if *id==output));
|
||||
assert!(matches!(&messages[0],Message::Select(id)if *id==output));
|
||||
assert!(
|
||||
matches!(&messages[1],Message::PortAnchor(port_edit::Event::Commit(_,_,anchor))if anchor.port==output&&anchor.side==PortAnchorSide::Top)
|
||||
);
|
||||
@@ -251,7 +251,7 @@ fn menu_captured_motion_must_not_turn_following_click_into_old_port_drag() {
|
||||
b,
|
||||
);
|
||||
let picked = match &press {
|
||||
Message::CanvasSelect(_, id) => Some(*id),
|
||||
Message::Select(id) => Some(*id),
|
||||
_ => None,
|
||||
};
|
||||
let release_debug = format!("{release:?}");
|
||||
@@ -300,7 +300,7 @@ fn drag_from_already_selected_stationary_port_keeps_source_between_batches() {
|
||||
.filter_map(|e| event(&c, &mut state, e, b))
|
||||
.collect();
|
||||
assert!(
|
||||
matches!(messages.first(),Some(Message::CanvasSelect(_, id)) if *id==output),
|
||||
matches!(messages.first(),Some(Message::Select(id)) if *id==output),
|
||||
"Stationary source was lost between batches: {messages:?}"
|
||||
);
|
||||
assert!(
|
||||
|
||||
@@ -165,7 +165,7 @@ fn nested_owner_palette_selection_drag_and_context_menu_pick_the_same_port() {
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.0;
|
||||
assert!(matches!(press, Some(Message::CanvasSelect(_, id)) if id == port.element));
|
||||
assert!(matches!(press, Some(Message::Select(id)) if id == port.element));
|
||||
assert_eq!(state.port_drag.as_ref().unwrap().anchor.port, port.element);
|
||||
let menu = canvas.context_request(&state, at, bounds);
|
||||
assert_eq!(menu.object, port.element);
|
||||
|
||||
@@ -375,7 +375,7 @@ mod tests {
|
||||
messages.extend(message);
|
||||
}
|
||||
}
|
||||
assert!(matches!(messages.first(), Some(Message::CanvasSelect(_, id)) if *id == port));
|
||||
assert!(matches!(messages.first(), Some(Message::Select(id)) if *id == port));
|
||||
assert!(
|
||||
matches!(messages.last(), Some(Message::PortAnchor(crate::port_edit::Event::Commit(_, _, anchor))) if anchor.port == port && anchor.side == PortAnchorSide::Top)
|
||||
);
|
||||
|
||||
@@ -322,8 +322,6 @@ fn article(topic: Topic) -> Article {
|
||||
"Show a correspondence between OA and SA",
|
||||
&[
|
||||
"Choose OA ↔ SA traceability in View content. Changing this setting does not create counterparts or add their placements, so an existing single-layer diagram can initially look unchanged.",
|
||||
"Switching back to Architecture or another single-layer projection shows only that view's layer. The other layer's placements, routing and saved visibility remain stored; return to OA ↔ SA traceability to show them again, subject to the view's filters. Selecting a displayed card, port or line and opening its Properties keeps the current view and perspective. Applying ordinary Properties edits keeps that saved architecture view open. Changing a placement's Diagram explicitly moves it and follows its destination. Changes you make to the view's own perspective or identity still take effect. If the app asks you to select again, choose a visible target.",
|
||||
"In a mixed traceability view, use Draw from a visible source's canvas menu or the Model explorer sidebar on the Architecture page to connect from that source's OA or SA layer without leaving the view. Choose a highlighted compatible visible target; Escape cancels. The palette offers tools for the view's perspective. Opening an endpoint or selecting a record in the Model explorer sidebar follows that record's perspective. Contextual relationship forms keep the view while opening; applying the reviewed relationship can navigate to its result.",
|
||||
"Use Review layer traces to inspect named Operational and System endpoints and the meaning of their link. This opens the model's layer-trace list with no search restriction; traces between relationships are reviewed here rather than drawn between element cards.",
|
||||
"In the chosen architecture view, use Add existing elements. The chooser offers compatible elements from both layers. Select an existing endpoint and click its position in the canvas; repeat for the other end if needed. This adds a placement, not a new semantic element or trace. For port correspondences, place the ports' owners.",
|
||||
"The view shows correspondence lines alongside ordinary relationships. The shown-line count reflects the current projection and visibility settings, not trace coverage. If a line is hidden, use View visibility and inspect Saved view visibility; placing an endpoint does not remove explicit hides or filters.",
|
||||
|
||||
@@ -557,7 +557,10 @@ pub fn popup(app: &App) -> Option<Menu<Message>> {
|
||||
&format!("Draw {}", tool.label().to_lowercase()),
|
||||
Message::Connection(crate::connection_edit::Event::Begin(*tool, id)),
|
||||
app.active_diagram.is_some_and(|diagram| {
|
||||
crate::connection_edit::can_begin(p, diagram, *tool, id)
|
||||
crate::connection_edit::visible(&projection::project_view(
|
||||
p, diagram,
|
||||
))
|
||||
.any(|element| element == id)
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -372,150 +372,3 @@ async fn source6_http_explicit_communication_ports_preview_apply_and_restart_pre
|
||||
assert_pair(&reopened);
|
||||
server.stop().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mixed_traceability_projection_roundtrip_preserves_dormant_http_state() {
|
||||
let f = fixture();
|
||||
let mut model = compile_bundle(&f.bundle, ProjectLimits::default())
|
||||
.unwrap()
|
||||
.decoded
|
||||
.project;
|
||||
let view = model.diagrams.get_mut(&f.diagram).unwrap();
|
||||
view.layer = Layer::Sa;
|
||||
view.projection = ProjectionKind::Traceability;
|
||||
// The OA placements and category are intentionally opposite the view's layer.
|
||||
view.view_options.hidden_edges = vec![f.mean];
|
||||
for (index, element) in f.sa.into_iter().enumerate() {
|
||||
model.place(
|
||||
f.diagram,
|
||||
element,
|
||||
Position {
|
||||
x: index as f32 * 400.,
|
||||
y: 300.,
|
||||
},
|
||||
);
|
||||
}
|
||||
let mut bundle = f.bundle.clone();
|
||||
bundle.files.insert(
|
||||
bundle.entry.clone(),
|
||||
format!(
|
||||
"// Authored source-6 engineering rationale\n{}",
|
||||
oa_core::language::format(&model).replacen(
|
||||
"{\n",
|
||||
"{\n include \"notes.arcadia\";\n",
|
||||
1
|
||||
)
|
||||
),
|
||||
);
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let server = TestServer::start(directory.path(), None).await;
|
||||
let original = import(&server, &bundle).await;
|
||||
assert_pair(&original);
|
||||
let resource = original.resource_id;
|
||||
let mixed = oa_core::projection::project_view(&original.model, f.diagram);
|
||||
assert!(
|
||||
f.oa.iter()
|
||||
.chain(&f.sa)
|
||||
.all(|id| mixed.nodes.iter().any(|n| n.element == *id))
|
||||
);
|
||||
|
||||
let mut ordinary = original.model.diagrams[&f.diagram].clone();
|
||||
ordinary.projection = ProjectionKind::Allocation;
|
||||
let response = transact(
|
||||
&server,
|
||||
&original,
|
||||
vec![Command::EditDiagram(f.diagram, ordinary.clone())],
|
||||
)
|
||||
.await;
|
||||
let status = response.status();
|
||||
let etag = response
|
||||
.headers()
|
||||
.get("etag")
|
||||
.map(|v| v.to_str().unwrap().to_owned());
|
||||
let result: Value = response.json().await.unwrap();
|
||||
assert_eq!(status, StatusCode::OK, "{result}");
|
||||
assert_eq!(result["changed"], true);
|
||||
assert_eq!(result["dry_run"], false);
|
||||
assert_eq!(result["revision"], original.revision + 1);
|
||||
let changed = server.get(resource).await;
|
||||
assert_eq!(etag.as_deref(), Some(changed.etag().as_str()));
|
||||
let mut expected = original.model.clone();
|
||||
expected.diagrams.insert(f.diagram, ordinary.clone());
|
||||
assert_eq!(
|
||||
changed.model, expected,
|
||||
"only the projection changes; dormant state is retained"
|
||||
);
|
||||
assert_pair(&changed);
|
||||
assert_eq!(
|
||||
changed.source_project.files["notes.arcadia"],
|
||||
bundle.files["notes.arcadia"]
|
||||
);
|
||||
assert!(
|
||||
changed.source_project.files[&bundle.entry]
|
||||
.starts_with("// Authored source-6 engineering rationale\n")
|
||||
);
|
||||
let projected = oa_core::projection::project_view(&changed.model, f.diagram);
|
||||
assert!(projected.nodes.iter().all(|n| !f.oa.contains(&n.element)));
|
||||
assert!(
|
||||
f.sa.iter()
|
||||
.all(|id| projected.nodes.iter().any(|n| n.element == *id))
|
||||
);
|
||||
|
||||
// A dormant option still must name an existing category. Rejection must not
|
||||
// advance the ETag or rewrite either side of the persisted model/source pair.
|
||||
let disk = directory.path().join(format!("{resource}.json"));
|
||||
let accepted_bytes = std::fs::read(&disk).unwrap();
|
||||
ordinary.view_options.participant_categories = CategorySelection::Any(vec![Id::new_v4()]);
|
||||
let rejected = transact(
|
||||
&server,
|
||||
&changed,
|
||||
vec![Command::EditDiagram(f.diagram, ordinary)],
|
||||
)
|
||||
.await;
|
||||
assert_eq!(rejected.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
let retained = server.get(resource).await;
|
||||
assert_eq!(retained.etag(), changed.etag());
|
||||
assert_eq!(
|
||||
serde_json::to_value(&retained).unwrap(),
|
||||
serde_json::to_value(&changed).unwrap()
|
||||
);
|
||||
assert_eq!(std::fs::read(&disk).unwrap(), accepted_bytes);
|
||||
server.stop().await;
|
||||
|
||||
let server = TestServer::start(directory.path(), None).await;
|
||||
let reopened = server.get(resource).await;
|
||||
assert_eq!(
|
||||
serde_json::to_value(&reopened).unwrap(),
|
||||
serde_json::to_value(&changed).unwrap()
|
||||
);
|
||||
assert_pair(&reopened);
|
||||
let response = transact(
|
||||
&server,
|
||||
&reopened,
|
||||
vec![Command::EditDiagram(
|
||||
f.diagram,
|
||||
original.model.diagrams[&f.diagram].clone(),
|
||||
)],
|
||||
)
|
||||
.await;
|
||||
let status = response.status();
|
||||
let result: Value = response.json().await.unwrap();
|
||||
assert_eq!(status, StatusCode::OK, "{result}");
|
||||
assert_eq!(result["changed"], true);
|
||||
assert_eq!(result["revision"], changed.revision + 1);
|
||||
let restored = server.get(resource).await;
|
||||
assert_eq!(restored.model, original.model);
|
||||
assert_pair(&restored);
|
||||
assert_eq!(
|
||||
restored.source_project.files["notes.arcadia"],
|
||||
bundle.files["notes.arcadia"]
|
||||
);
|
||||
let projected = oa_core::projection::project_view(&restored.model, f.diagram);
|
||||
assert!(
|
||||
f.oa.iter()
|
||||
.chain(&f.sa)
|
||||
.all(|id| projected.nodes.iter().any(|n| n.element == *id))
|
||||
);
|
||||
assert!(!projected.edges.iter().any(|edge| edge.id == f.mean));
|
||||
server.stop().await;
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ schemas["ViewOptions"] = obj({
|
||||
"coverage": {**ref("Coverage"), "default": "All"},
|
||||
"work_contexts": {**ref("ContextSelection"), "default": "All"},
|
||||
"exchange_labels": {**ref("ExchangeLabels"), "default": "Name"}}, [])
|
||||
schemas["ViewOptions"]["description"] = "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. String projections preserve both-layer hidden/category/context references and latent placements; only Traceability renders both layers. Typed StateMachine/Scenario views retain layer-scoped admission. Filtering preserves unfiltered automatic port positions."
|
||||
schemas["ViewOptions"]["description"] = "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. Traceability permits both-layer hidden/category/context references; ordinary projections require the diagram layer. Filtering preserves unfiltered automatic port positions."
|
||||
schemas["Diagram"]["properties"]["view_options"] = {**ref("ViewOptions"), "default": {}}
|
||||
|
||||
def upsert_variant(variants, name, value):
|
||||
|
||||
@@ -570,15 +570,8 @@ retain their identities and contracts; migration does not silently replace them.
|
||||
``coverage`` and ``work_contexts``, and ``Name`` for ``exchange_labels``.
|
||||
``{"Only":[]}`` hides all relation and trace lines; ``{"Any":[]}`` preserves
|
||||
an explicit empty category/context selection. Category members union within
|
||||
each family, and independent filters intersect. String projections permit saved
|
||||
references and placements from both layers. Only ``Traceability`` renders both;
|
||||
other string projections filter to the diagram's declared layer. ``EditDiagram``
|
||||
may change between these projections without deleting latent placements, routes
|
||||
or visibility choices. Returning to Traceability restores their applicability.
|
||||
Typed StateMachine/Scenario views retain their strict membership and layer checks.
|
||||
Missing objects, wrong reference kinds/families and duplicate selections remain
|
||||
invalid. Frozen versions 1–6 keep their prior layer admission before migration;
|
||||
older version-7 implementations may reject these newly accepted ordinary views.
|
||||
each family, and independent filters intersect. ``Traceability`` permits
|
||||
references from both layers; ordinary projections require the diagram's layer.
|
||||
These presentation choices preserve semantic records, routes, placements and
|
||||
the automatic port positions derived from unfiltered eligible exchanges.
|
||||
|
||||
|
||||
@@ -409,12 +409,8 @@ filters rather than widening them to All.
|
||||
|
||||
Traceability views retain their established two-layer projection: default options
|
||||
preserve eligible OA and SA lines. Their hidden-edge, category and context
|
||||
references may therefore select either layer. Ordinary string projections retain
|
||||
these references from either layer as saved presentation choices; they still
|
||||
render only their declared layer. Switching projections MUST preserve those
|
||||
choices, including explicit empty selections. Existing-object, reference-kind,
|
||||
family and uniqueness constraints still apply. Typed StateMachine and Scenario
|
||||
views retain layer-scoped reference admission. A filter never hides the other
|
||||
references may therefore select either layer. All ordinary projections require
|
||||
these references in the declared diagram layer. A filter never hides the other
|
||||
layer merely because the Traceability diagram has an OA or SA layer label.
|
||||
|
||||
Versions 1–5 reject the new relation, category constructor, diagram field (even
|
||||
@@ -1077,20 +1073,6 @@ capabilities with their involvement, exploitation, include, extend and
|
||||
generalization links. Decomposition and Overview retain general containment;
|
||||
Overview also exposes all relationships. These string projections hide
|
||||
inapplicable placements without deleting them or changing semantic records.
|
||||
This includes opposite-layer placements retained after leaving Traceability.
|
||||
Changing between string projections changes only the projection value; placement
|
||||
identities, positions, sizes, collapsed state, port anchors, routes and visibility
|
||||
choices remain authored. Returning to Traceability restores their applicability.
|
||||
Ordinary views MUST NOT render opposite-layer nodes, parents, ports or lines.
|
||||
This current-version admission correction does not broaden frozen source/JSON
|
||||
versions 1–6: their layer-scoped placement and visibility checks remain required
|
||||
before migration. Earlier version-7 implementations can reject ordinary views
|
||||
containing these retained opposite-layer records.
|
||||
|
||||
The explicit RelocateNode operation moves a placement to a different diagram
|
||||
only when its element belongs to the destination layer or the destination is
|
||||
Traceability. Retaining an existing hidden placement through a projection change
|
||||
does not authorize a relocation that immediately hides it in another layer.
|
||||
|
||||
A StateMachine projection MUST reference an existing StateMachine in the
|
||||
diagram's layer. Its placements MUST refer to that machine, its Regions, or
|
||||
|
||||
@@ -12490,7 +12490,7 @@
|
||||
},
|
||||
"required": [],
|
||||
"additionalProperties": false,
|
||||
"description": "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. String projections preserve both-layer hidden/category/context references and latent placements; only Traceability renders both layers. Typed StateMachine/Scenario views retain layer-scoped admission. Filtering preserves unfiltered automatic port positions."
|
||||
"description": "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. Traceability permits both-layer hidden/category/context references; ordinary projections require the diagram layer. Filtering preserves unfiltered automatic port positions."
|
||||
},
|
||||
"CommunicationPortChoice": {
|
||||
"oneOf": [
|
||||
|
||||
@@ -1,31 +1,5 @@
|
||||
# Architectural and product decisions
|
||||
|
||||
## Canvas inspection, contextual drawing and explicit relocation — 2026-10-02
|
||||
|
||||
An ordinary Properties Apply retains the current Architecture diagram, layer and
|
||||
projection. An accepted edit to the active Diagram itself uses its resulting
|
||||
identity, layer and projection. A queued canvas target is revalidated against that retained surface
|
||||
after Apply; an obsolete target is refused with feedback instead of navigating
|
||||
or selecting hidden content. Explicit Select/Open still follows the requested
|
||||
model target. An accepted change to a placement's actual owning diagram is also
|
||||
explicit navigation: it follows the committed destination and resulting UUID.
|
||||
Changing only geometry, alias or UUID in the same owning diagram retains the
|
||||
inspection surface. Invalid or missing ownership never grants this exception.
|
||||
|
||||
Contextual Draw uses the invoked menu target as its semantic source and the current
|
||||
diagram as its editing surface. Shared admission checks the diagram projection,
|
||||
visibility, family and semantic endpoint roles before arming. Restart clears the
|
||||
pending source while retaining an admitted tool. Explicit model navigation and
|
||||
post-review creation navigation retain their separate behavior. This avoids
|
||||
silently conflating an opposite-layer inspection with a request to change views.
|
||||
|
||||
The source repairs at c706 and 6018, the failed c706 workspace attempt, the
|
||||
full-model/source invariants and corrected Windows acceptance are attributed in
|
||||
[the navigation checkpoint](navigation-validation-20261002.md). The simpler
|
||||
blanket surface restoration was rejected when the unchanged relocation test
|
||||
showed that it hid successful explicit moves. This is bounded Architecture
|
||||
acceptance; diagram label readability and the complete OA/SA goal remain open.
|
||||
|
||||
## Private source composition decision — 2026-09-23
|
||||
|
||||
Adopt the opaque validated original-span rename batch as the next private
|
||||
|
||||
@@ -1,24 +1,3 @@
|
||||
# Navigation contribution checkpoint — 2026-10-02 UTC
|
||||
|
||||
Verified production source **6018ae30b9296b9f6b37d7fc038175080d15fafb** preserves
|
||||
ordinary opposite-layer Properties inspection and contextual drawing while
|
||||
following accepted explicit placement relocation. Windows full workspace tests
|
||||
pass **2,236 / 0 failures / 8 existing ignores**; formatting, strict workspace
|
||||
all-targets Clippy and matching desktop/server/CLI builds pass. Native creation,
|
||||
projection/history, corrected relocation and saved-source reopen evidence retain
|
||||
their actual c706/6018 attribution and independent reviews.
|
||||
[Source, commands, portable evidence and limits](navigation-validation-20261002.md)
|
||||
are authoritative. The earlier c706 workspace failure remains recorded.
|
||||
|
||||
This is a contribution checkpoint, not the complete OA/SA application or an
|
||||
integrated Help/Save release. Label ellipsis/readability and broader usability
|
||||
remain open. **Linux/WSL is still excluded by the user's explicit pause.**
|
||||
[Issue48](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/48)
|
||||
coordinates the subsequent verified union with delivered Save/Help; ownership,
|
||||
live status and release decisions belong there. Existing released versions are
|
||||
unchanged. Earlier integrated and contribution checkpoints follow with their
|
||||
original source and acceptance boundaries.
|
||||
|
||||
# Integrated prerelease source checkpoint — 2026-09-24 UTC
|
||||
|
||||
This branch assembles peer contributions for the Windows prerelease in issue37.
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Connection tool icons
|
||||
|
||||
Branch candidate for [#41](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/41), based on published Windows preview source `715036e132677dd2bc2fb37ed316d93dc9987aec`. This is not integrated or native acceptance.
|
||||
|
||||
The reported collapsed palette repeats one arrow for three different tools. Extend the existing Rust/Iced canvas glyphs at their actual 16×16 size: work endpoints plus an arrow for Interaction/Functional exchange, neutral participant endpoints for Communication means, facing component ports for Boundary exchange, and work entering a performer for Allocation. The first pair never coexist in one perspective. Unoriented generic tools must not imply a fixed direction.
|
||||
|
||||
Letter badges were rejected as language-dependent and cramped. Different arrow/dash styles risk confusing tool identity with relation direction. Native endpoint/container shapes fit the existing icon system without font or asset dependencies. Preserve button dimensions, explicit labels/tooltips, ordering, messages, availability and selection rules.
|
||||
|
||||
Independent source critique found no blocker and confirmed the tool semantics and Iced API use. Actual-size legibility remains unverified, particularly the exchange arrow, port details and possible download/import interpretation of Allocation. Verify enabled, disabled, hovered and selected palettes in both perspectives, expansion/collapse, tool activation, representative relation creation and cancellation. Existing keyboard/accessibility behavior must be checked, not inferred from hover labels. Reuse semantic connection tests; geometry hashes alone are not an identification or usability evaluator.
|
||||
|
||||
Linux remains excluded by the existing user pause. Live ownership and integration status belong to the linked issue.
|
||||
@@ -1,714 +0,0 @@
|
||||
"""Navigation evidence transport. Integrity is not product acceptance.
|
||||
|
||||
Commands: freeze TEMPLATE SOURCE_LOCAL NEW_FROZEN_CONFIG;
|
||||
build FROZEN_CONFIG SOURCE_LOCAL FRESH_PACKET; verify PACKET;
|
||||
selftest FRESH_DIRECTORY. No extraction, subprocesses, or native execution.
|
||||
"""
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path, PurePosixPath
|
||||
import re
|
||||
import stat
|
||||
import sys
|
||||
import zipfile
|
||||
import datetime
|
||||
import copy
|
||||
|
||||
MAX_FILE = 128 * 1024 * 1024
|
||||
MAX_TOTAL = 1024 * 1024 * 1024
|
||||
EXCLUDED = {"bin", "profile", "target", ".git", "navigation-oracle-runtime"}
|
||||
BLOCKED_SUFFIXES = {'.exe', '.dll', '.pdb', '.lib', '.obj', '.pfx', '.pem', '.key'}
|
||||
|
||||
|
||||
def need(ok, message):
|
||||
if not ok:
|
||||
raise ValueError(message)
|
||||
|
||||
|
||||
def pairs(items):
|
||||
out = {}
|
||||
for key, value in items:
|
||||
need(key not in out, "duplicate JSON key: " + key)
|
||||
out[key] = value
|
||||
return out
|
||||
|
||||
|
||||
def decode(data):
|
||||
def reject_constant(value):
|
||||
raise ValueError('nonfinite JSON constant: ' + value)
|
||||
return json.loads(data, object_pairs_hook=pairs, parse_constant=reject_constant)
|
||||
|
||||
|
||||
def digest(data):
|
||||
return hashlib.sha256(data).hexdigest()
|
||||
|
||||
|
||||
def safe(name):
|
||||
need(isinstance(name, str) and name and "\\" not in name and ":" not in name,
|
||||
"unsafe path")
|
||||
p = PurePosixPath(name)
|
||||
need(bool(p.parts) and not p.is_absolute() and str(p) == name, "noncanonical path: " + name)
|
||||
need(all(x not in (".", "..") and not x.endswith((".", " ")) for x in p.parts),
|
||||
"unsafe component: " + name)
|
||||
need(not any(re.fullmatch(r"(?i)(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?", x)
|
||||
for x in p.parts), "reserved component")
|
||||
need(not any(ord(c) < 32 or c in '<>"|?*' for c in name), "nonportable path")
|
||||
return name
|
||||
|
||||
|
||||
def permitted(name):
|
||||
safe(name)
|
||||
parts = PurePosixPath(name).parts
|
||||
need(not any(x.casefold() in EXCLUDED | {'.ssh', '.codex', '.config', 'credentials', 'auth'}
|
||||
or x.casefold().startswith('.env') for x in parts), 'excluded/private input')
|
||||
need(PurePosixPath(name).suffix.casefold() not in BLOCKED_SUFFIXES
|
||||
and PurePosixPath(name).name.casefold() != 'push-navigation.py', 'binary/private input')
|
||||
|
||||
|
||||
def hash_string(value):
|
||||
need(isinstance(value, str) and re.fullmatch('[0-9a-f]{64}', value), 'invalid hash')
|
||||
return value
|
||||
|
||||
|
||||
def timestamp(value):
|
||||
need(isinstance(value, str) and value, 'missing timestamp')
|
||||
parsed = datetime.datetime.fromisoformat(value)
|
||||
need(parsed.tzinfo is not None, 'timestamp needs timezone')
|
||||
return parsed
|
||||
|
||||
|
||||
def inventory(value):
|
||||
need(isinstance(value, dict) and value, 'missing identity inventory')
|
||||
folded = set()
|
||||
for name, sha in value.items():
|
||||
safe(name)
|
||||
need(name.casefold() not in folded, 'identity inventory case collision')
|
||||
folded.add(name.casefold())
|
||||
hash_string(sha)
|
||||
|
||||
|
||||
def validate_config(config, frozen=False):
|
||||
need(config.get('schema') == 'navigation-evidence-config-v2', 'config schema')
|
||||
need(config.get('exclusions') == sorted(EXCLUDED), 'exclusion policy mismatch')
|
||||
includes = config['includes']
|
||||
need(isinstance(includes, list) and includes, 'empty includes')
|
||||
names = []
|
||||
for item in includes:
|
||||
name = safe(item['path'])
|
||||
permitted(name)
|
||||
need(item['kind'] in ('file', 'tree') and isinstance(item['category'], str)
|
||||
and item['category'], 'invalid include')
|
||||
folded = name.casefold()
|
||||
need(not any(folded == old or folded.startswith(old + '/') or old.startswith(folded + '/')
|
||||
for old in names), 'overlapping include roots')
|
||||
names.append(folded)
|
||||
for key in ('gates', 'native_cases', 'oracle_runs'):
|
||||
values = [x['name'] if isinstance(x, dict) else x for x in config[key]]
|
||||
need(len(values) == len(set(values)), 'duplicate case')
|
||||
for name in values:
|
||||
need(re.fullmatch('[a-z0-9-]+', name), 'invalid case name')
|
||||
if frozen:
|
||||
need(config.get('frozen_entries'), 'freeze and review exact file inventory first')
|
||||
|
||||
|
||||
def coverage(config, entries):
|
||||
seen = set()
|
||||
for entry in entries:
|
||||
name = entry['path']
|
||||
permitted(name)
|
||||
need(name.casefold() not in seen, 'duplicate logical path')
|
||||
seen.add(name.casefold())
|
||||
owners = [i for i in config['includes'] if name == i['path'] or
|
||||
(i['kind'] == 'tree' and name.startswith(i['path'] + '/'))]
|
||||
need(len(owners) == 1 and entry['category'] == owners[0]['category'], 'outside whitelist/category')
|
||||
for item in config['includes']:
|
||||
need(any(e['path'] == item['path'] or (item['kind'] == 'tree' and
|
||||
e['path'].startswith(item['path'] + '/')) for e in entries), 'missing required include')
|
||||
for name in config['documentation']:
|
||||
need(safe(name).casefold() in seen, 'missing documentation')
|
||||
|
||||
|
||||
def regular(path):
|
||||
# Also rejects Windows junctions, including ancestors outside the selected root.
|
||||
for item in [path, *path.parents]:
|
||||
s = item.lstat()
|
||||
need(not stat.S_ISLNK(s.st_mode) and
|
||||
not getattr(s, "st_file_attributes", 0) & 0x400, "link/reparse: " + str(item))
|
||||
need(path.is_file(), "not a regular file: " + str(path))
|
||||
need(path.stat().st_size <= MAX_FILE, "file size cap")
|
||||
|
||||
|
||||
def read(path):
|
||||
regular(path)
|
||||
data = path.read_bytes()
|
||||
need(len(data) <= MAX_FILE, "file grew over cap")
|
||||
return data
|
||||
|
||||
|
||||
def walk_checked(base, exclude=False):
|
||||
# Check before descending, including junctions; os.walk never gets an
|
||||
# unchecked reparse directory to recurse into.
|
||||
for item in [base, *base.parents]:
|
||||
s = item.lstat()
|
||||
need(not stat.S_ISLNK(s.st_mode) and
|
||||
not getattr(s, "st_file_attributes", 0) & 0x400, "link/reparse ancestor")
|
||||
for current, dirs, files in os.walk(base, followlinks=False):
|
||||
if exclude:
|
||||
dirs[:] = [x for x in dirs if x.casefold() not in EXCLUDED]
|
||||
for name in dirs + files:
|
||||
path = Path(current) / name
|
||||
s = path.lstat()
|
||||
need(not stat.S_ISLNK(s.st_mode) and
|
||||
not getattr(s, "st_file_attributes", 0) & 0x400, "link/reparse tree")
|
||||
for name in files:
|
||||
yield Path(current) / name
|
||||
|
||||
|
||||
def selected(source, config):
|
||||
found = {}
|
||||
for item in config["includes"]:
|
||||
name = safe(item["path"])
|
||||
base = source / name
|
||||
need(base.exists(), "required input missing: " + name)
|
||||
candidates = [base] if item["kind"] == "file" else sorted(walk_checked(base, exclude=True))
|
||||
need(item["kind"] in ("file", "tree"), "unknown include kind")
|
||||
for file in candidates:
|
||||
relative = file.relative_to(source).as_posix()
|
||||
if any(x.casefold() in EXCLUDED for x in PurePosixPath(relative).parts):
|
||||
continue
|
||||
# Check directories too, before skipping them; never traverse a junction.
|
||||
s = file.lstat()
|
||||
need(not stat.S_ISLNK(s.st_mode) and
|
||||
not getattr(s, "st_file_attributes", 0) & 0x400, "link/reparse input")
|
||||
if file.is_dir():
|
||||
continue
|
||||
safe(relative)
|
||||
need(relative.casefold() not in found, "overlapping/case-colliding include")
|
||||
regular(file)
|
||||
found[relative.casefold()] = (relative, item["category"], file)
|
||||
return sorted(found.values())
|
||||
|
||||
|
||||
def check_zip(data, source_inputs):
|
||||
need(isinstance(source_inputs, dict) and source_inputs, "missing source inventory")
|
||||
expected = {}
|
||||
for name, sha in source_inputs.items():
|
||||
safe(name)
|
||||
need(name.casefold() not in expected and re.fullmatch("[0-9a-f]{64}", sha),
|
||||
"invalid source inventory")
|
||||
expected[name.casefold()] = (name, sha)
|
||||
seen = set()
|
||||
with zipfile.ZipFile(io.BytesIO(data)) as archive:
|
||||
members = archive.infolist()
|
||||
need(len(members) <= 10000, "ZIP member cap")
|
||||
need(sum(x.file_size for x in members) <= 512 * 1024 * 1024, "ZIP total cap")
|
||||
for member in members:
|
||||
name = safe(member.filename)
|
||||
need(not member.is_dir() and not member.flag_bits & 1, "ZIP directory/encryption")
|
||||
mode = member.external_attr >> 16
|
||||
need(stat.S_IFMT(mode) in (0, stat.S_IFREG), "ZIP nonregular member")
|
||||
need(member.file_size <= 64 * 1024 * 1024, "ZIP member cap")
|
||||
key = name.casefold()
|
||||
need(key not in seen and key in expected and expected[key][0] == name,
|
||||
"ZIP path inventory mismatch")
|
||||
seen.add(key)
|
||||
content = archive.read(member) # zipfile checks CRC; never extracts.
|
||||
need(len(content) == member.file_size and digest(content) == expected[key][1],
|
||||
"ZIP content mismatch")
|
||||
need(seen == set(expected), "ZIP missing inputs")
|
||||
|
||||
|
||||
def streams(record, prefix, get, suffix='.'):
|
||||
for stream in ('stdout', 'stderr'):
|
||||
size = record[stream + '_bytes']
|
||||
need(type(size) is int and size >= 0, 'invalid stream size')
|
||||
path = prefix + suffix + stream if suffix == '.' else prefix + stream + '.log'
|
||||
need(len(get(path)) == size, 'stream size mismatch: ' + path)
|
||||
|
||||
|
||||
def step_sequence(result, planned, get, prefix, stop_on_failure, require_finished=True):
|
||||
steps = result.get('steps')
|
||||
need(isinstance(steps, list) and 0 < len(steps) <= len(planned), 'invalid step count')
|
||||
need(len({s['label'] for s in planned}) == len(planned), 'duplicate plan label')
|
||||
for actual, expected in zip(steps, planned):
|
||||
need(actual['label'] == expected['label'] and actual['command'] == expected['command']
|
||||
and actual.get('expectation', 'exit0') == expected.get('expectation', 'exit0'),
|
||||
'step differs from plan')
|
||||
need(type(actual.get('exit_code')) is int, 'missing step exit')
|
||||
start = actual.get('started', actual.get('start'))
|
||||
need(timestamp(actual['finished']) >= timestamp(start), 'step timestamp order')
|
||||
streams(actual, prefix + safe(actual['label']), get)
|
||||
failed = [i for i,s in enumerate(steps) if s['exit_code'] != 0]
|
||||
if stop_on_failure and failed:
|
||||
need(failed == [len(steps)-1], 'not first-failure stopped prefix')
|
||||
return 'stopped-at-first-failure'
|
||||
need(len(steps) == len(planned), 'gate still open: incomplete successful prefix')
|
||||
if require_finished:
|
||||
need(timestamp(result.get('finished')) >= timestamp(steps[-1]['finished']), 'missing final closure')
|
||||
return 'completed-with-failures' if failed else 'completed-successfully'
|
||||
|
||||
|
||||
def same_fields(before, after, keys):
|
||||
for key in keys:
|
||||
need(key in before and key in after and before[key] is not None and
|
||||
before[key] == after[key], 'identity mismatch/missing: ' + key)
|
||||
|
||||
|
||||
def receipts(config, get, paths):
|
||||
matrix = []
|
||||
for gate in config["gates"]:
|
||||
prefix = "navigation-gates/" + gate["name"] + "/"
|
||||
before = decode(get(prefix + "initial.json"))
|
||||
raw_result = get(prefix + 'result.json')
|
||||
result = decode(raw_result)
|
||||
same_fields(before, result, ('source', 'source_inputs', 'started', 'environment'))
|
||||
need(isinstance(result['source'], str) and re.fullmatch('[0-9a-f]{40}', result['source']), 'source commit')
|
||||
inventory(result['source_inputs'])
|
||||
need(result.get("inputs_unchanged") is True, "gate source conservation missing")
|
||||
if gate['name'] == 'attempt01':
|
||||
need(digest(raw_result) == 'de7c4a7361a4c4991f783c16656af2ac38c49edaaf4d473f26462f3580264486',
|
||||
'historical attempt01 changed')
|
||||
planned = [{'label':a, 'command':b} for a,b in (
|
||||
('fmt','cargo fmt --all -- --check'),
|
||||
('workspace-tests','cargo test --workspace --locked -- --test-threads=1'),
|
||||
('clippy','cargo clippy --workspace --locked --all-targets -- -D warnings'),
|
||||
('native-builds','cargo build --workspace --locked --bins'))]
|
||||
outcome = step_sequence(result, planned, get, prefix, False, False)
|
||||
need(outcome == 'completed-successfully' and gate['zip'] is False and
|
||||
gate.get('missing_zip_reason'), 'historical attempt boundary')
|
||||
outcome = 'historically-closed-no-final-timestamp-no-input-ZIP'
|
||||
else:
|
||||
same_fields(before, result, ('phase', 'plan_sha256'))
|
||||
need(result['phase'] == gate['name'], 'gate phase mismatch')
|
||||
plan_bytes = get(gate['plan'])
|
||||
need(digest(plan_bytes) == result['plan_sha256'], 'gate plan mismatch')
|
||||
plan = decode(plan_bytes)
|
||||
need(plan['head'] == result['source'], 'gate plan source mismatch')
|
||||
outcome = step_sequence(result, plan['steps'], get, prefix, plan.get('stop_on_failure', True))
|
||||
need(gate['zip'] is True, 'unapproved missing source ZIP')
|
||||
if gate["zip"]:
|
||||
check_zip(get(prefix + "inputs.zip"), result["source_inputs"])
|
||||
matrix.append({"kind": "gate", "name": gate["name"], "source": result["source"],
|
||||
"receipt": prefix + "result.json", 'closure':outcome,
|
||||
"exits": [x["exit_code"] for x in result['steps']],
|
||||
"acceptance": "not inferred"})
|
||||
for name in config["native_cases"]:
|
||||
prefix = "navigation-native/" + safe(name) + "/"
|
||||
before = decode(get(prefix + "started.json"))
|
||||
result = decode(get(prefix + "finished.json"))
|
||||
same_fields(before, result, ('case','binary_source','binary_pins','pid','start','argv',
|
||||
'cwd','path','local_app_data'))
|
||||
need(result['case'] == name and type(result['pid']) is int and result['pid'] > 0
|
||||
and isinstance(result['argv'],list) and len(result['argv']) == 1, 'native launch identity')
|
||||
inventory(result['binary_pins'])
|
||||
need(set(result['binary_pins']) == {'arcadia-studio.exe','libunwind.dll'}, 'native binary pins')
|
||||
need(re.fullmatch('[0-9a-f]{40}', result['binary_source']), 'native source identity')
|
||||
if name == 'baseline01':
|
||||
same_fields(before,result,('fixture_sha256','release_tag'))
|
||||
hash_string(result['fixture_sha256'])
|
||||
need(result.get('fixture_unchanged') is True,'legacy fixture changed')
|
||||
else:
|
||||
same_fields(before,result,('original_fixtures',))
|
||||
inventory(result['original_fixtures'])
|
||||
need(result.get('original_fixtures_unchanged') is True,'native fixtures changed')
|
||||
need(result.get('binaries_unchanged') is True,'native binaries changed')
|
||||
need(type(result.get('exit_code')) is int and
|
||||
timestamp(result.get('finished')) >= timestamp(result['start']), 'native still open')
|
||||
streams(result,prefix,get,suffix='native')
|
||||
get(prefix + "observations.md")
|
||||
matrix.append({"kind": "native", "name": name, "source": result.get("binary_source"),
|
||||
"receipt": prefix + "finished.json", "binary_pins": result.get("binary_pins"),
|
||||
"exit": result["exit_code"], "acceptance": "not inferred"})
|
||||
for name in config["oracle_runs"]:
|
||||
prefix = "navigation-oracle-runs/" + safe(name) + "/"
|
||||
before = decode(get(prefix+'initial.json'))
|
||||
raw = get(prefix+'result.json')
|
||||
result = decode(raw)
|
||||
same_fields(before,result,('case','head','started','environment','qa_root','source_inputs'))
|
||||
need(result['case']==name,'oracle case mismatch')
|
||||
need(re.fullmatch('[0-9a-f]{40}',result['head']),'oracle source identity')
|
||||
inventory(result['source_inputs'])
|
||||
need(result.get('inputs_unchanged_except_initial_isolated_lock') is True,'oracle input drift')
|
||||
target = 'C:/Users/User/Documents/Git/ARCADIA-Studio/.local/target-gnullvm'
|
||||
planned = [
|
||||
{'label':'tests','command':f'cargo test --offline --manifest-path .local/navigation-oracle/Cargo.toml --target-dir {target} -- --nocapture --test-threads=1'},
|
||||
{'label':'build','command':f'cargo build --locked --offline --manifest-path .local/navigation-oracle/Cargo.toml --target-dir {target}'}]
|
||||
outcome = step_sequence(result,planned,get,prefix,True)
|
||||
final_lock = hash_string(result['final_utility_lock_sha256'])
|
||||
need(all(s['utility_lock_sha256']==final_lock for s in result['steps']), 'oracle lock drift')
|
||||
if name == 'attempt01':
|
||||
need(digest(raw)=='6523dd082eb02ad6f93130320fdfac4bcd3e320e61534a8ae1a54977537d17e7'
|
||||
and outcome=='stopped-at-first-failure','unknown historical oracle failure')
|
||||
else:
|
||||
need(outcome=='completed-successfully','oracle build not complete')
|
||||
hash_string(result['executable']['sha256'])
|
||||
need(type(result['executable']['bytes']) is int and result['executable']['bytes']>0,'oracle executable size')
|
||||
need(digest(get('navigation-oracle-qa-'+name+'/checks.jsonl'))==result['qa_checks_sha256'], 'oracle QA pin')
|
||||
matrix.append({'kind':'oracle-run','name':name,'source':result['head'],'closure':outcome,
|
||||
'receipt':prefix+'result.json','exits':[s['exit_code'] for s in result['steps']],
|
||||
'acceptance':'not inferred'})
|
||||
command_root = 'navigation-oracle-commands/'
|
||||
commands = sorted({p[len(command_root):].split('/')[0] for p in paths if p.startswith(command_root)})
|
||||
if not commands:
|
||||
return matrix
|
||||
build_path = 'navigation-oracle-runs/attempt02/result.json'
|
||||
build_raw = get(build_path)
|
||||
build = decode(build_raw)
|
||||
for name in commands:
|
||||
prefix = command_root+name+'/'
|
||||
safe(name)
|
||||
if name == 'draw-fixture-runtime':
|
||||
need({p for p in paths if p.startswith(prefix)} == {prefix+'failure.json'}, 'recorder failure files changed')
|
||||
failure_raw = get(prefix+'failure.json')
|
||||
failure = decode(failure_raw)
|
||||
need(digest(failure_raw)=='ecfa343366a1e156f9f20e9522cdc89f98352975b9100d3b7b2e6140fb4bafe3'
|
||||
and failure['oracle_started'] is False, 'unknown recorder failure')
|
||||
matrix.append({'kind':'oracle-command','name':name,'closure':'recorder-failed-before-launch',
|
||||
'receipt':prefix+'failure.json','acceptance':'none'})
|
||||
continue
|
||||
raw = get(prefix+'receipt.json')
|
||||
result = decode(raw)
|
||||
need(result['label']==name and type(result['exit_code']) is int and
|
||||
timestamp(result['finished'])>=timestamp(result['started']), 'open/misidentified oracle command')
|
||||
argv = result['argv']
|
||||
need(isinstance(argv,list) and all(isinstance(x,str) and x for x in argv) and
|
||||
len(argv)>=3 and argv[1] in ('fixture','snapshot','compare'), 'oracle argv')
|
||||
need(result['exe_unchanged'] is True and result['exe_sha256']==build['executable']['sha256']
|
||||
and result['build_receipt_sha256']==digest(build_raw), 'oracle build/executable binding')
|
||||
need(re.fullmatch('[0-9a-f]{40}',result['head']), 'oracle source identity')
|
||||
for stream in ('stdout','stderr'):
|
||||
size=result[stream+'_bytes']
|
||||
need(type(size) is int and size>=0 and len(get(prefix+stream))==size,'oracle stream size')
|
||||
if name == 'draw-fixture':
|
||||
need(digest(raw)=='89620b6726a510795a02bd173e0540c48610dbff0135915b0ce3ad81a6868818'
|
||||
and result['exit_code']==3221225781,'unknown runtime failure')
|
||||
outcome='runtime-load-failure-no-admission'
|
||||
else:
|
||||
need(result['exit_code']==0 and get(prefix+'stderr')==b'', 'oracle command failed: review/classify first')
|
||||
need(result['runtime_sha256']==config['oracle_runtime_sha256'] and
|
||||
result['path']=='C:\\Windows\\System32;C:\\Windows', 'oracle runtime environment')
|
||||
out=decode(get(prefix+'stdout'))
|
||||
if argv[1]=='snapshot':
|
||||
need(len(argv)==4 and out['status']=='captured', 'snapshot receipt status')
|
||||
dest=logical_argument(argv[3],config)
|
||||
snapshot=decode(get(dest+'/snapshot.json'))
|
||||
need(out['entry']==snapshot['entry'] and out['files']==len(snapshot['files']) and
|
||||
out['source_bytes']==sum(len(v) for v in snapshot['files'].values()) and
|
||||
out['engineering_diagnostics']==snapshot['diagnostics'], 'snapshot metadata binding')
|
||||
need(decode(get(dest+'/project.json'))==snapshot['project'], 'snapshot exported Project mismatch')
|
||||
elif argv[1]=='compare':
|
||||
need(len(argv) in (4,7) and out['pass'] is True and out['source_contract_pass'] is True
|
||||
and out['expected_entire_project_equal'] is True and
|
||||
out['raw_entry_and_path_set_equal'] is True,'compare status')
|
||||
for arg in argv[2:4]: get(logical_argument(arg,config)+'/snapshot.json')
|
||||
if len(argv)==4:
|
||||
need(out['permitted_projection'] is None and out['raw_source_bytes_equal'] is True,'exact compare scope')
|
||||
else:
|
||||
need(argv[4]=='--projection' and out['permitted_projection']=={'diagram':argv[5],'projection':argv[6]}
|
||||
and out['projection_declaration_diff']['outside_projection_value_exact'] is True,'projection compare scope')
|
||||
else:
|
||||
need(len(argv)==3 and out.get('status')=='fixture_written','fixture status')
|
||||
dest=logical_argument(argv[2],config)
|
||||
need(logical_argument(out['entry'],config)==dest+'/project.arcadia' and
|
||||
logical_argument(out['project_json'],config)==dest+'/project.json','fixture destination')
|
||||
get(dest+'/project.arcadia'); get(dest+'/project.json')
|
||||
outcome='recorded-success-metadata-checked-not-rerun'
|
||||
matrix.append({'kind':'oracle-command','name':name,'closure':outcome,'exit':result['exit_code'],
|
||||
'receipt':prefix+'receipt.json','acceptance':'not inferred'})
|
||||
return matrix
|
||||
|
||||
|
||||
def logical_argument(value, config):
|
||||
# Historical argv is data. This strips only a declared original root; never resolves/extracts it.
|
||||
value=value.replace('\\','/')
|
||||
for prefix in (config['original_source_local'].rstrip('/')+'/', '.local/'):
|
||||
if value.startswith(prefix):
|
||||
return safe(value[len(prefix):])
|
||||
raise ValueError('unknown historical source argument: '+value)
|
||||
|
||||
|
||||
def write_new(path, data):
|
||||
with path.open("xb") as file:
|
||||
file.write(data)
|
||||
|
||||
|
||||
def fresh_directory(path):
|
||||
for item in [path.parent, *path.parent.parents]:
|
||||
s = item.lstat()
|
||||
need(stat.S_ISDIR(s.st_mode) and not stat.S_ISLNK(s.st_mode) and
|
||||
not getattr(s, "st_file_attributes", 0) & 0x400, "output parent link/reparse")
|
||||
path.mkdir(parents=False, exist_ok=False)
|
||||
|
||||
|
||||
def collect(source, config):
|
||||
entries, blobs = [], {}
|
||||
selection = selected(source, config)
|
||||
total = 0
|
||||
for name, category, file in selection:
|
||||
permitted(name)
|
||||
data = read(file)
|
||||
total += len(data)
|
||||
need(total <= MAX_TOTAL, 'logical packet size cap')
|
||||
sha = digest(data)
|
||||
entries.append({"path": name, "category": category, "size": len(data), "sha256": sha})
|
||||
blobs[sha] = data
|
||||
coverage(config, entries)
|
||||
return selection, entries, blobs
|
||||
|
||||
|
||||
def stable(source, config, selection, entries):
|
||||
need([(a,b) for a,b,_ in selected(source,config)] == [(a,b) for a,b,_ in selection],
|
||||
"input file set changed during collection")
|
||||
for entry in entries:
|
||||
need(digest(read(source / entry["path"])) == entry["sha256"], "input changed during collection")
|
||||
|
||||
|
||||
def freeze(config_path, source, frozen_path):
|
||||
# Creates only a candidate inventory. Human review/pinning must precede build.
|
||||
source=Path(source).absolute()
|
||||
config_bytes=read(Path(config_path).absolute())
|
||||
config=decode(config_bytes)
|
||||
validate_config(config)
|
||||
need('frozen_entries' not in config, 'already frozen')
|
||||
selection,entries,blobs=collect(source,config)
|
||||
index = {x["path"]: blobs[x["sha256"]] for x in entries}
|
||||
receipts(config,index.__getitem__,set(index))
|
||||
stable(source,config,selection,entries)
|
||||
config['frozen_entries']=entries
|
||||
config['template_sha256']=digest(config_bytes)
|
||||
config['inventory_review']='REQUIRED: inspect logical file list and content before build/publication'
|
||||
path=Path(frozen_path).absolute()
|
||||
for item in [path.parent,*path.parent.parents]:
|
||||
s=item.lstat()
|
||||
need(stat.S_ISDIR(s.st_mode) and not stat.S_ISLNK(s.st_mode) and
|
||||
not getattr(s,'st_file_attributes',0)&0x400,'frozen output parent link/reparse')
|
||||
write_new(path,(json.dumps(config,indent=2)+'\n').encode())
|
||||
return {'frozen_config_sha256':digest(read(path)),'logical_files':len(entries),
|
||||
'review_required':True,'acceptance':'not inferred'}
|
||||
|
||||
|
||||
def build(config_path, source, output):
|
||||
source, output = Path(source).absolute(), Path(output).absolute()
|
||||
config_bytes = read(Path(config_path).absolute())
|
||||
config = decode(config_bytes)
|
||||
validate_config(config,frozen=True)
|
||||
selection,entries,blobs=collect(source,config)
|
||||
need(entries==config['frozen_entries'],'source differs from frozen inventory')
|
||||
index = {x["path"]: blobs[x["sha256"]] for x in entries}
|
||||
matrix = receipts(config, index.__getitem__,set(index))
|
||||
verifier = read(Path(__file__).absolute())
|
||||
need(index.get('navigation-evidence.py')==verifier,'running helper differs from frozen helper')
|
||||
fresh_directory(output)
|
||||
(output / "objects").mkdir()
|
||||
for sha, data in blobs.items():
|
||||
write_new(output / "objects" / sha, data)
|
||||
write_new(output / "navigation-evidence.py", verifier)
|
||||
write_new(output / "config.json", config_bytes)
|
||||
stable(source,config,selection,entries)
|
||||
manifest = {"schema": "navigation-evidence-v2", "entries": entries, "matrix": matrix,
|
||||
"config_sha256": digest(config_bytes), "verifier_sha256": digest(verifier),
|
||||
"documentation": config["documentation"], "acceptance": "not inferred"}
|
||||
write_new(output / "manifest.json", (json.dumps(manifest, indent=2) + "\n").encode())
|
||||
report = verify(output)
|
||||
# Read-only attribute is accidental-write protection, not tamper proofing.
|
||||
for file in walk_checked(output):
|
||||
if file.is_file():
|
||||
file.chmod(stat.S_IREAD)
|
||||
return report
|
||||
|
||||
|
||||
def verify(output):
|
||||
output = Path(output).absolute()
|
||||
manifest = decode(read(output / "manifest.json"))
|
||||
need(manifest["schema"] == "navigation-evidence-v2", "manifest schema")
|
||||
config_bytes = read(output / "config.json")
|
||||
need(digest(config_bytes) == manifest["config_sha256"], "config hash")
|
||||
need(digest(read(output / "navigation-evidence.py")) == manifest["verifier_sha256"], "verifier hash")
|
||||
config = decode(config_bytes)
|
||||
validate_config(config,frozen=True)
|
||||
need(manifest['entries']==config['frozen_entries'],'manifest differs from frozen whitelist')
|
||||
need(manifest['documentation']==config['documentation'] and manifest['acceptance']=='not inferred','manifest scope')
|
||||
coverage(config,manifest['entries'])
|
||||
names, objects, index = set(), set(), {}
|
||||
total=0
|
||||
for entry in manifest["entries"]:
|
||||
name = safe(entry["path"])
|
||||
need(name.casefold() not in names, "duplicate manifest path")
|
||||
need(not any(x.casefold() in EXCLUDED for x in PurePosixPath(name).parts), "excluded input")
|
||||
names.add(name.casefold())
|
||||
sha = entry["sha256"]
|
||||
need(isinstance(sha,str) and re.fullmatch("[0-9a-f]{64}", sha), "invalid digest")
|
||||
need(type(entry["size"]) is int and 0 <= entry["size"] <= MAX_FILE, "invalid size")
|
||||
total+=entry['size']
|
||||
need(total<=MAX_TOTAL,'logical size cap')
|
||||
data = read(output / "objects" / sha)
|
||||
need(len(data) == entry["size"] and digest(data) == sha, "object mismatch")
|
||||
index[name] = data
|
||||
objects.add("objects/" + sha)
|
||||
need(index.get('navigation-evidence.py')==read(output/'navigation-evidence.py'),'selfpin object mismatch')
|
||||
expected = objects | {"manifest.json", "config.json", "navigation-evidence.py"}
|
||||
actual = set()
|
||||
for file in walk_checked(output):
|
||||
s = file.lstat()
|
||||
need(not stat.S_ISLNK(s.st_mode) and not getattr(s,"st_file_attributes",0) & 0x400,
|
||||
"packet link/reparse")
|
||||
if file.is_file():
|
||||
actual.add(file.relative_to(output).as_posix())
|
||||
need(actual == expected, "extra or missing packet file")
|
||||
need(receipts(config, index.__getitem__,set(index)) == manifest["matrix"], "matrix mismatch")
|
||||
return {"integrity": "PASS", "logical_files": len(names), "objects": len(objects),
|
||||
"semantic_acceptance": "not evaluated", "manifest_sha256": digest(read(output / "manifest.json"))}
|
||||
|
||||
|
||||
def selftest(output):
|
||||
root = Path(output).absolute()
|
||||
fresh_directory(root)
|
||||
tests = []
|
||||
def reject(label, fn):
|
||||
try:
|
||||
fn()
|
||||
except (ValueError, FileNotFoundError, FileExistsError, KeyError, zipfile.BadZipFile):
|
||||
tests.append(label)
|
||||
return
|
||||
raise ValueError('falsifier unexpectedly accepted: '+label)
|
||||
source=root/'source'; source.mkdir()
|
||||
script=read(Path(__file__).absolute())
|
||||
for name,data in [('a.txt',b'evidence'),('b.txt',b'evidence'),('navigation-evidence.py',script)]:
|
||||
write_new(source/name,data)
|
||||
config={'schema':'navigation-evidence-config-v2','includes':[
|
||||
{'path':name,'category':'test','kind':'file'} for name in ('a.txt','b.txt','navigation-evidence.py')],
|
||||
'gates':[],'native_cases':[],'oracle_runs':[],'documentation':[],
|
||||
'exclusions':sorted(EXCLUDED),'original_source_local':'C:/example/.local',
|
||||
'oracle_runtime_sha256':'0'*64}
|
||||
template=root/'template.json'; write_new(template,json.dumps(config).encode())
|
||||
frozen=root/'frozen.json'; freeze(template,source,frozen)
|
||||
packet=root/'valid'; good=build(frozen,source,packet)
|
||||
need(good['logical_files']==3 and good['objects']==2,'dedup roundtrip')
|
||||
tests.append('valid-freeze-build-verify-dedup')
|
||||
reject('existing output refused',lambda:build(frozen,source,packet))
|
||||
reject('existing freeze refused',lambda:freeze(template,source,frozen))
|
||||
original=decode(read(packet/'manifest.json'))
|
||||
original_config=decode(read(packet/'config.json'))
|
||||
# Each falsifier gets its own create-new packet; valid packet stays read-only.
|
||||
for mutation in ('alter','delete','duplicate','traversal','extra','extra-listed','duplicate-json',
|
||||
'selfpin','omitted-required','unwhitelisted','category'):
|
||||
p=root/mutation; p.mkdir(); (p/'objects').mkdir()
|
||||
m=copy.deepcopy(original); cfg=copy.deepcopy(original_config)
|
||||
if mutation=='duplicate': m['entries'].append(dict(m['entries'][0]))
|
||||
if mutation=='traversal': m['entries'][0]['path']='../a.txt'
|
||||
if mutation=='extra-listed': m['entries'].append({'path':'absent.txt','category':'test','size':3,'sha256':'a'*64})
|
||||
if mutation=='omitted-required':
|
||||
m['entries']=[e for e in m['entries'] if e['path']!='a.txt']; cfg['frozen_entries']=copy.deepcopy(m['entries'])
|
||||
if mutation=='unwhitelisted':
|
||||
e=dict(m['entries'][0]); e['path']='unauthorized.txt'; m['entries'].append(e); cfg['frozen_entries']=copy.deepcopy(m['entries'])
|
||||
if mutation=='category':
|
||||
m['entries'][0]['category']='other'; cfg['frozen_entries']=copy.deepcopy(m['entries'])
|
||||
if mutation=='selfpin':
|
||||
m['verifier_sha256']=digest(b'changed')
|
||||
cb=json.dumps(cfg).encode(); m['config_sha256']=digest(cb)
|
||||
raw=json.dumps(m).encode()
|
||||
if mutation=='duplicate-json': raw=raw.replace(b'"schema":',b'"schema":"bad", "schema":',1)
|
||||
write_new(p/'manifest.json',raw); write_new(p/'config.json',cb)
|
||||
write_new(p/'navigation-evidence.py',b'changed' if mutation=='selfpin' else script)
|
||||
for obj in (packet/'objects').iterdir():
|
||||
if mutation=='delete' and obj.name==digest(b'evidence'): continue
|
||||
write_new(p/'objects'/obj.name,b'changed' if mutation=='alter' and obj.name==digest(b'evidence') else read(obj))
|
||||
if mutation=='extra': write_new(p/'extra.txt',b'extra')
|
||||
reject(mutation,lambda:verify(p))
|
||||
planned=[{'label':'one','command':'first'},{'label':'two','command':'second'}]
|
||||
closed={'started':'2026-01-01T00:00:00+00:00','finished':'2026-01-01T00:00:01+00:00',
|
||||
'exit_code':0,'stdout_bytes':0,'stderr_bytes':0}
|
||||
step=dict(closed,label='one',command='first')
|
||||
partial={'steps':[step]}
|
||||
reject('successful prefix still open',lambda:step_sequence(partial,planned,lambda _:b'','',True))
|
||||
complete={'steps':[step,dict(closed,label='two',command='second')]}
|
||||
reject('successful missing final timestamp',lambda:step_sequence(complete,planned,lambda _:b'','',True))
|
||||
complete['finished']=closed['finished']
|
||||
need(step_sequence(complete,planned,lambda _:b'','',True)=='completed-successfully','valid closure')
|
||||
failed={'steps':[dict(step,exit_code=101)]}
|
||||
need(step_sequence(failed,planned,lambda _:b'','',True)=='stopped-at-first-failure','failed prefix classification')
|
||||
tests.append('complete success and stopped failure distinguished')
|
||||
reject('stream size mismatch',lambda:step_sequence(complete,planned,lambda _:b'x','',True))
|
||||
reject('native/oracle identity mismatch',lambda:same_fields({'pid':1},{'pid':2},['pid']))
|
||||
reject('missing identity on both sides',lambda:same_fields({}, {}, ['binary_source']))
|
||||
reject('duplicate receipt/config JSON',lambda:decode(b'{"steps":[],"steps":[]}'))
|
||||
for constant in (b'NaN', b'Infinity', b'-Infinity'):
|
||||
reject('nonfinite JSON '+constant.decode(),lambda value=constant:decode(b'{"size":'+value+b'}'))
|
||||
reject('dot root path',lambda:safe('.'))
|
||||
native={'case':'baseline01','binary_source':'1'*40,'binary_pins':{'arcadia-studio.exe':'2'*64,'libunwind.dll':'3'*64},
|
||||
'pid':7,'start':closed['started'],'argv':['C:/private/bin/arcadia-studio.exe'],
|
||||
'cwd':'C:/private/working','path':'C:/Windows','local_app_data':'C:/private/profile',
|
||||
'fixture_sha256':'4'*64,'release_tag':'historical-test'}
|
||||
done=dict(native,finished=closed['finished'],exit_code=0,stdout_bytes=0,stderr_bytes=0,
|
||||
binaries_unchanged=True,fixture_unchanged=True)
|
||||
nc=dict(config,native_cases=['baseline01'])
|
||||
np='navigation-native/baseline01/'
|
||||
def native_records(result):
|
||||
data={np+'started.json':json.dumps(native).encode(),np+'finished.json':json.dumps(result).encode(),
|
||||
np+'stdout.log':b'',np+'stderr.log':b'',np+'observations.md':b'bounded selftest'}
|
||||
return receipts(nc,data.__getitem__,set(data))
|
||||
need(native_records(done)[0]['exit']==0,'legacy native fixture valid')
|
||||
for key,value in [('pid',8),('fixture_sha256','5'*64),('binaries_unchanged',False),('finished',None),('stdout_bytes',1)]:
|
||||
reject('native '+key,lambda k=key,v=value:native_records(dict(done,**{k:v})))
|
||||
# A command receipt must bind the included build object and its runtime/status.
|
||||
bp='navigation-oracle-runs/attempt02/result.json'; cp='navigation-oracle-commands/example/'
|
||||
bb=json.dumps({'executable':{'sha256':'6'*64}}).encode()
|
||||
oc=dict(config,oracle_runtime_sha256='7'*64)
|
||||
command={'label':'example','argv':['C:/oracle.exe','compare','.local/a','.local/b'],
|
||||
'started':closed['started'],'finished':closed['finished'],'exit_code':0,
|
||||
'exe_unchanged':True,'exe_sha256':'6'*64,'build_receipt_sha256':digest(bb),
|
||||
'head':'8'*40,'runtime_sha256':'7'*64,'path':'C:\\Windows\\System32;C:\\Windows',
|
||||
'stderr_bytes':0}
|
||||
command_output=json.dumps({'pass':True,'source_contract_pass':True,'expected_entire_project_equal':True,
|
||||
'raw_entry_and_path_set_equal':True,'permitted_projection':None,'raw_source_bytes_equal':True}).encode()
|
||||
command['stdout_bytes']=len(command_output)
|
||||
def command_records(result):
|
||||
data={bp:bb,cp+'receipt.json':json.dumps(result).encode(),cp+'stdout':command_output,cp+'stderr':b'',
|
||||
'a/snapshot.json':b'{}','b/snapshot.json':b'{}'}
|
||||
return receipts(oc,data.__getitem__,set(data))
|
||||
need(command_records(command)[0]['exit']==0,'command metadata valid')
|
||||
for key,value in [('build_receipt_sha256','9'*64),('runtime_sha256','9'*64),('exit_code',2),
|
||||
('finished',None),('stdout_bytes',0),('exe_unchanged',False)]:
|
||||
reject('oracle command '+key,lambda k=key,v=value:command_records(dict(command,**{k:v})))
|
||||
for name in ('../x','C:/x','a\\b','AUX.txt','a/../b','profile/x','auth/settings.json','x.exe'):
|
||||
reject('unsafe/private '+name,lambda n=name:permitted(n))
|
||||
for mutation in ('wrong-content','missing','extra','case-collision','traversal','symlink'):
|
||||
buf=io.BytesIO()
|
||||
with zipfile.ZipFile(buf,'w') as z:
|
||||
if mutation!='missing': z.writestr('a.txt',b'bad' if mutation=='wrong-content' else b'ok')
|
||||
if mutation=='extra': z.writestr('extra.txt',b'ok')
|
||||
if mutation=='case-collision': z.writestr('A.txt',b'ok')
|
||||
if mutation=='traversal': z.writestr('../x',b'ok')
|
||||
if mutation=='symlink':
|
||||
info=zipfile.ZipInfo('link'); info.create_system=3; info.external_attr=(stat.S_IFLNK|0o777)<<16
|
||||
z.writestr(info,b'a.txt')
|
||||
reject('ZIP '+mutation,lambda b=buf.getvalue():check_zip(b,{'a.txt':digest(b'ok')}))
|
||||
crc=io.BytesIO()
|
||||
with zipfile.ZipFile(crc,'w',compression=zipfile.ZIP_STORED) as z:
|
||||
z.writestr('a.txt',b'crc-evidence')
|
||||
damaged=bytearray(crc.getvalue()); at=damaged.index(b'crc-evidence'); damaged[at]^=1
|
||||
reject('ZIP CRC corruption',lambda:check_zip(bytes(damaged),{'a.txt':digest(b'crc-evidence')}))
|
||||
# Real filesystem link test when the platform permits unprivileged link creation.
|
||||
skipped=[]
|
||||
link=root/'linked-source'
|
||||
try:
|
||||
link.symlink_to(source,target_is_directory=True)
|
||||
except (OSError,NotImplementedError) as error:
|
||||
skipped.append('filesystem reparse/symlink creation unavailable: '+type(error).__name__)
|
||||
else:
|
||||
reject('filesystem link ancestor',lambda:read(link/'a.txt'))
|
||||
reject('output link ancestor',lambda:fresh_directory(link/'new'))
|
||||
# Changed source after freeze must fail before packet creation.
|
||||
(source/'a.txt').write_bytes(b'changed')
|
||||
reject('source changed since freeze',lambda:build(frozen,source,root/'changed-source'))
|
||||
report = {"selftest":"PASS", "cases":tests,'skipped':skipped,"scope":"transport integrity only; skipped controls are not passes"}
|
||||
write_new(root / "result.json", (json.dumps(report,indent=2)+"\n").encode())
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mode, *args = sys.argv[1:]
|
||||
need(mode in ("freeze", "build", "verify", "selftest"), "unknown mode")
|
||||
print(json.dumps({"freeze":freeze,"build":build,"verify":verify,"selftest":selftest}[mode](*args),indent=2))
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\peer01\\bin\\arcadia-studio.exe",
|
||||
"id": 25822524,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough — Arcadia Studio"
|
||||
},
|
||||
"accessibility": {
|
||||
"focused_element": "0 window Restore safe water pressure — release 1 walkthrough — Arcadia Studio Secondary Actions: Raise",
|
||||
"tree": "Window: \"Restore safe water pressure — release 1 walkthrough — Arcadia Studio\", App: arcadia-studio.exe.\n\t0 window Restore safe water pressure — release 1 walkthrough — Arcadia Studio Secondary Actions: Raise\n\t\t1 title bar (settable, string) Value: Restore safe water pressure — release 1 walkthrough — Arcadia Studio\n\t\t\t2 menu bar System ID: MenuBar\n\t\t\t\t3 menu item System\n\t\t\t4 button Minimize\n\t\t\t5 button Maximize\n\t\t\t6 button Close\n\nThe focused UI element is 0 window Restore safe water pressure — release 1 walkthrough — Arcadia Studio Secondary Actions: Raise."
|
||||
},
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "04-before-oa-edge-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "f267facc804d62ea065ae970b7315cc0a5e580dbf726a291626a354f923adf54"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 131 KiB |
@@ -1,12 +0,0 @@
|
||||
sa_function sa_target "SA execute repair" id "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3" {
|
||||
}
|
||||
|
||||
sa_port functional_exchange_sa_dispatch_repair_sa_execute_repair_input "Functional exchange: SA dispatch repair → SA execute repair input" id "0a6f4b4b-911c-531d-82d5-172bdc8b0564" {
|
||||
contract {
|
||||
FunctionInput {
|
||||
incoming_items [];
|
||||
};
|
||||
};
|
||||
parent @sa_target;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 131 KiB |
@@ -1,60 +0,0 @@
|
||||
sa_scenario sa_conversation "Repair request and measured reply" id "730b27ca-b039-5a3b-8dc6-03418f9061b3" {
|
||||
context @sa_restore;
|
||||
postcondition "The controller has received the measured pressure after repair";
|
||||
precondition "A pressure alarm has been assessed";
|
||||
steps [@sa_call, @sa_reply];
|
||||
viewpoint BoundaryInterface;
|
||||
}
|
||||
|
||||
sa_occurrence sa_call "Request valve repair" id "8bab4783-a098-5984-9e36-493957caf26c" {
|
||||
body {
|
||||
Exchange {
|
||||
exchange @sa_contact;
|
||||
message_kind Call;
|
||||
receiver @sa_responder;
|
||||
sender @sa_caller;
|
||||
values [{
|
||||
expression "420.0";
|
||||
field @sa_pressure_parameter;
|
||||
}];
|
||||
};
|
||||
};
|
||||
parent @sa_conversation;
|
||||
}
|
||||
|
||||
sa_lifeline sa_caller "Controller" id "98c61788-2fee-51f4-8cf9-de48e8c70e9e" {
|
||||
parent @sa_conversation;
|
||||
represented @sa_control;
|
||||
}
|
||||
|
||||
sa_occurrence sa_reply "Return pressure after repair" id "4e1c0a56-ce80-5f13-a93a-6956de54880c" {
|
||||
body {
|
||||
Exchange {
|
||||
exchange @sa_contact;
|
||||
message_kind Reply;
|
||||
receiver @sa_caller;
|
||||
reply_to @sa_call;
|
||||
sender @sa_responder;
|
||||
values [{
|
||||
expression "320.0";
|
||||
field @sa_pressure_parameter;
|
||||
}];
|
||||
};
|
||||
};
|
||||
parent @sa_conversation;
|
||||
}
|
||||
|
||||
sa_lifeline sa_responder "Technician" id "6249b54a-e396-5338-82b9-89266c55670d" {
|
||||
parent @sa_conversation;
|
||||
represented @sa_technician;
|
||||
}
|
||||
|
||||
artifact repair_test_evidence "Repair conversation acceptance notes" id "f5922bf7-05d8-590f-86e9-1d09f37f4fa6" {
|
||||
description "A local teaching artifact, not proof of a real valve's safety.";
|
||||
fragment "acceptance";
|
||||
owner @sa_conversation;
|
||||
relation Verifies;
|
||||
revision "lesson-1";
|
||||
uri "evidence/repair-check.md";
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\draw01\\bin\\arcadia-studio.exe",
|
||||
"id": 43582746,
|
||||
"title": "Cross-layer native Draw verification — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "04-sa-draw-armed-oa-retained-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "1aea20c087a78156ab476ce8269edd295a906a86cd45476147f8483363cab690"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 132 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\draw01\\bin\\arcadia-studio.exe",
|
||||
"id": 43582746,
|
||||
"title": "Cross-layer native Draw verification — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "05-wrong-layer-target-refused-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "a73be6efe78cc071da3c4ee352a11023689cbb8ddf0632121be40dd920d9e574"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
oa_activity oa_source "OA request repair" id "e4a68a7d-6949-5c17-9263-c03c03c21af2" {
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"case": "relocation01-reopen",
|
||||
"binary_source": "6018ae30b9296b9f6b37d7fc038175080d15fafb",
|
||||
"start": "2026-10-02T06:55:10.845877+00:00",
|
||||
"binary_pins": {
|
||||
"arcadia-studio.exe": "f86b29555d1e512538ff3f7ee75619b0286add5aa2d60cdc724e8c27cc931165",
|
||||
"libunwind.dll": "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4"
|
||||
},
|
||||
"original_fixtures": {
|
||||
"eight-card.json": "9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8",
|
||||
"draw/oracle.json": "b3eb669d353bf9f3490faf50cdf65b9f46d77719de993557c56ea159d4696ae0",
|
||||
"draw/project.arcadia": "2a657d69cf30487364672cd45d0a0fa01a78d64140cc58eeee47e61ea4a112d4",
|
||||
"draw/project.json": "520e5645bcf87380a50be08cb9ef758b85a19414e58ce3bd61926c09767a7963",
|
||||
"draw/views/diagram_mixed_oa.arcadia": "d481e137236c62b13afebae3b14d6eedb61c27b7f6566cb3f6dc325d4aaea927",
|
||||
"draw/views/diagram_mixed_sa.arcadia": "b5c326eafea071ed0f058b8a792271c2bb83c87eab6b7718a51a9cd3782d449d",
|
||||
"draw/oa/activities/activity_oa_source.arcadia": "4d8b0971cc64a3ae13f7bf4b0c79a5ee978facb8ee660c2690c473b0b935ba5f",
|
||||
"draw/oa/activities/activity_oa_target.arcadia": "afef0fbac3fb7fbe42936a0c213e72541b31a471af8257a9462bdafb1f39c8ab",
|
||||
"draw/sa/functions/function_sa_source.arcadia": "536de40f46f64c745af0a41a8dfa70309782e90617be69dd37610cb4e9730ed2",
|
||||
"draw/sa/functions/function_sa_target.arcadia": "cb6d97f9b3c112f9b50bb9bb018ce0e6e34d51c8ae53afd6d6493367f371f1fa",
|
||||
"relocation-fixture.json": "282a3ef1206000c1bd8d32c55bde950831806096fbfcec7c324e182a49d7df4c"
|
||||
},
|
||||
"argv": [
|
||||
"C:\\ar-nav-01a0d717\\.local\\navigation-native\\relocation01-reopen\\bin\\arcadia-studio.exe"
|
||||
],
|
||||
"cwd": "C:\\ar-nav-01a0d717\\.local\\navigation-native\\relocation01-reopen\\working",
|
||||
"path": "C:\\Windows\\System32;C:\\Windows",
|
||||
"local_app_data": "C:\\ar-nav-01a0d717\\.local\\navigation-native\\relocation01-reopen\\profile",
|
||||
"pid": 6776
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"label": "draw01-dataflow",
|
||||
"argv": [
|
||||
"C:\\ar-nav-01a0d717\\.local\\navigation-oracle-runtime\\navigation-oracle.exe",
|
||||
"snapshot",
|
||||
"C:/ar-nav-01a0d717/.local/navigation-native/draw01/working/draw-save/project.arcadia",
|
||||
"C:/ar-nav-01a0d717/.local/navigation-native/draw01/dataflow"
|
||||
],
|
||||
"started": "2026-10-02T06:31:09.966755+00:00",
|
||||
"exe_sha256": "bd2db2e177d9d21f44b45e5efa89071e00155d31509672bf5aea6a57915e09a6",
|
||||
"runtime_sha256": "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4",
|
||||
"path": "C:\\Windows\\System32;C:\\Windows",
|
||||
"head": "c7065db76ecca3faa733ccb5f09b87fd0a0958be",
|
||||
"build_receipt_sha256": "1ed50fe9c42ff4a67084ed70ce934404f5549ca68bc54e6d2f1436e66ea8d22f",
|
||||
"exit_code": 0,
|
||||
"finished": "2026-10-02T06:31:10.897201+00:00",
|
||||
"stdout_bytes": 7336,
|
||||
"stderr_bytes": 0,
|
||||
"exe_unchanged": true
|
||||
}
|
||||
|
Before Width: | Height: | Size: 117 KiB |
@@ -1,17 +0,0 @@
|
||||
oa_exchange_category oa_contact_category "Field contact" id "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771" {
|
||||
exchanges [@oa_contact];
|
||||
family "Participant";
|
||||
}
|
||||
|
||||
oa_event oa_high_pressure "Pressure alarm detected" id "65d6b8d6-271a-5c84-8b42-abbe310c955e" {
|
||||
body {
|
||||
Change {
|
||||
expression "measured_pressure > operating_limit";
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
oa_exchange_category oa_repair_traffic "Repair work exchanges" id "9349b353-53f2-56f8-b571-a05b09335cdf" {
|
||||
exchanges [@oa_repair_request, @oa_repair_report];
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"head": "6018ae30b9296b9f6b37d7fc038175080d15fafb",
|
||||
"steps": [
|
||||
{"label":"native-bins","command":"cargo build --workspace --bins --locked"}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 148 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\fixed01-reopen\\bin\\arcadia-studio.exe",
|
||||
"id": 17957882,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "05-reopen-saved-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "ab1fde5b53c66d227235610ab3aed1069ab86a9d70e4b10e18148d6275e5f925"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
{
|
||||
"schema_version": 7,
|
||||
"id": "01324fcc-9456-5341-8fd0-2c261bddf186",
|
||||
"alias": "navigation_draw",
|
||||
"name": "Cross-layer native Draw verification",
|
||||
"description": "Synthetic native acceptance fixture; four complete typed work elements, two saved mixed-layer views, initially no relations.",
|
||||
"elements": {
|
||||
"0a6f4b4b-911c-531d-82d5-172bdc8b0564": {
|
||||
"id": "0a6f4b4b-911c-531d-82d5-172bdc8b0564",
|
||||
"alias": "functional_exchange_sa_dispatch_repair_sa_execute_repair_input",
|
||||
"name": "Functional exchange: SA dispatch repair → SA execute repair input",
|
||||
"description": "",
|
||||
"parent": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Port": {
|
||||
"contract": {
|
||||
"FunctionInput": {
|
||||
"incoming_items": []
|
||||
}
|
||||
},
|
||||
"legacy": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"7957e536-d4b4-50c2-8271-ef829952353f": {
|
||||
"id": "7957e536-d4b4-50c2-8271-ef829952353f",
|
||||
"alias": "functional_exchange_sa_dispatch_repair_sa_execute_repair_output",
|
||||
"name": "Functional exchange: SA dispatch repair → SA execute repair output",
|
||||
"description": "",
|
||||
"parent": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"body": {
|
||||
"Sa": {
|
||||
"Shared": {
|
||||
"Port": {
|
||||
"contract": {
|
||||
"FunctionOutput": {
|
||||
"outgoing_items": []
|
||||
}
|
||||
},
|
||||
"legacy": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"7b9d5c34-1c45-5048-a3a0-77e5784a5a69": {
|
||||
"id": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"alias": "oa_target",
|
||||
"name": "OA perform repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Oa": "Activity"
|
||||
}
|
||||
},
|
||||
"c5e2f9d6-ba03-50b6-9860-b2dd717c02b3": {
|
||||
"id": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"alias": "sa_target",
|
||||
"name": "SA execute repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": "Function"
|
||||
}
|
||||
},
|
||||
"e4a68a7d-6949-5c17-9263-c03c03c21af2": {
|
||||
"id": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"alias": "oa_source",
|
||||
"name": "OA request repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Oa": "Activity"
|
||||
}
|
||||
},
|
||||
"fa0825f4-ae33-5eb3-9222-bf3a6465e72e": {
|
||||
"id": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"alias": "sa_source",
|
||||
"name": "SA dispatch repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": "Function"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"50e20de1-197f-4126-90da-81c6e4c93409": {
|
||||
"id": "50e20de1-197f-4126-90da-81c6e4c93409",
|
||||
"alias": "functional_exchange_sa_dispatch_repair_sa_execute_repair",
|
||||
"name": "Functional exchange: SA dispatch repair → SA execute repair",
|
||||
"description": "",
|
||||
"body": {
|
||||
"FunctionalExchange": {
|
||||
"source": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"target": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"source_port": "7957e536-d4b4-50c2-8271-ef829952353f",
|
||||
"target_port": "0a6f4b4b-911c-531d-82d5-172bdc8b0564",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"a425a1af-e16f-4e25-9fb7-9f063dbe2fda": {
|
||||
"id": "a425a1af-e16f-4e25-9fb7-9f063dbe2fda",
|
||||
"alias": "interaction_oa_request_repair_oa_perform_repair",
|
||||
"name": "Interaction: OA request repair → OA perform repair",
|
||||
"description": "",
|
||||
"body": {
|
||||
"Interaction": {
|
||||
"source": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"target": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"diagrams": {
|
||||
"a60ffb09-36c2-5abc-bec5-6eab563c8cb8": {
|
||||
"id": "a60ffb09-36c2-5abc-bec5-6eab563c8cb8",
|
||||
"alias": "mixed_oa",
|
||||
"name": "OA mixed Draw canvas",
|
||||
"description": "",
|
||||
"layer": "Oa",
|
||||
"projection": "Traceability",
|
||||
"view_options": {
|
||||
"hidden_edges": [],
|
||||
"relation_kinds": "All",
|
||||
"work_categories": "All",
|
||||
"participant_categories": "All",
|
||||
"coverage": "All",
|
||||
"work_contexts": "All",
|
||||
"exchange_labels": "Name"
|
||||
},
|
||||
"nodes": {
|
||||
"1f2bb997-b4af-5910-b91b-1948764cff4f": {
|
||||
"id": "1f2bb997-b4af-5910-b91b-1948764cff4f",
|
||||
"alias": "mixed_oa_oa_target",
|
||||
"element": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"371bfc51-edcc-5770-aad1-11b84772d2c8": {
|
||||
"id": "371bfc51-edcc-5770-aad1-11b84772d2c8",
|
||||
"alias": "mixed_oa_oa_source",
|
||||
"element": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"752822e9-1f95-5e9c-b1d3-58c325d875fa": {
|
||||
"id": "752822e9-1f95-5e9c-b1d3-58c325d875fa",
|
||||
"alias": "mixed_oa_sa_source",
|
||||
"element": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"eb2ce67e-c283-5689-8780-b54e1b06d7cc": {
|
||||
"id": "eb2ce67e-c283-5689-8780-b54e1b06d7cc",
|
||||
"alias": "mixed_oa_sa_target",
|
||||
"element": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
"routes": []
|
||||
},
|
||||
"aa64b55c-9bbb-5444-bda4-c9be0c66242a": {
|
||||
"id": "aa64b55c-9bbb-5444-bda4-c9be0c66242a",
|
||||
"alias": "mixed_sa",
|
||||
"name": "SA mixed Draw canvas",
|
||||
"description": "",
|
||||
"layer": "Sa",
|
||||
"projection": "Traceability",
|
||||
"view_options": {
|
||||
"hidden_edges": [],
|
||||
"relation_kinds": "All",
|
||||
"work_categories": "All",
|
||||
"participant_categories": "All",
|
||||
"coverage": "All",
|
||||
"work_contexts": "All",
|
||||
"exchange_labels": "Name"
|
||||
},
|
||||
"nodes": {
|
||||
"44169450-37f5-59fd-b9ab-32482c943f99": {
|
||||
"id": "44169450-37f5-59fd-b9ab-32482c943f99",
|
||||
"alias": "mixed_sa_sa_target",
|
||||
"element": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"a0a18bd6-891a-56a4-a46d-d7b7d9911fc9": {
|
||||
"id": "a0a18bd6-891a-56a4-a46d-d7b7d9911fc9",
|
||||
"alias": "mixed_sa_oa_target",
|
||||
"element": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"aca2f538-69bf-561f-9eff-433febee5234": {
|
||||
"id": "aca2f538-69bf-561f-9eff-433febee5234",
|
||||
"alias": "mixed_sa_sa_source",
|
||||
"element": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"f5e4244f-e1c4-59ec-9109-be1e851386c1": {
|
||||
"id": "f5e4244f-e1c4-59ec-9109-be1e851386c1",
|
||||
"alias": "mixed_sa_oa_source",
|
||||
"element": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
"routes": []
|
||||
}
|
||||
},
|
||||
"artifact_links": {},
|
||||
"traces": {}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"recorded_at": "2026-10-02T07:10:23.939491+00:00",
|
||||
"command": [
|
||||
"C:/Users/User/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/python.exe",
|
||||
".local/navigation-evidence.py",
|
||||
"selftest",
|
||||
".local/navigation-evidence-selftest-attempt01"
|
||||
],
|
||||
"working_directory": "C:/ar-nav-01a0d717",
|
||||
"tool_exit_code": 0,
|
||||
"helper_sha256": "3b2c6c59c895dd2e40b5dc408e68fe7c3760447c62a7f98413db60ea18ec7825",
|
||||
"result_sha256": "4ef9a80a3de9bc60e21804f2d2ed51d77a78474c5e23f855b06ea5ce25e7cebb",
|
||||
"result_reported_cases": 52,
|
||||
"skipped": [
|
||||
"filesystem reparse/symlink creation unavailable: OSError"
|
||||
],
|
||||
"scope": "Parent executed after independent candidate review. Recorded now from returned tool exit and retained actual result; no fabricated command start/end timestamps. Transport only."
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
# Independent native subset evidence review
|
||||
|
||||
2026-10-02 UTC; reviewer diagram_reading (Astra). Root C:/ar-nav-01a0d717. Read-only review of fixed01, fixed01-reopen and draw01 notes, process receipts, original JPEGs/state manifests, and selected saved-data oracle receipts. No GUI control, Cargo, source/model or Gitea changes; only this note was written. This is evidence review of the existing private c706 run, not a new native run or release acceptance.
|
||||
|
||||
## Finding requiring a wording correction
|
||||
|
||||
The draw01 observation that the OA Interaction label became fully visible at 96% overstates the display. Capture16 shows its label clipped at the top canvas boundary at 113%. Captures28,31,33 show its label box back inside the viewport at 96%, but the text is still ellipsized as Interaction: OA request repair .... The functional-exchange label is also ellipsized in 09 and 33. Say that refitting resolved viewport clipping while full label text remained truncated. This matters to the still-open small-diagram readability requirement; successful creation does not establish readable labels.
|
||||
|
||||
The expanded palette also visibly clips both right-side card contents at 113% in captures25–27. At 96% after perspective navigation, capture28 contains all four cards within the narrower canvas. The screenshots support this width/camera limitation; they cannot prove that authored placements were unchanged. The final saved-data comparator supplies the separate conservation evidence.
|
||||
|
||||
No other material contradiction found in the requested bounded subset. Do not broaden these results to source acceptance: the notes explicitly identify c706's failed workspace placement-relocation regression. This review does not rerun or overturn that failure or accept a later repair.
|
||||
|
||||
## Directly inspected original captures
|
||||
|
||||
All images below were opened individually through view_image at their original JPEG paths; no regenerated diagrams or crops were used.
|
||||
|
||||
- draw01: 04,05,06,07,08,09,16,21,22,23,25,26,27,28,31,33,34,35 (all numbered -0.jpg).
|
||||
- fixed01: 06-oa-edge-inspected, 12-valid-apply-retains-mixed, 17-valid-draft-click-oa-process, 22-allocation-switch-succeeds.
|
||||
- fixed01-reopen: 01-reopened-source-1 (owned warning), 04-reopened-allocation-0, 06-reverse-traceability-0, 09-explicit-open-endpoint-0.
|
||||
|
||||
Draw04 visibly retains Operational and OA mixed Draw canvas while the Inspector identifies SA dispatch repair / sa_source. The source is green and the compatible SA execute card is outlined blue; OA cards are not candidate-highlighted. Draw05 retains that view and shows Choose a highlighted compatible endpoint after the pointer is on the OA card, with a temporary dashed preview. It supports refusal feedback, not a semantic mutation claim. Draw06 shows both SA functions as possible sources and the source-selection hint, consistent with a blank click resetting source choice while keeping the tool. Draw07 shows Select/move and Drawing cancelled; 08 shows Nothing to undo. A screenshot of that status cannot independently count history entries.
|
||||
|
||||
Draw09 shows the Functional exchange between the two SA cards, triangle port glyphs, matching source/target names in Properties, created feedback, and the same OA canvas. Draw16 shows System and SA mixed Draw canvas while Properties identifies an Interaction from OA request repair to OA perform repair (UUID a425a1af-e16f-4e25-9fb7-9f063dbe2fda). The green OA line and existing lower SA line are visible. Exact automatic port identities, contracts, record counts and unchanged records belong to the separately reviewed saved-Project evaluator, not visual inference.
|
||||
|
||||
Draw21 has the Model explorer tab active and its source context menu has no Draw entry. Draw22 has Architecture active with the same sidebar object menu and includes Draw functional exchange. Draw23 confirms the latter starts from sa_source on the retained SA canvas, with the target/port highlighted. Documentation should say Architecture sidebar, not generically Model explorer. This review confirms the visible distinction, not that a later documentation change is already shipped.
|
||||
|
||||
Draw25 shows expanded SA Functional exchange enabled, Boundary exchange and Allocation greyed. Draw26 leaves Select/move active and displays the Boundary exchange compatible-source tooltip. Draw27 activates Functional exchange, adds its source-selection hint and blue compatible SA outlines. Draw28 shows the OA labels/glyphs; Interaction, Communication means and Allocation are visually distinct at this scale, but this is not evidence that an unaided person understands each icon. Draw33/34 show compact OA controls and the disabled Communication means guidance. Draw35 shows the compact Interaction selected and only OA cards highlighted as sources. SA compact selected glyph state is visible in contextual/sidebar capture23; that is not an independent direct click test of the compact SA glyph. The notes distinguish that from the separately clicked expanded control.
|
||||
|
||||
Draw31 shows Functional dataflow, just the two OA cards and their line, and the reduced palette (Activity, Interaction, Add existing; no participant/Communication/Allocation controls). Hidden SA records cannot be inferred as conserved from this image. The recorded projection comparator is the relevant saved-data evidence.
|
||||
|
||||
Fixed01 captures06/12 keep System and the named mixed Traceability view while the OA Chain involvement alias oa_assessment_delivery is inspected/renamed, with UUID 628cb4fc-fe63-5f6b-b364-6268f26b5740. Capture17 shows the OA process selected, Properties applied feedback, and the same canvas. Capture22 shows six SA cards in allocated nesting and Inspector projection Allocation; OA process/capability are absent from the canvas. Fixed01-reopen04 repeats that allocation rendering; 06 restores eight-card traceability. Reopen09 intentionally navigates to Operational and selects oa_assessment_procedure after the explicit endpoint action. This complements inspection retention rather than contradicting it.
|
||||
|
||||
The owned warning JPEG confirms Windows rejected the forward-slash filename before model admission. The detailed Return/UIA recovery sequence is recorded in the observer's note; a still JPEG alone does not independently prove each failed input call. The reviewed draw04 state JSON has accessibility:null, consistent with the tool's limited semantic exposure. Neither that fact nor pointer success establishes screen-reader support, complete Tab navigation, focus semantics, or keyboard-only authoring. Escape/Undo/Redo are bounded exercised keyboard paths; pan hints visible in the UI do not establish that keyboard pan was tested.
|
||||
|
||||
## Process, logs, saved-state evidence and limits
|
||||
|
||||
All three started/finished pairs identify c7065db76ecca3faa733ccb5f09b87fd0a0958be and GUI a9d8b2fe2eecb66e17cd16ab2efe6c270e70588d0282fe2ac5d30e50f827b8d8 with DLL f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4, system-only PATH and separate profile paths. Finished receipts record exit0, unchanged copied binaries/original fixtures; actual stdout.log and stderr.log files are zero bytes for each case. PIDs are fixed01 9668, reopen 9384, draw01 8736. Fresh-process attribution comes from these process records and action notes, not similar screenshots. There is no fresh-process Draw reopen claim.
|
||||
|
||||
Read the fixed01 preparation/inspection/name-undo/implicit-undo/projection-undo/projection-redo exact compare receipts, fixed01-reopen/reverse conservation, draw01 final and dataflow comparison receipts. All inspected command receipts have exit0 and empty actual stderr; comparison outputs pass. Exact comparisons report complete expected Project and raw source-byte equality. Projection comparisons report only the nominated view change and compiler-indexed token replacement, with exact outside-token bytes. The name/implicit saved-delta reports identify the one relation and expected name; their differing evaluator attribution is correctly disclosed in observations.md. The draw delta reports pin helper 6ac2c230... and expressly limit source coverage to inventories. The earlier independent helper review remains .local/native-draw-evaluator-review.md.
|
||||
|
||||
These are existing receipts, not executions performed by this reviewer. Screenshots alone do not establish complete Project conservation, invisible source bytes, one-step transaction history, no unsaved RAM changes, or fresh-process persistence. Counts and warning counts likewise do not establish those facts. Do not infer performance from this run: notes disclose concurrent compiler activity. Synthetic fixtures are useful bounded cases, not recovered user models or comprehensive engineering projects.
|
||||
|
||||
## Evidence pins
|
||||
|
||||
SHA-256 of the reviewed observation/process files:
|
||||
|
||||
| Case | observations.md | started.json | finished.json |
|
||||
| --- | --- | --- | --- |
|
||||
| fixed01 | 7c75d9e04772c35d5084243216caee2781fcebdb24d569c5652de70d554f2403 | f202e806fc935cc43bbf5f53bd64b8cae2627f84785e679c39d4b8b1de63114a | 45c59211f3ce4c78b2dabd93f34b31dec8abbfee0c49a6e553c4a1d3610cf23c |
|
||||
| fixed01-reopen | d6d5724bdbdb02b917b435b7668888886420b31dd8af5ef2d6746c37ca5f9596 | b386c60c93e02883e7b32ec679596f454d1a4f4e23fd799200928b694f4c7bb6 | fdd86e93bd8740245fd0d9e7d0bbe6981b1e7b98b82cdbf8ea2923a13c22bae7 |
|
||||
| draw01 | 52b21b0f80a39808949c7afa531b934528a4069521d04b2e24303fa00ca6a57e | f17a7aa2cc3bc1719e718a40099f38b0b3a8c5c5529e95a8e0c78a71f102d019 | f75847d4d1ad96ee489256e0549ea4569537d4fae53f7659cd5479dab05e017f |
|
||||
|
||||
Read-only hash calculations verified every JPEG against its adjacent original capture manifest: fixed01 28/28, fixed01-reopen 11/11 (including warning image), draw01 38/38. No mismatches. For example draw04 SHA is 1aea20c087a78156ab476ce8269edd295a906a86cd45476147f8483363cab690; its manifest reports 1442 x 932 and the correct private draw01 executable path. The individual manifests remain the authoritative full capture-pin inventory.
|
||||
@@ -1,158 +0,0 @@
|
||||
# Isolated navigation oracle
|
||||
|
||||
Prepared against core commit `6520a89dc042dd433454f6cc63cb23b0e6df7911`. This local crate is not a production CLI feature or workspace member. It has not been compiled or executed by its author. The initial preparation made no generated fixture/admission claim; parent execution evidence and the tested Windows runtime recipe are appended below. The prior `.local/populated-projection-regression.patch` is unchanged.
|
||||
|
||||
## Parent build and first admission
|
||||
|
||||
From `C:/ar-nav-01a0d717`, after the exclusive frozen compiler gate permits it:
|
||||
|
||||
```powershell
|
||||
cargo build --offline --manifest-path .local/navigation-oracle/Cargo.toml --target-dir .local/navigation-oracle/target
|
||||
```
|
||||
|
||||
The utility's Cargo.lock is initially an exact copy of the repository lock, used as a dependency seed. The first isolated build may update that local lock for this new package. Preserve its final bytes and the binary hash with the evidence. Subsequent reproducible builds should add `--locked --offline`. No Cargo command has been run in this task. Use Rust 1.90 or later, matching the repository's declared minimum. Dependencies are the repository path `oa-core` plus the same serde/serde_json major requirements already in the repository; no Python, shell helper or external server is required by the utility. The tested Windows gnullvm executable does require its matching libunwind.dll beside the executable; see the standalone runtime recipe below.
|
||||
|
||||
The `reviewed_core` field is a source-review pin, not an automatic runtime fingerprint. Record actual repository HEAD, tracked diff, utility hashes, final lock and executable hash when building. A later parent core change must not be attributed to this pin.
|
||||
|
||||
## Commands
|
||||
|
||||
```powershell
|
||||
$oracle = 'C:/ar-nav-01a0d717/.local/navigation-oracle-runtime/navigation-oracle.exe'
|
||||
& $oracle fixture C:/ar-nav-01a0d717/.local/native-draw-fixture
|
||||
& $oracle snapshot C:/path/to/saved/project.arcadia C:/path/to/new-baseline-snapshot
|
||||
& $oracle snapshot C:/path/to/saved/project.arcadia C:/path/to/new-after-snapshot
|
||||
& $oracle compare C:/path/to/new-baseline-snapshot C:/path/to/new-after-snapshot
|
||||
& $oracle compare C:/path/to/new-baseline-snapshot C:/path/to/new-after-snapshot --projection 00000000-0038-4000-8000-000000000001 Allocation
|
||||
```
|
||||
|
||||
Every output directory must be new, with an existing parent. Existing evidence is never overwritten. Fixture/snapshot writes that fail may leave partial output; preserve the failure, then choose another new directory. `compare` writes a JSON report to stdout and does not modify either snapshot. Exit 0 means that command completed successfully or comparison passed; 1 means a completed comparison failed; 2 means usage, loading, admission, IO or snapshot consistency failed. Capture stderr and the exit code as well as stdout.
|
||||
|
||||
For the projection trial: exact `compare` for selection/Properties; nominated-diagram `Allocation` comparison for switched state; exact baseline-versus-Undo, switched-versus-Redo and switched-versus-fresh-reopen comparisons. The utility does not infer which diagram changed. It never excuses unrelated fields, other diagrams, ID changes or reference changes.
|
||||
|
||||
## Snapshot contract
|
||||
|
||||
`snapshot` uses core `source_files::load_project` with the CLI's explicit policy: 64 MiB aggregate source, 256 files, include depth 32, 1024 include edges, 100000 graph objects, 250000 records and 500000 ordered references. It rejects only discovery/admission diagnostics whose actual enum is `validation::Severity::Error`, and returns full located compiler diagnostics on admission failure. Engineering warnings from successful compilation are retained in the report/snapshot. It compiles the reachable bundle with `source_project::compile_bundle` and exports the complete accepted Project to `project.json`.
|
||||
|
||||
`snapshot.json` contains the accepted Project, logical entry name and every reachable source path mapped to its exact raw captured disk bytes as an integer array. It preserves CR/LF, whitespace, comments and UTF-8 bytes; source is not regenerated or normalized. Disk expectations are checked against compiler input. Two complete source captures must match before evidence is written. This detects changes across the two observations but is not filesystem locking or an atomic multi-file snapshot; keep the saved project quiet.
|
||||
|
||||
The core loader rejects unsafe include paths, symlinks/junction escapes and invalid UTF-8. No snapshot logical path is used to write a file. Comparison revalidates logical paths, aggregate limits and UTF-8, recompiles all raw source, and requires that embedded Project and stored diagnostic strings exactly equal fresh compilation results. Compare reports diagnostics from that fresh compilation. Duplicate JSON source-path keys are rejected by a small serde visitor matching the core SourceBundle boundary pattern. Snapshot input itself is capped at 512 MiB. Source requiring UUID stabilization or migration is rejected so compiler-added text cannot be mistaken for original disk bytes: explicitly save the accepted current-version source first. Detached/unreachable editor buffers and unrelated disk files are outside this saved-project oracle.
|
||||
|
||||
## Comparison contract
|
||||
|
||||
Without an allowance, comparison requires complete Project equality, identical logical entry/path set and byte equality for every captured source file.
|
||||
|
||||
With `--projection DIAGRAM_UUID ProjectionKind`, clone the complete baseline Project and replace exactly the nominated diagram's projection. The after Project must equal that entire expected value. Accepted names are Overview, Decomposition, Allocation, FunctionalDataflow, Capabilities and Traceability; both the baseline and target must be architecture/string projections, so typed StateMachine/Scenario changes have no allowance.
|
||||
|
||||
Source still requires either exact bytes or exactly one changed source file whose complete prefix and suffix outside the compiler-indexed projection value match byte-for-byte. Both versions must have an explicit projection value in the same file and the same reachable path set. The JSON report includes both byte spans and original/replacement field text, plus whether outside-field bytes were exact. Omitted/defaulted projection declarations fail this narrow source allowance rather than broadening it silently. Project mismatches report up to 200 unexpected JSON paths; full snapshots remain available to inspect all differences. Matching counts alone never establishes conservation.
|
||||
|
||||
This is a saved-source oracle. It cannot inspect unsaved desktop state, prove GUI click ordering, prove exact in-memory source history, or establish native selection usability. Save/capture each relevant checkpoint and associate screenshots/action logs with it. Model-changing Draw actions deliberately fail this unchanged/projection-only comparator; inspect their precise creation delta separately instead of adding a broad allowance.
|
||||
|
||||
## Native Draw fixture contract
|
||||
|
||||
`fixture NEW_DIR` constructs typed records through `Project::add_element`, `ensure_diagram`, `place` and `reidentify`. It creates OA Activity source/target and SA Function source/target, with distinct labels and two Traceability diagrams declared OA and SA. Both diagrams contain all four work cards, placed in two uncluttered rows: OA at (60,60)/(520,60), SA at (60,300)/(520,300). Initially there are no relations or ports, allowing native Draw to create the Interaction or FunctionalExchange and its ports using the application's actual workflow.
|
||||
|
||||
UUIDv5 identities and aliases are deterministic; no random identity remains from the construction helpers. The generated `oracle.json` records exact element/view IDs and the source entry. The generator validates the complete Project, compiles the core `format_bundle` output, checks four projected cards per view, saves the bundle using `source_files::new_project`/`expect_new_file`/`save_project`, recompiles the saved source and requires full Project equality. It then writes Project JSON. Generated output is synthetic and must never be described as recovered user source or an already-run native acceptance result.
|
||||
|
||||
Use the SA mixed view to inspect/select OA work before invoking contextual Draw; use the OA mixed view for the SA work counterpart. Semantic source and target must follow the selected work layer even when the canvas perspective differs. Preserve the documented scope distinction: opening the contextual form retains the canvas; successful Apply may perform the existing explicit post-review navigation. The separate architecture palette limitation is not claimed repaired.
|
||||
|
||||
## Reviewed source identities and remaining checks
|
||||
|
||||
SHA-256 at preparation:
|
||||
|
||||
- `src/main.rs`: `69c2c90f5cfebf86f755ea47861e4ab059e7359e442993a996fb9e91e2caeb7c`
|
||||
- `Cargo.toml`: `ba95c25a04dbfd0d6d19bd59f466e4082f840723ebE8180e5061a4a157e139c8`
|
||||
- Repository/seed Cargo.lock: `2aa80d113c5112b31c0767b874845d60f555e4e8ae7eb3ecd0b4b91954b55378`
|
||||
- Core `source_files.rs`: `bb903f8c53456f69c5ac213a192a1eb0d19abf8344bd82a03be8e659ba7bc94f`
|
||||
- Core `source_project.rs`: `d872f2a8f7c4add53f2708bf3fdaea1b4be715d4240f2d9b93baf4624d3f72a7`
|
||||
- CLI limits source `crates/oa-cli/src/lib.rs`: `790f3bdef78025813262c9677d4db5813f32978a398f3b7fb4900427dc0c314e`
|
||||
|
||||
Parent review risks: uncompiled Rust/API compatibility; the isolated lock's initial adjustment; no atomic filesystem snapshot; snapshot memory grows with lossless byte arrays; strict stable-source/explicit-value requirements can reject otherwise valid but insufficiently prepared input. Useful oracle checks before trusting native results: identical snapshots pass, one allowed projection change passes only with the matching nominated view/value, an unrelated comment change fails exact bytes despite equal Project, and unrelated semantic/placement edits fail even under the projection allowance. Those checks are proposed, not executed.
|
||||
|
||||
## Review correction before first execution
|
||||
|
||||
The initial source and README are preserved verbatim under `revisions/01-pre-warning-repair/`, with their hashes. The initial capture check incorrectly rejected all discovery diagnostics, including legitimate `unallocated_leaf` warnings; no successful run is attributed to that version. The correction filters the actual Error severity, keeps compiler warnings, binds stored diagnostic strings to fresh compilation, guards typed baseline projections as well as targets, and rejects duplicate source-path JSON keys. `project.json` is a convenience export; the comparator admits the embedded model and raw sources from `snapshot.json` only.
|
||||
|
||||
Two Rust tests now cover nonfatal engineering warnings/error rejection, deleted/invented warning rejection, and duplicate raw-file-key rejection. Parent command after the frozen gate: `cargo test --offline --manifest-path .local/navigation-oracle/Cargo.toml --target-dir .local/navigation-oracle/target`. These tests and the utility still have not been compiled or run by this author. Runtime fixture generation and the actual warning-bearing source-load path remain required parent checks.
|
||||
|
||||
## Retained filesystem verification module (not executed)
|
||||
|
||||
`src/verification.rs` adds one test, `verification::filesystem_evaluator_contract`. Before any full utility test run, the parent must set `NAVIGATION_ORACLE_QA_DIR` to a fresh absolute path whose parent exists. The test intentionally fails without that explicit destination and refuses an existing destination. It never deletes or replaces evidence.
|
||||
|
||||
```powershell
|
||||
$env:NAVIGATION_ORACLE_QA_DIR = 'C:/ar-nav-01a0d717/.local/navigation-oracle-qa-run-01'
|
||||
cargo test --offline --manifest-path .local/navigation-oracle/Cargo.toml --target-dir .local/navigation-oracle/target verification::filesystem_evaluator_contract -- --nocapture
|
||||
```
|
||||
|
||||
Use a different fresh path for every rerun, including after failure. Capture Cargo stdout/stderr and exit status. Under the supplied path, `checks.jsonl` records each completed control with its result and relevant errors; every source variant, snapshot, corrupt input and sentinel is retained. Comparison JSON reports also go to test stdout; `--nocapture` makes them available to the parent log. An interrupted or failed run retains partial evidence and has no final completion marker.
|
||||
|
||||
The test invokes actual `fixture`, `capture`, `snapshot`, `read_snapshot` and `compare` functions. Positive variants use `prepare_command` and `update_bundle_with_identity_map`, then the core new-project/save APIs; the core crate has no desktop Document type. The projection edit is a real EditDiagram command and the unrelated model edit a real Rename command. Corrupt snapshots are deliberately based on valid captures. Duplicate-key input is syntactically valid JSON with a duplicated existing path, and missing-include input omits an actual discovered dependency from a previously valid complete bundle.
|
||||
|
||||
Controls cover warning-bearing fixture generation/capture; identical snapshots; accepted projection-only change; wrong diagram/value; comment and CRLF changes with equal accepted Project; unrelated coherent semantic change; embedded-model and diagnostic tampering; duplicate/unsafe snapshot paths; invalid UTF-8 and missing include on disk; and preservation of an existing output sentinel for both snapshot and fixture commands. The comment/CRLF cases are checked both against the switched model in exact mode and against the original in projection-allowance mode. Both existing-output controls require a direct boxed std::io::Error with ErrorKind::AlreadyExists and record the actual kind/message; unrelated earlier errors cannot satisfy the refusal check. The previous module and README bytes are retained with hashes under revisions/04-before-output-refusal-check/.
|
||||
|
||||
This module adds no dependency or production behavior. Original reviewed main.rs `2cf2d1ced864ed339eb31470d42b370ba7d6de559c4679965fec4729984b08d6` and README are preserved under `revisions/03-before-filesystem-tests/`; the pre-attempt01 main.rs retained that entire source as an exact prefix and added only the two-line cfg(test) module declaration. The subsequent compiler repair is recorded below. Module SHA-256: `c7d6b184b7bfa579ec6dd2a777a0a417a65418f768d4b8cbf6afce7589ab9a25`. No Cargo, filesystem exercise or GUI run has occurred in this preparation task.
|
||||
|
||||
Bounded omissions: this exercise does not build a typed StateMachine/Scenario fixture to retest the architecture guard; it does not simulate concurrent filesystem races, interrupted multi-file writes, symlink/junction attacks or out-of-memory limits. It also cannot replace native Draw, selection or unsaved desktop Document-history checks.
|
||||
|
||||
## Attempt01 compiler repair (not rerun by this author)
|
||||
|
||||
Parent attempt01 exited 101 before any test executed: Rust E0382 at src/main.rs:261 identified the placement alias being moved by the assignment right-hand side before the left-hand side borrowed it to compute its ID. Evidence is ../navigation-oracle-runs/attempt01/tests.stderr. The isolated fixture generator now computes placement_id once before moving placement_alias and reuses that ID for reidentification and lookup. No alias clone, verification assertion, production repository file or dependency changed.
|
||||
|
||||
The exact pre-repair main.rs and README are preserved with SHA-256 records under revisions/05-before-placement-id-repair/. Main was da515eff05bd34eb0cb354e412db74dbdede72518c99839fc5d75d56cdd7c892; README was 82355e35120e8f0508d146306d37efa8c22eac8d3f0c59029d5cdaff28835257. The parent-adjusted isolated Cargo.lock is e97d03c226c2c3f2328b68bcb6d823df00c0569918fa9891d50504cd12e0af1d and was left untouched. This repair has not been compiled or run by its author; parent owns a rerun with fresh logs and QA root. Earlier no-execution statements describe preparation and remain no test-pass claims.
|
||||
## Tested standalone Windows runtime and parent execution evidence
|
||||
|
||||
This is a receipt review, not a new execution. Parent attempt02 at repository HEAD `c7065db76ecca3faa733ccb5f09b87fd0a0958be` built with Rust 1.98.1 gnullvm / LLVM-MinGW 20260826 UCRT and passed all three tests. Its authoritative input manifest, commands and result are `../navigation-oracle-runs/attempt02/result.json` (SHA-256 `1ed50fe9c42ff4a67084ed70ce934404f5549ca68bc54e6d2f1436e66ea8d22f`); logs and retained QA controls are alongside it and in `../navigation-oracle-qa-attempt02/`. This actual build attribution does not change the source-review `reviewed_core` pin of `6520a89dc042dd433454f6cc63cb23b0e6df7911`.
|
||||
|
||||
The first standalone fixture receipt, `../navigation-oracle-commands/draw-fixture/receipt.json`, records exit 3221225781 (`0xC0000135`, Windows DLL-not-found status), with no stdout or stderr. The same executable succeeded after staging beside it the matching `libunwind.dll`: `../navigation-oracle-commands/draw-fixture-runtime02/receipt.json` records exit 0, unchanged executable, empty stderr, and child PATH `C:\Windows\System32;C:\Windows`. Its stdout reports the saved `../native-draw-fixture/project.arcadia` and Project JSON. The intermediate `draw-fixture-runtime/failure.json` instead records a Python recorder `SystemRoot` key-case failure before the oracle started; it is not an oracle or fixture result.
|
||||
|
||||
Verified current private runtime files in `../navigation-oracle-runtime/`:
|
||||
|
||||
| File | Bytes | SHA-256 |
|
||||
| --- | ---: | --- |
|
||||
| navigation-oracle.exe | 114890752 | `bd2db2e177d9d21f44b45e5efa89071e00155d31509672bf5aea6a57915e09a6` |
|
||||
| libunwind.dll | 190976 | `f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4` |
|
||||
|
||||
The executable came from `C:/Users/User/Documents/Git/ARCADIA-Studio/.local/target-gnullvm/debug/navigation-oracle.exe`. A verified identical DLL is `C:/ar-save-01a0d717/.local/package46/extracted/ArcadiaStudio-0.2.0-preview.save-diagnostics.20261001-x86_64-pc-windows-gnullvm/libunwind.dll`. For another private runtime copy, use a new directory and verify both hashes before launch. This recipe reproduces the tested arrangement; the particular new paths below have not been executed:
|
||||
|
||||
```powershell
|
||||
$runtimeCopy = 'C:/ar-nav-01a0d717/.local/navigation-oracle-runtime-copy-01'
|
||||
$fixtureOutput = 'C:/ar-nav-01a0d717/.local/native-draw-fixture-copy-01'
|
||||
if ((Test-Path -LiteralPath $runtimeCopy) -or (Test-Path -LiteralPath $fixtureOutput)) { throw 'Choose fresh runtime/output paths' }
|
||||
New-Item -ItemType Directory -Path $runtimeCopy -ErrorAction Stop | Out-Null
|
||||
Copy-Item -LiteralPath 'C:/ar-nav-01a0d717/.local/navigation-oracle-runtime/navigation-oracle.exe' -Destination $runtimeCopy -ErrorAction Stop
|
||||
Copy-Item -LiteralPath 'C:/ar-nav-01a0d717/.local/navigation-oracle-runtime/libunwind.dll' -Destination $runtimeCopy -ErrorAction Stop
|
||||
$oracle = Join-Path $runtimeCopy 'navigation-oracle.exe'
|
||||
if ((Get-FileHash -LiteralPath $oracle -Algorithm SHA256).Hash -ne 'bd2db2e177d9d21f44b45e5efa89071e00155d31509672bf5aea6a57915e09a6') { throw 'Executable pin mismatch' }
|
||||
if ((Get-FileHash -LiteralPath (Join-Path $runtimeCopy 'libunwind.dll') -Algorithm SHA256).Hash -ne 'f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4') { throw 'Runtime pin mismatch' }
|
||||
$savedOraclePath = $env:PATH
|
||||
try {
|
||||
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
|
||||
& $oracle fixture $fixtureOutput
|
||||
if ($LASTEXITCODE -ne 0) { throw "Oracle exited $LASTEXITCODE" }
|
||||
} finally {
|
||||
$env:PATH = $savedOraclePath
|
||||
}
|
||||
```
|
||||
|
||||
The PATH change applies only to this shell and is restored; no compiler directory is needed for this standalone launch. For recorded commands, the existing `../record-native-oracle.py` (SHA-256 `abbe6fae3055673ca3c33f0f87cba21189dd2ca905f63acc2da69b0b605fbc23`) checks executable/DLL pins, sets the child system-only PATH, and retains argv, stdout, stderr, exit, repository HEAD and build-receipt hash in a fresh command directory. The Python recorder is optional evidence tooling, not a runtime dependency of the Rust executable.
|
||||
|
||||
## Fixed01 saved-evidence inventory for scoped review
|
||||
|
||||
At this review, `../navigation-oracle-commands/fixed01-*` contains ten snapshot receipts and seven compare receipts. All 17 record exit 0, empty stderr and an unchanged executable with the runtime pin above. Snapshots are `prepared`, `baseline`, `inspected`, `name-applied`, `name-undo`, `implicit-applied`, `implicit-undo`, `switched`, `projection-undo` and `projection-redo`, under `../navigation-native/fixed01/`. Each compare folder below contains its actual `receipt.json`, `stdout` JSON report and `stderr`:
|
||||
|
||||
| Compare receipt suffix after fixed01- | Before → after | Reported contract/result |
|
||||
| --- | --- | --- |
|
||||
| preparation-equality | prepared → baseline | Exact full Project and raw source, pass |
|
||||
| inspection-equality | baseline → inspected | Exact full Project and raw source, pass |
|
||||
| name-undo-equality | baseline → name-undo | Exact full Project and raw source, pass |
|
||||
| implicit-undo-equality | baseline → implicit-undo | Exact full Project and raw source, pass |
|
||||
| projection-conservation | baseline → switched | Only nominated projection change, pass |
|
||||
| projection-undo-equality | baseline → projection-undo | Exact full Project and raw source, pass |
|
||||
| projection-redo-equality | switched → projection-redo | Exact full Project and raw source, pass |
|
||||
|
||||
The projection report nominates diagram `00000000-0038-4000-8000-000000000001`; only `views/diagram_issue38_representative_mixed.arcadia` changes, from `Traceability` at byte span [338,350) to `Allocation` at [338,348). Full expected Project and entry/path set match; prefix/suffix outside the projection value are byte-exact. Its raw_source_bytes_equal is correctly false because that one permitted token changes. Six exact comparisons instead report no changed files and raw_source_bytes_equal true. These receipts preserve the existing `work_without_behavior_context` warning; success does not mean a warning-free model.
|
||||
|
||||
The separate parameterized `../check-native-name.py` (SHA-256 `617a189b546ded2c9133dd0884152cea265d2f86041a5ef983394e6063c82f72`) reviews full saved Project equality after exactly one nominated relation-name edit, and full raw source equality after exactly one unique declaration-header replacement. It fixes baseline/fixture paths and relation UUID `628cb4fc-fe63-5f6b-b364-6268f26b5740`; only the after-directory, expected name and new report stem are parameters. It is not a general parser/admission substitute: the pinned Rust snapshot receipts supply source admission. Intended invocation for the already-recorded implicit case was `python .local/check-native-name.py implicit-applied "Implicit assessment delivery" implicit-delta-check`; it now refuses overwriting the existing report. Use a fresh report stem for any separately authorized rerun.
|
||||
|
||||
`../navigation-native/fixed01/implicit-delta-check.json` is SHA-256 `eb14e21c5b75dd3ba63200ccf8860cb85aee6f5688b4db2626958060178f5f09`, reports all five checks true, and binds that evaluator hash, baseline snapshot `7695c2ff4aa735110cb198f6451be596c5ac59a65ae9ccd7c7602e0259cb5e9a`, after snapshot `86fe91f1eaf0e8a0a9a509c7f4d2e5f90ec5f2b01a9169e0678dc8342a7a282b`, and original fixture `9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8`. The changed declaration is in `oa/behavior/process_oa_assessment_procedure.arcadia`. The earlier explicit-name `name-delta-check.json` also reports five true checks but has no evaluator_sha256 field; do not retrospectively attribute it to the current parameterized helper.
|
||||
|
||||
This inventory does not establish GUI click order, one-step interaction history, visible content, fresh reopening, or unsaved RAM conservation. Those claims require the parent's corresponding native capture/action evidence. No GUI, Rust/Python oracle command or Cargo test was executed during this README update. Pre-update README SHA-256 `496bc4eab252671232f6d2269b95fa0bc3596aad36341ca362cca9c140df3e08` is retained under `revisions/06-before-runtime-instructions/`; source and evaluator bytes are unchanged.
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\draw01\\bin\\arcadia-studio.exe",
|
||||
"id": 43582746,
|
||||
"title": "Cross-layer native Draw verification — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "27-enabled-functional-tool-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "81c25b3d2e1a189389b5abdb0b3987d5984fb3491a56600ff36c870b584efab7"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\fixed01\\bin\\arcadia-studio.exe",
|
||||
"id": 15008282,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough * — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "20-click-away-undo-saved-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "6b57d48bfdf74e3b942cb57fadf5e3ade360a6838ae5f7178e4a30de0815ac83"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 141 KiB |
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\draw01\\bin\\arcadia-studio.exe",
|
||||
"id": 43582746,
|
||||
"title": "Cross-layer native Draw verification — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "03-sa-source-on-oa-menu-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "b4e365b03c7965b78205703e9f8211f9ef330a0f523a48089ef510edc4140631"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\peer01\\bin\\arcadia-studio.exe",
|
||||
"id": 25822524,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough * — Arcadia Studio"
|
||||
},
|
||||
"accessibility": {
|
||||
"focused_element": "0 window Restore safe water pressure — release 1 walkthrough — Arcadia Studio Secondary Actions: Raise",
|
||||
"tree": "Window: \"Restore safe water pressure — release 1 walkthrough — Arcadia Studio\", App: arcadia-studio.exe.\n\t0 window Restore safe water pressure — release 1 walkthrough — Arcadia Studio Secondary Actions: Raise\n\t\t1 title bar (settable, string) Value: Restore safe water pressure — release 1 walkthrough — Arcadia Studio\n\t\t\t2 menu bar System ID: MenuBar\n\t\t\t\t3 menu item System\n\t\t\t4 button Minimize\n\t\t\t5 button Maximize\n\t\t\t6 button Close\n\nThe focused UI element is 0 window Restore safe water pressure — release 1 walkthrough — Arcadia Studio Secondary Actions: Raise."
|
||||
},
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "06-valid-pending-name-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "7dcac290662e627f12d14239f2775fb15370ee4cc5a82d1177bd4a8d0054e33e"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\relocation01-reopen\\bin\\arcadia-studio.exe",
|
||||
"id": 2360006,
|
||||
"title": "Cross-layer native Draw verification — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "09-inspection-no-undo-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "b863bcc65d39147b44f3075aa9aa377383e3868ca90a1bd1c7d1ee41840bc975"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\baseline01\\bin\\arcadia-studio.exe",
|
||||
"id": 53937404,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "04-edge-click-wrong-oa-view-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "9c437213d3baaa2817c1a134ff2735a9d2d45893cb7fb8afad39f14f08ffd93c"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
diagram mixed_oa "OA mixed Draw canvas" id "a60ffb09-36c2-5abc-bec5-6eab563c8cb8" {
|
||||
projection Traceability;
|
||||
}
|
||||
|
||||
node mixed_oa_oa_source id "5a8083e2-55ea-5fa3-94b4-3e5aafc1922f" {
|
||||
diagram @relocation_empty_sa;
|
||||
element @oa_source;
|
||||
position {
|
||||
x 60.0;
|
||||
y 60.0;
|
||||
};
|
||||
}
|
||||
|
||||
node mixed_oa_oa_target id "1f2bb997-b4af-5910-b91b-1948764cff4f" {
|
||||
diagram @mixed_oa;
|
||||
element @oa_target;
|
||||
position {
|
||||
x 520.0;
|
||||
y 60.0;
|
||||
};
|
||||
}
|
||||
|
||||
node mixed_oa_sa_source id "752822e9-1f95-5e9c-b1d3-58c325d875fa" {
|
||||
diagram @mixed_oa;
|
||||
element @sa_source;
|
||||
position {
|
||||
x 60.0;
|
||||
y 300.0;
|
||||
};
|
||||
}
|
||||
|
||||
node mixed_oa_sa_target id "eb2ce67e-c283-5689-8780-b54e1b06d7cc" {
|
||||
diagram @mixed_oa;
|
||||
element @sa_target;
|
||||
position {
|
||||
x 520.0;
|
||||
y 300.0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\fixed01\\bin\\arcadia-studio.exe",
|
||||
"id": 15008282,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "06-oa-edge-inspected-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "4d95c38ee9b926dd0e68c0597514eaf60f0ee04f76a43e11d71a9073d32a61b1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
--- a/crates/oa-desktop/src/connection_edit.rs
|
||||
+++ b/crates/oa-desktop/src/connection_edit.rs
|
||||
@@ -37,6 +37,26 @@
|
||||
let compatible = self.tool.targets(project, source);
|
||||
self.candidates = visible(view).filter(|id| compatible.contains(id)).collect();
|
||||
}
|
||||
+}
|
||||
+
|
||||
+/// Contextual drawing follows the visible semantic source, not the palette layer.
|
||||
+/// The caller separately validates the menu/context lifetime and pending drafts.
|
||||
+pub fn can_begin(project: &Project, diagram: Id, tool: Tool, source: Id) -> bool {
|
||||
+ let Some(diagram) = project.diagrams.get(&diagram) else {
|
||||
+ return false;
|
||||
+ };
|
||||
+ let Some(element) = project.elements.get(&source) else {
|
||||
+ return false;
|
||||
+ };
|
||||
+ diagram.projection.is_architecture()
|
||||
+ && (element.layer() == diagram.layer
|
||||
+ || diagram.projection == oa_core::ProjectionKind::Traceability)
|
||||
+ && Tool::for_layer(element.layer()).contains(&tool)
|
||||
+ && tool.can_start(project, source)
|
||||
+ && (tool != Tool::Allocation
|
||||
+ || tool.available_in(element.layer(), diagram.projection))
|
||||
+ && visible(&oa_core::projection::project_view(project, diagram.id))
|
||||
+ .any(|id| id == source)
|
||||
}
|
||||
|
||||
pub fn visible(view: &ProjectedView) -> impl Iterator<Item = Id> + '_ {
|
||||
--- a/crates/oa-desktop/src/model_menu.rs
|
||||
+++ b/crates/oa-desktop/src/model_menu.rs
|
||||
@@ -557,10 +557,7 @@
|
||||
&format!("Draw {}", tool.label().to_lowercase()),
|
||||
Message::Connection(crate::connection_edit::Event::Begin(*tool, id)),
|
||||
app.active_diagram.is_some_and(|diagram| {
|
||||
- crate::connection_edit::visible(&projection::project_view(
|
||||
- p, diagram,
|
||||
- ))
|
||||
- .any(|element| element == id)
|
||||
+ crate::connection_edit::can_begin(p, diagram, *tool, id)
|
||||
}),
|
||||
));
|
||||
}
|
||||
--- a/crates/oa-desktop/src/app.rs
|
||||
+++ b/crates/oa-desktop/src/app.rs
|
||||
@@ -2751,7 +2751,23 @@
|
||||
e.kind() != Kind::Port && d.projection.includes(e.kind())
|
||||
})
|
||||
});
|
||||
- if show_in_mixed_view || menu.request.canvas.is_some() {
|
||||
+ // Draw acts on the visible current view even when opened from
|
||||
+ // Explorer; explicit Select/Open actions still navigate.
|
||||
+ let draw_in_current_view = match &*action {
|
||||
+ Message::Connection(crate::connection_edit::Event::Begin(tool, source)) => {
|
||||
+ *source == menu.request.object
|
||||
+ && self.active_diagram.is_some_and(|diagram| {
|
||||
+ crate::connection_edit::can_begin(
|
||||
+ self.project(),
|
||||
+ diagram,
|
||||
+ *tool,
|
||||
+ *source,
|
||||
+ )
|
||||
+ })
|
||||
+ }
|
||||
+ _ => false,
|
||||
+ };
|
||||
+ if show_in_mixed_view || draw_in_current_view || menu.request.canvas.is_some() {
|
||||
if !self.inspect_canvas_target(menu.request.object) {
|
||||
return Task::none();
|
||||
}
|
||||
@@ -5042,8 +5042,30 @@
|
||||
epoch: self.canvas_epoch,
|
||||
})
|
||||
}
|
||||
+ // Admission belongs to the caller: palette choice, contextual source, or
|
||||
+ // an already admitted session whose complete context is still current.
|
||||
+ fn arm_connection(
|
||||
+ &mut self,
|
||||
+ context: crate::route_edit::Context,
|
||||
+ tool: crate::connection_tool::Tool,
|
||||
+ ) -> crate::route_edit::Context {
|
||||
+ self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
+ let context = crate::route_edit::Context {
|
||||
+ epoch: self.canvas_epoch,
|
||||
+ ..context
|
||||
+ };
|
||||
+ self.connection = Some(crate::connection_edit::Session::new(
|
||||
+ self.project(),
|
||||
+ &project_view(self.project(), context.diagram),
|
||||
+ context,
|
||||
+ tool,
|
||||
+ ));
|
||||
+ self.selected_bend = None;
|
||||
+ self.status = tool.hint().into();
|
||||
+ context
|
||||
+ }
|
||||
fn edit_connection(&mut self, event: crate::connection_edit::Event) {
|
||||
- use crate::connection_edit::{Event, Session};
|
||||
+ use crate::connection_edit::Event;
|
||||
let Some(context) = self.route_context() else {
|
||||
return;
|
||||
};
|
||||
@@ -5059,10 +5081,17 @@
|
||||
}
|
||||
match event {
|
||||
Event::Begin(tool, source) => {
|
||||
- self.edit_connection(Event::Choose(Some(tool)));
|
||||
- if let Some(context) = self.route_context() {
|
||||
- self.edit_connection(Event::Endpoint(context, source));
|
||||
- }
|
||||
+ if !crate::connection_edit::can_begin(
|
||||
+ self.project(),
|
||||
+ context.diagram,
|
||||
+ tool,
|
||||
+ source,
|
||||
+ ) {
|
||||
+ self.status = "Choose a visible compatible source in this diagram.".into();
|
||||
+ return;
|
||||
+ }
|
||||
+ let context = self.arm_connection(context, tool);
|
||||
+ self.edit_connection(Event::Endpoint(context, source));
|
||||
}
|
||||
Event::Choose(Some(tool)) => {
|
||||
if !crate::connection_tool::Tool::for_layer(self.layer).contains(&tool) {
|
||||
@@ -5076,16 +5105,7 @@
|
||||
{
|
||||
return;
|
||||
}
|
||||
- self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
- let context = self.route_context().unwrap();
|
||||
- self.connection = Some(Session::new(
|
||||
- self.project(),
|
||||
- &project_view(self.project(), context.diagram),
|
||||
- context,
|
||||
- tool,
|
||||
- ));
|
||||
- self.selected_bend = None;
|
||||
- self.status = tool.hint().into();
|
||||
+ self.arm_connection(context, tool);
|
||||
}
|
||||
Event::Endpoint(captured, id) => {
|
||||
if captured != context {
|
||||
@@ -5164,8 +5184,11 @@
|
||||
Event::Restart(captured) => {
|
||||
if captured == context
|
||||
&& let Some(session) = &self.connection
|
||||
+ && session.context == context
|
||||
{
|
||||
- self.edit_connection(Event::Choose(Some(session.tool)));
|
||||
+ // A contextual mixed-layer tool need not be in this layer's
|
||||
+ // palette. Its unchanged session context retains admission.
|
||||
+ self.arm_connection(context, session.tool);
|
||||
}
|
||||
}
|
||||
Event::Choose(None) => {}
|
||||
@@ -1,5 +0,0 @@
|
||||
diagram relocation_empty_sa "Native relocation empty SA Traceability" id "59eb4f9d-e6d5-570b-903d-b1b0a7fdcb4d" {
|
||||
layer Sa;
|
||||
projection Traceability;
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"pass": true,
|
||||
"mode": "oa",
|
||||
"before": "sa-redo",
|
||||
"after": "oa-created",
|
||||
"baseline_evidence": {
|
||||
"snapshot_sha256": "0eef3229db1da360bc1651780db7420b1f491631944722d6bdc6568aafcadea7",
|
||||
"receipt_sha256": "da919f6ee43a1f433239ae41757022ee12381be09c7755844298d8c5c4a84258",
|
||||
"stdout_sha256": "136f13545f3c1c08c786b0203ffb2c4794da58fbce7457622ab71663993be5aa"
|
||||
},
|
||||
"before_evidence": {
|
||||
"snapshot_sha256": "8c688a41ec233afb011dee26b32616c0c708472b9844816403dd1b648c804ddf",
|
||||
"receipt_sha256": "75807de212a9db970cc24b8b92c7b5ea47a3606cd4c397139a6506b3ca1e318a",
|
||||
"stdout_sha256": "d4636dc4d16d391f655e8ca1d0f7e4ab3999297faefa95e075d43e94f803885a"
|
||||
},
|
||||
"after_evidence": {
|
||||
"snapshot_sha256": "74307d7754a2825ae400f2a7e43a399b5287a6ea47c101242ea4d09bd9ad0373",
|
||||
"receipt_sha256": "e2eeca603061159c453eef8d56e8c0e13e11c9383eb020540b66c2303e446f02",
|
||||
"stdout_sha256": "18542dd87db7d551e7f52852716738641f80f0c0a200b480b5e2f185e537bfe5"
|
||||
},
|
||||
"fixture_sha256": "520e5645bcf87380a50be08cb9ef758b85a19414e58ce3bd61926c09767a7963",
|
||||
"relation": "a425a1af-e16f-4e25-9fb7-9f063dbe2fda",
|
||||
"derived_ports": [],
|
||||
"expected_relation": {
|
||||
"id": "a425a1af-e16f-4e25-9fb7-9f063dbe2fda",
|
||||
"alias": "interaction_oa_request_repair_oa_perform_repair",
|
||||
"name": "Interaction: OA request repair \u2192 OA perform repair",
|
||||
"description": "",
|
||||
"body": {
|
||||
"Interaction": {
|
||||
"source": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"target": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"full_expected_project_equal": true,
|
||||
"source_inventory_only": {
|
||||
"added_paths": [],
|
||||
"removed_paths": [],
|
||||
"changed_existing_paths": [
|
||||
"oa/activities/activity_oa_source.arcadia"
|
||||
]
|
||||
},
|
||||
"scope": "Exact complete saved Project delta; historical pinned Rust capture admission. Source insertion bytes are inventoried, not proved. Receipts do not hash snapshot payloads; the report pins current bytes. No GUI, unsaved RAM or history-step-count claim.",
|
||||
"evaluator_sha256": "6ac2c23068c5e1921f8734d6f3cb1af30b9039e2dc3382cef6c981db2868e07f",
|
||||
"time": "2026-10-02T06:30:30.073769+00:00"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 98 KiB |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"label": "fixed01-switched",
|
||||
"argv": [
|
||||
"C:\\ar-nav-01a0d717\\.local\\navigation-oracle-runtime\\navigation-oracle.exe",
|
||||
"snapshot",
|
||||
"C:/ar-nav-01a0d717/.local/navigation-native/fixed01/working/eight-save/project.arcadia",
|
||||
"C:/ar-nav-01a0d717/.local/navigation-native/fixed01/switched"
|
||||
],
|
||||
"started": "2026-10-02T06:06:50.204350+00:00",
|
||||
"exe_sha256": "bd2db2e177d9d21f44b45e5efa89071e00155d31509672bf5aea6a57915e09a6",
|
||||
"runtime_sha256": "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4",
|
||||
"path": "C:\\Windows\\System32;C:\\Windows",
|
||||
"head": "c7065db76ecca3faa733ccb5f09b87fd0a0958be",
|
||||
"build_receipt_sha256": "1ed50fe9c42ff4a67084ed70ce934404f5549ca68bc54e6d2f1436e66ea8d22f",
|
||||
"exit_code": 0,
|
||||
"finished": "2026-10-02T06:06:52.193230+00:00",
|
||||
"stdout_bytes": 780,
|
||||
"stderr_bytes": 0,
|
||||
"exe_unchanged": true
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
# Independent review of proposed navigation repairs
|
||||
|
||||
2026-10-01. Reviewed against clean tracked source HEAD `6520a89dc042dd433454f6cc63cb23b0e6df7911` in `C:/ar-nav-01a0d717`. These are **unapplied proposals**, not a tested implementation:
|
||||
|
||||
- `.local/draft-application-repair.patch`: SHA256 `b2bea569ee566d2138c66cec002d83480c279215e909a34db327d4e8f44d1ee8`, authored by parent.
|
||||
- `.local/connection-begin-repair.patch`: SHA256 `9640d795f22c4768e2e5077b21da894810344e87b210560b3a877583439b1c5b`, authored by another agent.
|
||||
- I authored the separate draft falsifier tests/policy, not either production proposal. The nine draft tests are still unapplied and unrun; I also read the proposed connection test names/control structure without executing them.
|
||||
|
||||
`git apply --check` with both production patch paths returned exit0 against this source. This establishes textual applicability only. No tracked files, Cargo, GUI, HTTP, or Gitea were modified or operated. This report is a conceptual/source review, not approval of runtime behavior.
|
||||
|
||||
## Findings and bounded risks
|
||||
|
||||
### 1. Remaining non-canvas Draw affordance mismatch
|
||||
|
||||
`model_menu.rs:552–564` offers Draw based on visibility in the active diagram, including the proposed `can_begin`. But a non-canvas menu request, such as Explorer's `model_menu::open` (`explorer_view.rs:141`, `:230`), has no canvas context. `app.rs:2738–2760` calls explicit `select(request.object)` before dispatching Begin unless the action is Show in a mixed view.
|
||||
|
||||
Falsifier: while an SA Traceability view contains a visible OA source, open that source's non-canvas model menu and select its enabled Draw action. Dispatch first navigates to OA; if the OA destination lacks a placement for that source, the proposed Begin refuses it as invisible. If that destination does contain the source, drawing arms there rather than on the view used to enable the menu. This mismatch predates the proposal's canvas correction; it is not evidence that the new `can_begin` admission should be weakened.
|
||||
|
||||
Canvas and Actions requests have `request.canvas` and do not follow this path. Keep acceptance explicitly bounded to them, or align the non-canvas action's enabling/destination contract separately. A practical regression should obtain the actual enabled popup message from `Request::object` without setting `canvas`, rather than injecting bare Begin. I did not execute this reproduction.
|
||||
|
||||
### 2. Dirty child creation can lose its explicitly requested kind at the handler boundary
|
||||
|
||||
With the draft proposal, `CreateContextElement(Activity, true)` → `Add(true)` → successful `apply()` → `sync()` preserves the SA canvas but `normalize_creation()` changes the global `create_kind` from Activity to Function (`app.rs:868`, `:895–901`). `add_element` then derives OA from the selected OA parent and calls `make_element(Function, Oa)`, which returns None (`app.rs:4414–4436`; `model.rs:1901`). The rename commits but no child is created.
|
||||
|
||||
Direct-message falsifier: use the existing mixed authoring fixture, inspect an OA Activity in the SA Traceability view, create a valid pending name draft, and dispatch `CreateContextElement(Activity, true)`. Preserve the explicit requested kind across accepted drafts if this is part of the handler contract. Do not globally relax active-layer palette normalization.
|
||||
|
||||
**Reachability qualification:** current `CreateContextElement` producers are menu actions, and both popup and Invoke refuse dirty drafts. Sidebar Add(true) is filtered by active-layer `can_parent`/allocation guards, so I have not established a current native route to this exact dirty cross-layer child case. My initial message identified the handler failure before this producer audit; it must not be reported as an observed UI blocker. Clean contextual child actions are unaffected. The parent can either cover this narrow handler case or keep it explicitly outside the repair's contract; it does not justify widening the palette scope.
|
||||
|
||||
### 3. No additional source-level blocker found in the targeted fixes
|
||||
|
||||
For the accepted Properties/inspection policy, the draft proposal resolves the origin before `sync`, rather than rebuilding UI state on the incidental object layer and restoring it afterward. It captures only saved architecture projections on Page::Diagram; `ProjectionKind::is_architecture` explicitly excludes typed StateMachine and Scenario. It follows the accepted diagram's resulting UUID only when that very diagram was the draft's original record, and reads its actual resulting layer. Deliberate layer and projection changes are therefore not blindly undone.
|
||||
|
||||
The visible-hit check compares original surface identity/layer/projection and recomputes target visibility after accepted edits. The first four independent falsifiers should now reach the intended behavior on source reasoning: name edits preserve inspection context; diagram reidentity and projection changes commit their valid edit but refuse the old hit. A revision increment alone does not refuse every otherwise-valid click. Ordinary node fallback receives the same surface/visibility boundary.
|
||||
|
||||
The `was_visible` exception is deliberate, not general authorization for hidden hits: it permits an invisible Show target inspected through existing contextual menus. Current CanvasSelect independently admits only visible edges/ports before calling the helper; context-menu Invoke refuses dirty drafts; connection Endpoint also refuses them. Thus the helper's invisible path does not, through these inspected callers, bypass the dirty-draft revalidation of a real hit. Preserve these caller preconditions. Group selection continues to refuse dirty drafts rather than auto-apply them.
|
||||
|
||||
## Navigation and lifecycle checks
|
||||
|
||||
- **Explicit Select:** after draft acceptance it still checks the requested target's semantic layer and chooses its diagram. Preserving the earlier canvas in `apply` does not remove this explicit step. The already-inspected cross-layer object does not hit the early return because its layer differs from the canvas layer. Existing postreview `focus_after_review` remains unchanged.
|
||||
- **Direct contextual forms:** AddRelation and AddRelationKind already derive the semantic layer from the selected object. PortAction derives its workflow from that object. The proposed shared acceptance step retains their originating architecture canvas while opening them. Workflow successful Apply still executes its explicit destination/focus behavior. The dirty-child exception is separately described above.
|
||||
- **Show/latent actions:** mixed-view Show keeps its existing special dispatch and may inspect an existing invisible element without navigating first. Neither proposal broadens semantic placement validation or makes a hidden source eligible for drawing. Actions can inspect a latent selected object; Draw remains disabled unless its source is actually projected.
|
||||
- **Typed Behavior/Scenario:** no originating architecture tuple is produced on Page::Behavior or for a typed saved projection. Existing behavior pending-form guards and saved-Scenario reidentity logic remain active. A valid edit turning the active architecture diagram into a typed view is not restored as architecture; existing post-handler state/scenario routing still owns the transition. This source argument needs the existing lifecycle regression gates; it is not native coverage.
|
||||
- **Source application:** `apply_source` is unchanged, and ordinary `apply` still refuses while source drafts are dirty. Accepted source edits increment the document revision, invalidating captured menu/endpoint contexts. These patches do not establish a new arbitrary source-reidentity mapping. Do not claim that a textual UUID rewrite follows the same known original/resulting-ID mechanism as a Properties draft.
|
||||
|
||||
## Connection admission and stale-session assessment
|
||||
|
||||
The proposed `can_begin` checks a real architecture diagram, a real element, same layer or Traceability, the semantic layer's tool family, `can_start`, Allocation projection eligibility, and actual projected visibility. This preserves port-direction/type checks delegated to the tool and excludes typed views and hidden sources. It does not need to require targets before starting; an admitted session with no compatible visible target already reports that condition.
|
||||
|
||||
Palette Choose retains its active-layer eligibility. Contextual Begin uses the source's layer, fixing the old recursive-Choose mismatch without permitting arbitrary opposite-layer drawing in ordinary views. `arm_connection` only factors session construction; its callers still own admission. It advances the input epoch and stamps the resulting context while model/document/diagram/revision remain synchronous and unchanged.
|
||||
|
||||
Restart now requires captured context, current route context, and stored session context to agree, then retains the admitted tool even if it is absent from the displayed layer's palette. The outer update guards already reject delayed Endpoint/Restart messages before retiring a newer session. Menu token/document/revision/epoch checks still precede normal Begin dispatch; there is no new unwrapped UI producer of bare Begin. The proposal does not change semantic target checks, command planning, or commit/history behavior.
|
||||
|
||||
Useful runtime falsifiers remain: both mixed directions; functional ports and wrong-direction/wrong-layer targets; hidden source; Allocation in a projection hiding work; restart after a cross-layer Begin; stale token/revision/epoch/session; source application/reidentity between menu open and Invoke; and cancellation without model/source/history edits. The proposed connection tests cover several of these, and existing lifetime tests cover additional context guards, but neither test text nor this inspection establishes execution success.
|
||||
|
||||
Recommendation: the two proposals are proportionate and textually compatible for the bounded canvas/Properties repair. Run the preserved failing baselines and repaired tests before any success claim, then native editing/navigation and save/reopen gates. Reconcile or explicitly bound the two residual caller contracts above. No production, visual, full-suite, or platform acceptance is issued here.
|
||||
|
||||
## Follow-up: revised F2 proposal reviewed
|
||||
|
||||
The original assessment above is retained historically. Parent subsequently accepted Explorer Draw as a contextual command on the current visible view and kept the dirty-child handler case outside this current UI repair contract. I reviewed the worker's frozen replacement production patch SHA256 `0dde2c155af1dae63d612bc4a85dd9fe65d10e193ecbf306d0aabf3c5a67f4df` and connection test patch SHA256 `17062100d23d2e7d344c336317e00d560a57ec4a531a54df4846e0d75e0f1a57`, still against unchanged HEAD 6520a89. I did not author these patches.
|
||||
|
||||
The replacement adds a narrow `draw_in_current_view` dispatch exception at the original `app.rs:2751`: the action must be Connection Begin, its explicit source must equal the validated menu object's ID, and shared `can_begin` must admit that source in the active view. It then uses contextual inspection rather than `select`. The earlier menu token/document/revision/epoch/page/layer/diagram checks and dirty-draft refusal still execute first. The Begin handler independently repeats admission. Show and explicit Select remain distinct branches; palette normalization/admission is unchanged.
|
||||
|
||||
This resolves the source path in finding 1 for real enabled non-canvas Draw actions: both popup enabling and dispatch now use the same view/source admission without an intervening semantic-layer navigation. It does not broaden hidden sources, incompatible ports, unavailable Allocation or typed projections. A manually injected action that was never offered by the popup is not established as an accepted UI command by this review; I have not added a new arbitrary-action contract.
|
||||
|
||||
The added fifth connection test uses an actual `Request::object` with no canvas/placement envelope, extracts and dispatches the popup's token-wrapped Draw action, and exercises both OA-in-SA and SA-in-OA. The source-layer fallback diagram is present but empty, so the test would expose the old navigation/refusal path rather than accidentally finding another usable placement. Shared assertions verify unchanged surface/model/source/history when arming, correct session source/tool/candidate exclusions, then one completed relation, unchanged diagrams/existing elements and model/source undo/redo. It retains the previous actual-canvas, allocation, restart/cancel/stale-token tests.
|
||||
|
||||
Combined `git apply --check` for draft production, revised connection production and connection test patches returned exit0. No tracked change or runtime operation was performed. No further material source finding was identified in the revised F2 proposal. This is readiness for the parent's preserved failing run and repaired verification, not a claim that those runs passed.
|
||||
|
||||
For invalidated-click status after a valid Properties edit, see `.local/stale-canvas-hit-feedback.md`: use the existing status area, “Properties applied. The view changed; select a visible target again.” The initial stale-envelope and failed-validation paths must not claim an edit was applied. That note is a proposal, not an implemented or tested change.
|
||||
|
||||
## Follow-up: F1 feedback-inclusive proposal reviewed
|
||||
|
||||
Reviewed the separate parent-authored `.local/draft-application-repair-feedback.patch`, SHA256 `43f2ffb18e867ce64387862518f3b3ef6bd38864e533e33e3baef7a97c72a85d` (4,547 bytes). The original F1 patch remains preserved. The replacement captures `draft_dirty()` before calling `apply`, and writes the proposed status only after successful acceptance followed by target/surface invalidation. Failed validation returns before that branch; pre-dispatch stale context returns outside it; clean refusals keep their prior status. Both helper and SelectNode paths use the same text. No additional model mutation, history entry, navigation or dialog is introduced. No further material source finding was identified in this feedback delta.
|
||||
|
||||
At parent request I revised my independent test patch to assert exact success-with-refused-click feedback in the diagram reidentity/projection cases, preservation of the actual draft validation error on invalid ordinary clicks, the existing grouped refusal text, pre-envelope stale rejection's unchanged status/model/source/draft, and ordinary direct Apply's “Properties applied” status. The nine original case boundaries remain. Pre-feedback bytes are preserved exclusively as `.local/draft-canvas-regressions-before-feedback.patch`, SHA256 `d6f8062e0c5cd43104f52510ab0ff779106d90097978f7e19afcef0cd93792e5`. Revised `.local/draft-canvas-regressions.patch`: 14,623 bytes, SHA256 `4b80c0a88590fcbff0394bdd096969dfc672bf9c9510f6bca9cea473d5225c75`.
|
||||
|
||||
Combined `git apply --check` on revised draft tests, F1 feedback production, final F2 production and final F2 tests returned exit0 against unchanged source. This remains unapplied source/applicability review; there has been no compiler, test, or native execution by this reviewer.
|
||||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 146 KiB |
@@ -1,19 +0,0 @@
|
||||
sa_function sa_source "SA dispatch repair" id "fa0825f4-ae33-5eb3-9222-bf3a6465e72e" {
|
||||
}
|
||||
|
||||
sa_port functional_exchange_sa_dispatch_repair_sa_execute_repair_output "Functional exchange: SA dispatch repair → SA execute repair output" id "7957e536-d4b4-50c2-8271-ef829952353f" {
|
||||
contract {
|
||||
FunctionOutput {
|
||||
outgoing_items [];
|
||||
};
|
||||
};
|
||||
parent @sa_source;
|
||||
}
|
||||
|
||||
functional_exchange functional_exchange_sa_dispatch_repair_sa_execute_repair "Functional exchange: SA dispatch repair → SA execute repair" id "50e20de1-197f-4126-90da-81c6e4c93409" {
|
||||
source @sa_source;
|
||||
source_port @functional_exchange_sa_dispatch_repair_sa_execute_repair_output;
|
||||
target @sa_target;
|
||||
target_port @functional_exchange_sa_dispatch_repair_sa_execute_repair_input;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"case": "peer01",
|
||||
"binary_source": "6520a89dc042dd433454f6cc63cb23b0e6df7911",
|
||||
"start": "2026-10-02T05:21:47.781232+00:00",
|
||||
"binary_pins": {
|
||||
"arcadia-studio.exe": "fd6b2f3aeff99977fb59abad50104f9bd47852191e7e8d3016823cf5f1a415fe",
|
||||
"libunwind.dll": "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4"
|
||||
},
|
||||
"original_fixtures": {
|
||||
"eight-card.json": "9db1015369f450d5810c96a01fddb8a009d57f6aafbda5182703f15ded23d9c8"
|
||||
},
|
||||
"argv": [
|
||||
"C:\\ar-nav-01a0d717\\.local\\navigation-native\\peer01\\bin\\arcadia-studio.exe"
|
||||
],
|
||||
"cwd": "C:\\ar-nav-01a0d717\\.local\\navigation-native\\peer01\\working",
|
||||
"path": "C:\\Windows\\System32;C:\\Windows",
|
||||
"local_app_data": "C:\\ar-nav-01a0d717\\.local\\navigation-native\\peer01\\profile",
|
||||
"pid": 6820
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\draw01\\bin\\arcadia-studio.exe",
|
||||
"id": 43582746,
|
||||
"title": "Cross-layer native Draw verification * — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "32-projection-control-undo-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "5144f6f053d20d3d30995911eeefd80c195cd160b7793712846372dd6aff637d"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 153 KiB |
@@ -1,206 +0,0 @@
|
||||
{
|
||||
"schema_version": 7,
|
||||
"id": "01324fcc-9456-5341-8fd0-2c261bddf186",
|
||||
"alias": "navigation_draw",
|
||||
"name": "Cross-layer native Draw verification",
|
||||
"description": "Synthetic native acceptance fixture; four complete typed work elements, two saved mixed-layer views, initially no relations.",
|
||||
"elements": {
|
||||
"7b9d5c34-1c45-5048-a3a0-77e5784a5a69": {
|
||||
"id": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"alias": "oa_target",
|
||||
"name": "OA perform repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Oa": "Activity"
|
||||
}
|
||||
},
|
||||
"c5e2f9d6-ba03-50b6-9860-b2dd717c02b3": {
|
||||
"id": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"alias": "sa_target",
|
||||
"name": "SA execute repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": "Function"
|
||||
}
|
||||
},
|
||||
"e4a68a7d-6949-5c17-9263-c03c03c21af2": {
|
||||
"id": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"alias": "oa_source",
|
||||
"name": "OA request repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Oa": "Activity"
|
||||
}
|
||||
},
|
||||
"fa0825f4-ae33-5eb3-9222-bf3a6465e72e": {
|
||||
"id": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"alias": "sa_source",
|
||||
"name": "SA dispatch repair",
|
||||
"description": "",
|
||||
"parent": null,
|
||||
"body": {
|
||||
"Sa": "Function"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {},
|
||||
"diagrams": {
|
||||
"a60ffb09-36c2-5abc-bec5-6eab563c8cb8": {
|
||||
"id": "a60ffb09-36c2-5abc-bec5-6eab563c8cb8",
|
||||
"alias": "mixed_oa",
|
||||
"name": "OA mixed Draw canvas",
|
||||
"description": "",
|
||||
"layer": "Oa",
|
||||
"projection": "Traceability",
|
||||
"view_options": {
|
||||
"hidden_edges": [],
|
||||
"relation_kinds": "All",
|
||||
"work_categories": "All",
|
||||
"participant_categories": "All",
|
||||
"coverage": "All",
|
||||
"work_contexts": "All",
|
||||
"exchange_labels": "Name"
|
||||
},
|
||||
"nodes": {
|
||||
"1f2bb997-b4af-5910-b91b-1948764cff4f": {
|
||||
"id": "1f2bb997-b4af-5910-b91b-1948764cff4f",
|
||||
"alias": "mixed_oa_oa_target",
|
||||
"element": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"371bfc51-edcc-5770-aad1-11b84772d2c8": {
|
||||
"id": "371bfc51-edcc-5770-aad1-11b84772d2c8",
|
||||
"alias": "mixed_oa_oa_source",
|
||||
"element": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"752822e9-1f95-5e9c-b1d3-58c325d875fa": {
|
||||
"id": "752822e9-1f95-5e9c-b1d3-58c325d875fa",
|
||||
"alias": "mixed_oa_sa_source",
|
||||
"element": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"eb2ce67e-c283-5689-8780-b54e1b06d7cc": {
|
||||
"id": "eb2ce67e-c283-5689-8780-b54e1b06d7cc",
|
||||
"alias": "mixed_oa_sa_target",
|
||||
"element": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
"routes": []
|
||||
},
|
||||
"aa64b55c-9bbb-5444-bda4-c9be0c66242a": {
|
||||
"id": "aa64b55c-9bbb-5444-bda4-c9be0c66242a",
|
||||
"alias": "mixed_sa",
|
||||
"name": "SA mixed Draw canvas",
|
||||
"description": "",
|
||||
"layer": "Sa",
|
||||
"projection": "Allocation",
|
||||
"view_options": {
|
||||
"hidden_edges": [],
|
||||
"relation_kinds": "All",
|
||||
"work_categories": "All",
|
||||
"participant_categories": "All",
|
||||
"coverage": "All",
|
||||
"work_contexts": "All",
|
||||
"exchange_labels": "Name"
|
||||
},
|
||||
"nodes": {
|
||||
"44169450-37f5-59fd-b9ab-32482c943f99": {
|
||||
"id": "44169450-37f5-59fd-b9ab-32482c943f99",
|
||||
"alias": "mixed_sa_sa_target",
|
||||
"element": "c5e2f9d6-ba03-50b6-9860-b2dd717c02b3",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"a0a18bd6-891a-56a4-a46d-d7b7d9911fc9": {
|
||||
"id": "a0a18bd6-891a-56a4-a46d-d7b7d9911fc9",
|
||||
"alias": "mixed_sa_oa_target",
|
||||
"element": "7b9d5c34-1c45-5048-a3a0-77e5784a5a69",
|
||||
"position": {
|
||||
"x": 520.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"aca2f538-69bf-561f-9eff-433febee5234": {
|
||||
"id": "aca2f538-69bf-561f-9eff-433febee5234",
|
||||
"alias": "mixed_sa_sa_source",
|
||||
"element": "fa0825f4-ae33-5eb3-9222-bf3a6465e72e",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 300.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
},
|
||||
"f5e4244f-e1c4-59ec-9109-be1e851386c1": {
|
||||
"id": "f5e4244f-e1c4-59ec-9109-be1e851386c1",
|
||||
"alias": "mixed_sa_oa_source",
|
||||
"element": "e4a68a7d-6949-5c17-9263-c03c03c21af2",
|
||||
"position": {
|
||||
"x": 60.0,
|
||||
"y": 60.0
|
||||
},
|
||||
"size": {
|
||||
"width": 224.0,
|
||||
"height": 94.0
|
||||
},
|
||||
"collapsed": false
|
||||
}
|
||||
},
|
||||
"routes": []
|
||||
}
|
||||
},
|
||||
"artifact_links": {},
|
||||
"traces": {}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 128 KiB |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"after_diagnostics": [
|
||||
"SourceDiagnostic {\n path: \"sa/functions/function_sa_audit_thresholds.arcadia\",\n diagnostic: Diagnostic {\n severity: Warning,\n code: \"work_without_behavior_context\",\n object_id: Some(\n 4149a872-3440-59de-9879-8981e78d27ab,\n ),\n field: None,\n line: 1,\n column: 1,\n message: \"Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence.\",\n span: Span {\n start: 0,\n end: 278,\n },\n },\n related: [],\n}"
|
||||
],
|
||||
"before_diagnostics": [
|
||||
"SourceDiagnostic {\n path: \"sa/functions/function_sa_audit_thresholds.arcadia\",\n diagnostic: Diagnostic {\n severity: Warning,\n code: \"work_without_behavior_context\",\n object_id: Some(\n 4149a872-3440-59de-9879-8981e78d27ab,\n ),\n field: None,\n line: 1,\n column: 1,\n message: \"Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence.\",\n span: Span {\n start: 0,\n end: 278,\n },\n },\n related: [],\n}"
|
||||
],
|
||||
"changed_source_files": [],
|
||||
"expected_entire_project_equal": true,
|
||||
"model_paths_may_be_truncated": false,
|
||||
"pass": true,
|
||||
"permitted_projection": null,
|
||||
"projection_declaration_diff": null,
|
||||
"raw_entry_and_path_set_equal": true,
|
||||
"raw_source_bytes_equal": true,
|
||||
"source_contract_pass": true,
|
||||
"unexpected_model_paths_first_200": []
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"window": {
|
||||
"app": "process:C:\\ar-nav-01a0d717\\.local\\navigation-native\\fixed01\\bin\\arcadia-studio.exe",
|
||||
"id": 15008282,
|
||||
"title": "Restore safe water pressure — release 1 walkthrough — Arcadia Studio"
|
||||
},
|
||||
"accessibility": null,
|
||||
"screenshots": [
|
||||
{
|
||||
"filename": "22-allocation-switch-succeeds-0.jpg",
|
||||
"id": "screenshot-0",
|
||||
"width": 1442,
|
||||
"height": 932,
|
||||
"sha256": "be6a6ad2b930d952ce825faf9a50a7d657a91c45aec4a3c0f1abb9ad331dab80"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 53 KiB |
@@ -1,560 +0,0 @@
|
||||
{
|
||||
"source": "6520a89dc042dd433454f6cc63cb23b0e6df7911",
|
||||
"started": "2026-10-02T04:45:32.845796+00:00",
|
||||
"environment": "Rust1.98.1 gnullvm; LLVM-MinGW20260826UCRT; shared target; jobs1/incremental0",
|
||||
"source_inputs": {
|
||||
"AGENTS.md": "c758e562b07298f31adf3ec4b0cb646f76000623178ad21521647c76aa80b491",
|
||||
"Cargo.lock": "2aa80d113c5112b31c0767b874845d60f555e4e8ae7eb3ecd0b4b91954b55378",
|
||||
"Cargo.toml": "66d401d9f7629f1f6d174ae41b7f9203c9ce3a76d69abfc4381227dbdec12eae",
|
||||
"PROJECT_GOAL.md": "6a4c1a2ea531f1dc64f6b5a067a6fac5f6032f27cbc5a9ced37faa802f0a0fb1",
|
||||
"crates/oa-cli/Cargo.toml": "49c567f1af74a826683b4d96845feb3806fd9bcb7dc2767e16b5509d4e5e1cde",
|
||||
"crates/oa-cli/src/lib.rs": "790f3bdef78025813262c9677d4db5813f32978a398f3b7fb4900427dc0c314e",
|
||||
"crates/oa-cli/src/main.rs": "de964f9b6c59c0f31c080afbd3b31d442b07b1b44f6a187a248d3409c45d35fb",
|
||||
"crates/oa-cli/tests/source_cli.rs": "a3a1b856c7a9fd3d80bc8074150a24fbb0ef0e9ddf736c7cea036725fc3dc2b8",
|
||||
"crates/oa-core/Cargo.toml": "6b5c062474ebb62d5082e07dabed99ee85e4a833edf26690fb4f887028d3416f",
|
||||
"crates/oa-core/build.rs": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9",
|
||||
"crates/oa-core/examples/allocation_multiselect_native.rs": "73e36e3be2e974a16ee025191a3b17158bd623df1b23ed3ae29917c857311e54",
|
||||
"crates/oa-core/examples/attachment_snapshot.rs": "3e0c5581b36a93501c9aed677862bb965165e291eb69a03644bc9758e2e58c9b",
|
||||
"crates/oa-core/examples/generate_demo.rs": "e26f432c95331aeca50985dbb3ad2c558affd43023b585811f0f7bfaa59f6661",
|
||||
"crates/oa-core/examples/generate_release1.rs": "1d5f11165d74893e4bc18dbf3a2f2111f4026bd89fd06ee7f0bae69e45c4d4b5",
|
||||
"crates/oa-core/examples/multiselect_native.rs": "f265eefc50d455b0f32407527b2addc5057c8418f343640f5069d2410a348868",
|
||||
"crates/oa-core/examples/process_multiselect_native.rs": "7ce7dc9dedb22140a8c226252bfcb5861f581c605336ec442929c3ea42f26562",
|
||||
"crates/oa-core/examples/release_dense_views.rs": "d94ff767d7ca41808491d9fd8d20ab764fce723ea3ee48e60a04c95cd43af31d",
|
||||
"crates/oa-core/examples/routing_workshop.rs": "571307b18b6d4fa04700c5a4d4b0c2154c419e93a21292b2028e7b6f46a2faa7",
|
||||
"crates/oa-core/examples/state_bend_routing_snapshot.rs": "9054f0cdd2c1447de460feb9a325b61f9334c6d09458c24a724ab47f55c3610c",
|
||||
"crates/oa-core/examples/state_multiselect_native.rs": "e9757c2c7e9b8061b0fd375d4cbe57eb3047e04064787735ff05a71e55f7b580",
|
||||
"crates/oa-core/src/analysis_numeric.rs": "6c88eef3119162aeb6c07b9e75812c3af9fbfe75554b0503ad00f103cb478408",
|
||||
"crates/oa-core/src/analysis_numeric/dimension.rs": "d4ad8e5307a1620e31791d724a0e31fdba860d00dc83e3e1f2209d9c5e22a340",
|
||||
"crates/oa-core/src/analysis_numeric/exact.rs": "4eb8cb0f66b10e5da204899af82ee26adfec74bab47b1a6c9842b5488ee04c4d",
|
||||
"crates/oa-core/src/analysis_numeric/fixtures/r7-literal-boundaries.json": "3b959dec5e100a6f03817f5314bef497347ba579ebd235476516d4bd83399181",
|
||||
"crates/oa-core/src/analysis_numeric/meter.rs": "1f7174beb992935b4c399acdef91114af73faf041070feab968455ac827e8ee4",
|
||||
"crates/oa-core/src/analysis_numeric/quantity.rs": "ead5142d6eeea8f99399d57e02d8f7d819980923de76387cd454694d6a747aee",
|
||||
"crates/oa-core/src/analysis_numeric/tests.rs": "323ce82fc057d36f44463569fd8ea324c7d700fddfbf32bce15e04bd2a66970b",
|
||||
"crates/oa-core/src/data.rs": "7f9c7c7dd8353cb5a38597b92cde6d3e890203d392473187eb387d41ff37414a",
|
||||
"crates/oa-core/src/demo.rs": "1dc6227eef183149f0ec2ff09afcfd550be6d58139c0dcaa826ff42ba9268d33",
|
||||
"crates/oa-core/src/diagram_edit.rs": "7f307d64fe09947973871a05bc8d5dca82b64b65a8e6b3aaed22d6efc6497962",
|
||||
"crates/oa-core/src/language.rs": "4ede86f76ca54954cf9d7f1c12fd501934c8a04c94a0a442a0a5a7f8367a1f2f",
|
||||
"crates/oa-core/src/language/semantic_source.rs": "584b5bb59d87a7b8d44d5dd2d124c3a1323b21492b138a17bc9526c0087c9e29",
|
||||
"crates/oa-core/src/language_nesting_tests.rs": "bd061611e8cb6f28ab203c73e544ac98bf275eb464cd314ad5fbd25f773df33b",
|
||||
"crates/oa-core/src/language_outline_tests.rs": "4c79a01e8aea243dd9ef66ce71b9fae263d0e79c319a46c51223679897e8b9df",
|
||||
"crates/oa-core/src/lib.rs": "c7c947f97585618594b711dc90d482122bf41082e0d0c313be7c4e35e069dc24",
|
||||
"crates/oa-core/src/migration_v2/README.md": "fb61d3faa33d7bdfba73415415a0882c0fbd3e0b45df42f972a19aec6da927b9",
|
||||
"crates/oa-core/src/migration_v2/adapter.rs": "66741b4c8354a23b8a932ecc5bf1bc3bb6e74279558757b8c35bc2157a7e51b5",
|
||||
"crates/oa-core/src/migration_v2/mod.rs": "857d8bb13b6814daba6780ca770c16352943c01cb130245d055f11b3b957b3e9",
|
||||
"crates/oa-core/src/migration_v2/model.rs": "8ac63eb59b2e57bdecff2477ee0634096090924d3dd1851e1c454880a889dacb",
|
||||
"crates/oa-core/src/migration_v2/references.rs": "33a6819abc83139fdaebeebd1b553238a328637106cce7dd5a535f1791d43e20",
|
||||
"crates/oa-core/src/migration_v2/source.rs": "74fee9b24e7c912182c998ab9d9aeec125e2009c3214959229e844c67afe2bf6",
|
||||
"crates/oa-core/src/migration_v2/state_validation.rs": "e29ef071bab0a68460ddf55c46131c6f66c022d52c3c602c37c013c3649da1b0",
|
||||
"crates/oa-core/src/migration_v2/transitions.rs": "f74174c532ba56b57c80b719a11fb4adea0a78ccc2828fff72fe2b9eda5a10b1",
|
||||
"crates/oa-core/src/migration_v2/validation.rs": "6d5e4a4e2805ae5045bf789a0485e14a61c63346587bace2b4a91173a688f780",
|
||||
"crates/oa-core/src/migration_v3/README.md": "3755a63f840d7f0eadf68afa7a9d587877a9468ff982d3610539e8a0bb067a82",
|
||||
"crates/oa-core/src/migration_v3/mod.rs": "f4edb6a0b50ecfe8670973776d703047b064420cb1f05913129c00fc656b3722",
|
||||
"crates/oa-core/src/migration_v4.rs": "c21b7fcb89da61cf178156fe6373ab28ff67c4734af55a561171fe0ebbaeda7f",
|
||||
"crates/oa-core/src/model.rs": "05a6bd5eef14c77e7cafd2bc72eb3bb6b19f33c6d06184b4e7d21173a2684078",
|
||||
"crates/oa-core/src/operations.rs": "a5ffbbf54cd440316a106d1188bfb699b57dae59f466edcb33618c333cd19f41",
|
||||
"crates/oa-core/src/persistence.rs": "d6166a26c10722f064bc27c40735a3be0f4e4fddd33c8d538dcca1a62966e306",
|
||||
"crates/oa-core/src/ports.rs": "5cac12eea70a54ebebbd6be77b63ce92e3195325c4bc7d27705c518686cbac80",
|
||||
"crates/oa-core/src/projection.rs": "4d0b721568021668e88545c77f1045c319bfcdc3577ce4420f48c0ef2eaf6f9d",
|
||||
"crates/oa-core/src/projection/port_layout.rs": "a08d17d10a58bb8b206dd001b19b91d5aa3a9f743b298a4da57d503223c20079",
|
||||
"crates/oa-core/src/projection/state.rs": "73a607bac713ec7ff49a509fefb1a53c7686dca93ee241ffc835564d743afb28",
|
||||
"crates/oa-core/src/references.rs": "b6d2c388815c51469dd73eb62628c77ea6d95d20324c83e184b2b9f896aa8a4a",
|
||||
"crates/oa-core/src/references/scenario_layout_tests.rs": "663eb337726321e613b6fb391fc2ab84e4fa4a4ea791a3d95600b4d85d1edf9a",
|
||||
"crates/oa-core/src/routing.rs": "b377fe80cece3892d64f62ba5e1578d8974eeefc2a317620f343a86f2f257fbd",
|
||||
"crates/oa-core/src/routing/attachments.rs": "cfb4200cdb99e4d86eb6e5ac1b123e4075fb7d4c788860d154365ad8ece139d7",
|
||||
"crates/oa-core/src/scenario_layout.rs": "1cf56f501b09f099a36b476a01aee6a27ad139e884b23b253af66f39798b8767",
|
||||
"crates/oa-core/src/scenario_layout/tests.rs": "fd68063e40dc59bc3e68786aa54fe4080569a77b6cf63ed18ce6323a4b1be0db",
|
||||
"crates/oa-core/src/scenario_layout/tests/source_lifecycle.rs": "fc82db6f68f17419db599b96c96fad5c9654b1d5d127f415a9a8b81fb1f2b86a",
|
||||
"crates/oa-core/src/semantic.rs": "f9895dd5b8fb27d10d4cfbb41c3542ef2018365a589b8d5778e5674ae5a1f239",
|
||||
"crates/oa-core/src/semantic/catalog.rs": "1ff74fcca18084d178681d7ff38ee1ccac632c27c4de0595ba1ceab8067ab3a4",
|
||||
"crates/oa-core/src/semantic/historical.rs": "eaf3284bf234312698514cdb636bc8348e1b5d746cb16b18df777e77d312abfb",
|
||||
"crates/oa-core/src/semantic/linked.rs": "6f335b14e809aecfc35defcaf77cb821b080d1506dbc46297912b25af98c07a2",
|
||||
"crates/oa-core/src/semantic/mutate.rs": "dc2b4c4a6eb156fe468eaa8c85fd37a505c0cd8ea1f226cb69cd01689afd3d9d",
|
||||
"crates/oa-core/src/semantic/review.rs": "0c157d2c327e72e89cca3b423e71ee73798105c5bb7271439c1a101642c91e3e",
|
||||
"crates/oa-core/src/semantic/types.rs": "6922fe35089f12fad43ab0ce1c6886d01a3855f90b8e2543e99802ac5e6f1bbb",
|
||||
"crates/oa-core/src/session.rs": "c31ea79bf37a15a35c4b6b35c0c3ebf7c029ce0ec8213b3865308961bb3d9f52",
|
||||
"crates/oa-core/src/source_defaults.rs": "d5905e5765c6a5f110ea0acb7ab05551acad1d9fa0d8cdb023c275d7daa451f7",
|
||||
"crates/oa-core/src/source_defaults_tests.rs": "160c0ca3725e07777f87e1f82182e09cd1cf47f1aef37961443bf5a714aa660d",
|
||||
"crates/oa-core/src/source_files.rs": "bb903f8c53456f69c5ac213a192a1eb0d19abf8344bd82a03be8e659ba7bc94f",
|
||||
"crates/oa-core/src/source_project.rs": "d872f2a8f7c4add53f2708bf3fdaea1b4be715d4240f2d9b93baf4624d3f72a7",
|
||||
"crates/oa-core/src/source_project/source_migration.rs": "0c8bfdb032d28a6778433da9dfeda34d7c0a03b084ca510e16795690f70c59c0",
|
||||
"crates/oa-core/src/source_project/source_structure.rs": "9f1f83315856a7025fe662c0fbb13452f31613224a03663150d6225d1ddc162f",
|
||||
"crates/oa-core/src/state_validation.rs": "e28d7f74385e2a78763716b34491bd73a00bfeccc085970ceb130627e3faee18",
|
||||
"crates/oa-core/src/topology_context.rs": "16de69b297de95d93c73808621607833b713af811dd082f0169186ae1e5c3e2d",
|
||||
"crates/oa-core/src/topology_context/analysis.rs": "dead6194f0aa60cc46045c0e5ee10dbdbb689158c38634dfbbbc45d2ba88ac65",
|
||||
"crates/oa-core/src/topology_context/analysis_tests.rs": "17839263410815e96fe027d4eba3aae0a9ab3b82b16947e2b29ebe0d09782b56",
|
||||
"crates/oa-core/src/transitions.rs": "621d88214d4b95bb60be49a9f640733e85b40d024b65db08d700cf24e74d5861",
|
||||
"crates/oa-core/src/transitions/communication.rs": "2b476630a88edd9021b9156ca12a5402e01604095f0d4002afb452671903a028",
|
||||
"crates/oa-core/src/validation.rs": "c70410f668a7ebb19c11eff2eab625fd51bf8812e855869953cc25f5b39f308d",
|
||||
"crates/oa-core/src/validation/coverage.rs": "b08e4b344ecad055714168c83beff7fe29ba4edc53edbffa2eae04e5580a4d5f",
|
||||
"crates/oa-core/src/view_options.rs": "8bfafe0fce688b15db14b2dbe6d024689887509c1eb01a0fb3c9f7ca95b00198",
|
||||
"crates/oa-core/src/view_options_tests.rs": "64aac44de2df42a1012239a3dd75c473daf0c8990b3170b7bf7e78a556080208",
|
||||
"crates/oa-core/tests/attachment_geometry.rs": "4367f5fe0c8aee8ccd1b66f16327e1d20fe9434d733d7b2ffc281d2f97f685e0",
|
||||
"crates/oa-core/tests/attachment_lifecycle.rs": "720b71c0092b8506fa73f230de117f658cd5ce095dbf40217c0070ad65e651f0",
|
||||
"crates/oa-core/tests/attachment_trace_ports.rs": "b27f9c4029bbd8fc1966449e1f968aa6f7f217ca71f4d464955cecacaa51ab86",
|
||||
"crates/oa-core/tests/attachment_workshop.rs": "82a451dd96d806e88bfbcb363037898e054214210bca0e20e025415b9072a446",
|
||||
"crates/oa-core/tests/communication_boundary_scenarios.rs": "e1100b8f363910a487d90c73acc7df543dc6d532158c3c63ad11cd3d40d8e12b",
|
||||
"crates/oa-core/tests/communication_means.rs": "a25f2749180bc6f4beb1445bfad3351eedf0669131a7439b633ab804439e95a4",
|
||||
"crates/oa-core/tests/coverage_diagnostics.rs": "e2e9c3996daab47f2bd23f5d8c48b4e372af33331152f5fc85b76f06d04fcdf4",
|
||||
"crates/oa-core/tests/default_omission_admission.rs": "11ea431453c34f57e3d45b2c446a773821f0875a19d1d17ae5a6362d5f4c2c82",
|
||||
"crates/oa-core/tests/default_omission_fidelity.rs": "93cad594b124d7a0e91b1cb5c2671643e44e34fe6f31e93c1944e8fd7b89c17a",
|
||||
"crates/oa-core/tests/default_omission_output.rs": "1da3133853c31f6bfe8fa7fc47ba3050ddfdd3c2a112315ae77d16108763a7b5",
|
||||
"crates/oa-core/tests/default_omission_preservation.rs": "77c3d5087db986ab727b31373669ec27467c9906e733b31f5d3a4d563b098215",
|
||||
"crates/oa-core/tests/default_omission_updates.rs": "43fefab9149d2d1f8a325a5de20cc67a3423debe4e245348cb5e05b9ffc14b03",
|
||||
"crates/oa-core/tests/edge_attachments.rs": "32d8d96c6edaedbcc8479af2af7f78952f8aab4c2fe8b5fd5608f0031edb2eaa",
|
||||
"crates/oa-core/tests/fixtures/v2-coastal-rescue.arcadia": "f0df129c1914bfc01d0b047b0cd4b520d8cf0c249ea9ced085de400c0be87ff3",
|
||||
"crates/oa-core/tests/fixtures/v2-coastal-rescue.json": "3ed53508eb544bbd788832f7bebea2ec31b3d66191f24f45098cc9750ec4c005",
|
||||
"crates/oa-core/tests/fixtures/v3-data-contract.arcadia": "305f19d1b79f99f8d4cf627ba2e9ca6836bb7959cc0070227815937a699eafea",
|
||||
"crates/oa-core/tests/fixtures/v3-data-contract.json": "1b41299c53204e6232397d25a58d5bccdbbc16e770c75e8edb55ce36ccfe58a2",
|
||||
"crates/oa-core/tests/fixtures/v4-reviewed-prose.arcadia": "5fa53a26a5ad010c8e59eb56d9b2b52003569e66c0377ffb58840590168ad0c1",
|
||||
"crates/oa-core/tests/fixtures/v4-reviewed-prose.json": "2808088afcc165f4bd6da5129151143411c997041c3de61d600ab76e05d24a47",
|
||||
"crates/oa-core/tests/group_movement.rs": "a6c7b4c1b2137da923474f5e43faf72f24fe681fd36b4752a42fa45f92297d04",
|
||||
"crates/oa-core/tests/language.rs": "9ad8bd0d3804e75e1aefedb93895053e1ef27647c6169bc38a33a7d5950d7801",
|
||||
"crates/oa-core/tests/legacy_diagram_admission.rs": "d77fc8919cb231fe55f587413385278ae312a4e8a09a5df96fada10327d0e686",
|
||||
"crates/oa-core/tests/port_anchors.rs": "69f6fd78aa74a22f6ad40042c69f219e2e53beb83a1a6a60ca545ce86a16f9d5",
|
||||
"crates/oa-core/tests/port_migration.rs": "1801c376a5435ac7efb2a8342348b2c7ffef930bba65deca4acb66b06e0483d7",
|
||||
"crates/oa-core/tests/port_source.rs": "8d2ddaca4045d330f6eda2591561497b858aad53769a823249d0bea4f5916e12",
|
||||
"crates/oa-core/tests/prepared_commands.rs": "52cab1c668064777412df0a6a3c20d74d579b33794b4b00525b9f151c06eac9b",
|
||||
"crates/oa-core/tests/process_diagnostics.rs": "1dbbc590239ef7542be4a2bdc9543aaa64d108417b03dd312a0894e364745906",
|
||||
"crates/oa-core/tests/projection_switching.rs": "1116f56714e9ad556ac577611bd46338000d2be610c5fa1d141591d4518064aa",
|
||||
"crates/oa-core/tests/r3_constructor_ack.rs": "e5c44676ea0aab97f2e091ddac2f4bc4a4a90f5877f7bbf08b61229600944dd0",
|
||||
"crates/oa-core/tests/r3_migration.rs": "ebd04120f5215b3956fdc79ee78f6e38734c7d13392f1fc28aaba957912265bd",
|
||||
"crates/oa-core/tests/r3_semantic.rs": "49c3763eaf2131070a7c46cc23a84f917d292d41610b6404510e5607f8890c32",
|
||||
"crates/oa-core/tests/r3_source.rs": "f31f60659df3c7ef601650d1cff140589d8729f01a3f65a4a6a6c2272f15c408",
|
||||
"crates/oa-core/tests/r6_data.rs": "5f7ffb3bc73c3c85423623fe56f8680646dc97040327bdbead9d47a37e80f155",
|
||||
"crates/oa-core/tests/r6_migration.rs": "81519bc1512f5e069432e9a1cdad04c10216d492df01b4b75b02102480ebd6a1",
|
||||
"crates/oa-core/tests/r6_source.rs": "88a0aa087a7678e2898ff6057538072ca92072190712f05fff08372cc56ebed6",
|
||||
"crates/oa-core/tests/release1_teaching.rs": "be179290d871125dc97cfd2a3067c7ac89c8a538fdb0afafc5b825bcdfb2bef2",
|
||||
"crates/oa-core/tests/release_capabilities.rs": "b92cba2a562e78287ddd7dc3e94557848d17faa0c8a102601b50286b9af48a30",
|
||||
"crates/oa-core/tests/release_diagrams.rs": "cefcc0582d91266d5de6ee5daefcfe41b606ab971ab235b59034a8e1dbc24b7d",
|
||||
"crates/oa-core/tests/release_language_limits.rs": "8578da4f76bbd2d0e1bfe295ab1259e2162a3fe3d0d2ba8901915d7a40942806",
|
||||
"crates/oa-core/tests/release_ports.rs": "506e62beab0c7ec0cece83e9e0c1a810fa1f99285c9e1cab0c77f06de55a40e1",
|
||||
"crates/oa-core/tests/release_routing.rs": "11d233daf23ce084863f86dd0959ceabce9fc718c4de5dc9f69152c99b56d65a",
|
||||
"crates/oa-core/tests/release_scenarios.rs": "0f5d876badeec653fc7b12610a77d867e9a53582e605253deff8d379da21572f",
|
||||
"crates/oa-core/tests/release_states.rs": "b1a26acee95eaba7f9920f9da738a1e33606552eb078e6d298895daff7e3d811",
|
||||
"crates/oa-core/tests/release_topology.rs": "c51dbe5aa5a038f9ed830daa02aa67714472179eb7fd6da5ce36dc6931c8b3a9",
|
||||
"crates/oa-core/tests/review_regressions.rs": "f154e5c88d35e31512e2b07733a1327ea3e2fcfd1ef8eb23066a4e9573f387c1",
|
||||
"crates/oa-core/tests/route_source.rs": "1a73fdbac0d0ae0e84d8d67b6ad398e11223958f5dca007f73757c91827decfb",
|
||||
"crates/oa-core/tests/routing_geometry.rs": "d3bd79e0d8dcc2445495b56a474e52eb62be510b679ea290569cce16ea5ebc94",
|
||||
"crates/oa-core/tests/routing_labels.rs": "2d5529a8994d645627c6dd3f7e560a8fe42313c569db487ca213248852c6addd",
|
||||
"crates/oa-core/tests/semantic_artifact_absence_oracle.rs": "d93eaf9667f4221232168bef0fe9f069e5be1b1f23410bda5dd67010b96e5c8a",
|
||||
"crates/oa-core/tests/semantic_attachments_gui_oracle.rs": "7f526d363ba05e7ac89faf0fead0796e419c34ee204edf44a1b46c78308b0970",
|
||||
"crates/oa-core/tests/source6_view_contract.rs": "5108a1c5a5b1d4e0fa7eeb0e36e5c872a6f6799d006fab13478c3db1e1d37af4",
|
||||
"crates/oa-core/tests/source7_contract.rs": "0702d3bd0173def4f67770458e4af2b23f213fd5938ee70e3ae0042897ef5f1e",
|
||||
"crates/oa-core/tests/source_files.rs": "7566e2b44e19e6fd201e2460aa09a710992f6e208bdd55fb2db79e3797d9e568",
|
||||
"crates/oa-core/tests/source_identity_updates.rs": "f52aa38b4d317e532c641ad7463472e4b40e802e60052cf0017556ffc63da06c",
|
||||
"crates/oa-core/tests/source_nested_updates.rs": "9ac3390936c0d250651ea94a1b8faf0adfb680f061ececc56f2a52909b5ba855",
|
||||
"crates/oa-core/tests/source_project.rs": "cef58adb16aa4d2527ddc0628aec3e26f0b42b4be70014a7e360fafdac13a942",
|
||||
"crates/oa-core/tests/state_diagrams.rs": "71283d3aa49e5632eee58fbc25e6d5afd46a2c16afb0028127f2a02dee8f6fdd",
|
||||
"crates/oa-core/tests/transition_counterpart_compatibility.rs": "55e30c3f6a5cadf21e7da64ad9c00da636a9a8f43f3cc266b1a7e6eb7155ba9c",
|
||||
"crates/oa-core/tests/transition_lifecycle.rs": "7879e82e3e744675cb3b0664b7cae06c812990ed97ebb6b9e331df2d02261199",
|
||||
"crates/oa-core/tests/workflows.rs": "bd843dfddbd5724ec08f657571f887496203056fe2fa5864ef50a168a613b89b",
|
||||
"crates/oa-desktop/Cargo.toml": "e4fc5d2c14d9aa84ec493d0c6bef47a1046b4929ca502e4bd096c27d4fe22587",
|
||||
"crates/oa-desktop/build.rs": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9",
|
||||
"crates/oa-desktop/examples/allocation_policy_probe.rs": "45a6ac17360d0f10749d96824ec359a3b97832065641e4458a3e7e3ef1f09395",
|
||||
"crates/oa-desktop/src/allocation_drag.rs": "19b0b893f96fa9fb79bc8db7b838b0f8fc50e3c8d24295fa5beccb552b680d09",
|
||||
"crates/oa-desktop/src/allocation_drag/arrange.rs": "1614fadf191d2e6fe38b487f738ed65e8740f112b3a91bfef1a56310c817b977",
|
||||
"crates/oa-desktop/src/allocation_drag/arrange_tests.rs": "33f2e978872bda0b24baef3f10e63127ff480c05745a4d6ac7d1d38b1795ad6f",
|
||||
"crates/oa-desktop/src/allocation_drag/assignment.rs": "ec68353960e2428879354365fbbd87f9f15a62b5595eec50e689a6e63309a169",
|
||||
"crates/oa-desktop/src/allocation_drag/assignment_boundary_tests.rs": "320efdd81fd665cd5bb86d27c8c3c146d4a1aed82df1b308c3a85dd6745b473e",
|
||||
"crates/oa-desktop/src/allocation_drag/assignment_tests.rs": "4d11856beb7513e253f3d9806a6b9316f40feb4d723c6da767718f0bbe2f9b8e",
|
||||
"crates/oa-desktop/src/allocation_drag/geometry.rs": "43e168551766194f57589697757405e9e7a6d9dd8147a99d1fa9587abe23e95d",
|
||||
"crates/oa-desktop/src/allocation_drag/group_arrange_experiment.rs": "2d3b728fd8e553ad07a6bf7dbe94ef7a8ae2f8d4d5f89ebe3045e4220906e70b",
|
||||
"crates/oa-desktop/src/allocation_drag/tests.rs": "c9dadf7a5d7bca32a4c4ab07d855e6083107cba8b0c05b3cb6bb0c4737ba904b",
|
||||
"crates/oa-desktop/src/allocation_input.rs": "7ceb868b158e4384b217b2b2b118c3714fc6d851fa2c89c2a61236cae125ffda",
|
||||
"crates/oa-desktop/src/app.rs": "9e1a761f67fe54443dec90877d5e02c13f092b6446c4ac31c70fb320aee7f1d0",
|
||||
"crates/oa-desktop/src/app/allocation_drag.rs": "26934501d8a95a6d196bdf477eed00c68545c98921744b181613c0b1f9d95c36",
|
||||
"crates/oa-desktop/src/app/allocation_drag_full_tree_tests.rs": "36105ebc9c3410c87d7981aa3bd1e27edb4b2f51f25043912041ce469b223a8f",
|
||||
"crates/oa-desktop/src/app/allocation_drag_tests.rs": "4428ceb7dd74597fc96b644a31c8224cbe3fcf0fad1c77bed8738129ca5fe50c",
|
||||
"crates/oa-desktop/src/app/allocation_drag_tests/control_focus.rs": "1f06509824d190585f55947452d1e0813f4bd1e4120d4c4185b4f768abf2e5ac",
|
||||
"crates/oa-desktop/src/app/allocation_drag_tests/group.rs": "6e160cfd1d966376ba5459cb00a545f608c426c16692e603bb6cd4ccefce29a0",
|
||||
"crates/oa-desktop/src/app/allocation_drag_tests/group/connection_lifetime.rs": "d5242e2c530e433649a061af6f0cf09e0532cf3922705b509c3a159b04715cf4",
|
||||
"crates/oa-desktop/src/app/allocation_drag_tests/group/route_lifetime.rs": "7110c79b3fe8c2679e0ec6bd59bbe307399359e89d2d9d6120c546de756a580b",
|
||||
"crates/oa-desktop/src/app/allocation_drag_tests/navigation_union.rs": "9962cd4e11efb586b55c9a5f78b9df26c4316b6df3a0b01fd3587ee89e4c5900",
|
||||
"crates/oa-desktop/src/app/architecture_authoring.rs": "509b15f35a27fc8d761e4b0097131725654433e4b2db50f2a2ee7022bf7cfd8a",
|
||||
"crates/oa-desktop/src/app/architecture_authoring/palette.rs": "1d04ccaf27ed6dfe55aa180323f7b387accd85f72782d9be54aead49edf1c540",
|
||||
"crates/oa-desktop/src/app/architecture_authoring/tests.rs": "3ac4656fc97011924ba94f1b0e1b643f2beb68e666b5137e76f3b15c3ad490bf",
|
||||
"crates/oa-desktop/src/app/architecture_authoring/tests/traceability.rs": "fed26199475a27d22807db7dfa1fe7b86fe78447ccaf262367247470b9415b20",
|
||||
"crates/oa-desktop/src/app/architecture_authoring_tests.rs": "8f18b6f30cd1acc1520eac98b423eae53234b6719ca7da1f139ae97b2180cf94",
|
||||
"crates/oa-desktop/src/app/behavior_lifecycle_tests.rs": "f9a0b030faca5deaf3f55d9f5efcafdf1a7cb1a663001aa20a61baba4f92d080",
|
||||
"crates/oa-desktop/src/app/behavior_removal_tests.rs": "47f6ddd8da27854ebb450f24c810f26f68be64687ba6068a0ab34ef5c3dc1922",
|
||||
"crates/oa-desktop/src/app/canvas_selection_tests.rs": "047cb9d8de71be78438320ceb1c1a9605b67731a8d7fa062d82dfcb610f5e324",
|
||||
"crates/oa-desktop/src/app/checkpoints.rs": "55e5babdcabef767af3bc8be9cc3153b3f5cc0695701b890820ec0225643852e",
|
||||
"crates/oa-desktop/src/app/diagram_focus_tests.rs": "106b6d530c30fdef877559a8dbb4ce9cb37c9fe4d208c5dd0aaaa8024e3b1857",
|
||||
"crates/oa-desktop/src/app/diagram_removal.rs": "addb51604f41d680b06586bb362362ee3c2603a334f33022ce9abc281295ac85",
|
||||
"crates/oa-desktop/src/app/diagram_removal_tests.rs": "3062ce2ad0498c6691a5bdf46bd7ec69781d964df2fa33db7bb403ccbb56a8f3",
|
||||
"crates/oa-desktop/src/app/diagram_removal_tests/navigation_union.rs": "7396e60a11d3ef2147111e56d0cab107685032be30c7a22143a1dadae7548101",
|
||||
"crates/oa-desktop/src/app/diagram_reveal.rs": "50f9e456646a84946e7c311c04cad2c330be544a7692ed412c6b23a8f18e4de5",
|
||||
"crates/oa-desktop/src/app/diagram_reveal/tests.rs": "0fa6781388006a04e78958f72d9efd3cc7a52c74d1648cf9179c459a6d91799e",
|
||||
"crates/oa-desktop/src/app/diagram_selection_tests.rs": "4fa507adbc23db3a6eb202d809c695ec0ebd6181356930c4fa7528a9a0c7be57",
|
||||
"crates/oa-desktop/src/app/diagram_usages.rs": "e838fea551758d2a6f307bef0ce6b6912c57b1b712acc3d742cc1bb8b29a2f20",
|
||||
"crates/oa-desktop/src/app/diagram_usages/tests.rs": "949dd6659f75c02aacbc6acc2d51ac88dc5e3108fd217f0e7b452c7f66c97473",
|
||||
"crates/oa-desktop/src/app/diagram_usages/tests/saved_scenario.arcadia": "74cd2c1dd5eed7eb19895a752bb8ff9ce93655f7638a5312c96f56218568ebb3",
|
||||
"crates/oa-desktop/src/app/diagram_usages/tests/saved_scenario.rs": "98d3ee39c970a32559e643602ee36394a8f86eed3e3bfe504e005eec8eff4bb7",
|
||||
"crates/oa-desktop/src/app/escape.rs": "da745f11513c461487e82bbd1ef48b6bc1f311133885407d8a90241ed95a4f36",
|
||||
"crates/oa-desktop/src/app/escape/tests.rs": "73d1fbbb76f0ff8d8e2d4b5e522e592c8403f3a5c826f32a5daa2af4b1b1f3ae",
|
||||
"crates/oa-desktop/src/app/hosted.rs": "08346c39a094f38d58a2a2f33ff8032608bbe4ad862a35e7fa5a39ecfbbe0f4c",
|
||||
"crates/oa-desktop/src/app/hosted/tests.rs": "fa118f64d04b400ccc01e4e67adaf3541975288882afa265a9e7f092cdbda232",
|
||||
"crates/oa-desktop/src/app/multiselect.rs": "a80d2464fd0c96d2f670e115845b1502688f1c021728abb85d026352975c9121",
|
||||
"crates/oa-desktop/src/app/multiselect_tests.rs": "09eea1403af1e4804dcb3ab863ad110095f451d9b1957dcdbf859fe7dd833abd",
|
||||
"crates/oa-desktop/src/app/process_canvas.rs": "d20cc74f5abd12a6f9683b2df1559e0db71946a61769cafeef8e155f2407e811",
|
||||
"crates/oa-desktop/src/app/process_multiselect.rs": "a6aa5fff173de8db22bf4fb9570603be408e5429bd7ac4366cf9671abb3a8f14",
|
||||
"crates/oa-desktop/src/app/process_multiselect/tests.rs": "22ead59676c53877d58339e7fe31ba548231610f8c12dcb1f07ef550e0680d90",
|
||||
"crates/oa-desktop/src/app/reference_authoring.rs": "38e033e23e8135032f70117a376a5fe7bc68127c466559a35d7ec200932e3f77",
|
||||
"crates/oa-desktop/src/app/reference_authoring/tests.rs": "b96ddc6bff7f8fde97720e18a92070f891c1340348d2e19c7c9c5a7bda650ce8",
|
||||
"crates/oa-desktop/src/app/reference_authoring/tests/mounted.rs": "14a4914a1c86e50c395672dab8fe80d7af679a79c9082890b509fad7b7c543d9",
|
||||
"crates/oa-desktop/src/app/repair_save_state_tests.rs": "5543dcba853bb1513acf824b860249729a6004747e7e320b46efe60ea19e2d83",
|
||||
"crates/oa-desktop/src/app/review_navigation_tests.rs": "872fa91479dad5436e139ffcaece270323e9f5daeeb7a4d80c2c2534f48bc21b",
|
||||
"crates/oa-desktop/src/app/save_as_tests.rs": "f8b915793f1a36fd152569c4781a4bdc17f4af404a8ceb373330053413087a1c",
|
||||
"crates/oa-desktop/src/app/scenario_details_tests.rs": "c2dc36d3b602671bf1a01046373e834f938250471b17ebc3f383e78a5a72b565",
|
||||
"crates/oa-desktop/src/app/scenario_diagram.rs": "81c9ac425dea73a43b8d4ba44743581e2bbf94353b5c4cc50e55623006f428df",
|
||||
"crates/oa-desktop/src/app/scenario_diagram/adoption_tests.rs": "93f8a4b712036ba69554b485fae2d9fa10a56f9f2419cd978917d03c83694954",
|
||||
"crates/oa-desktop/src/app/scenario_diagram/owner_navigation_tests.rs": "d40a0e9f9779c2cf3ac2727106bcb991350d4a81db0e7c2cf51b0218270d6a86",
|
||||
"crates/oa-desktop/src/app/scenario_diagram/tests.rs": "101dcc6e44f9313e2ef4be5f1a79432f4f4fe110a1ee27bd0d3a41805db70303",
|
||||
"crates/oa-desktop/src/app/scenario_spacing.rs": "6378d55f463ccf27aafb60a2b011266bf2776009062f0cb03861dd0a84edb5eb",
|
||||
"crates/oa-desktop/src/app/scenario_spacing/tests.rs": "36de676badb9bf04515dc54e8d3437963caa6fc982422a19cf41bf3fb94f1255",
|
||||
"crates/oa-desktop/src/app/source_indentation_tests.rs": "0b021a283fa897ff165aedbd4ea497dd80f4345cf9af5da8a87b6eee03eb8e9c",
|
||||
"crates/oa-desktop/src/app/source_rename.rs": "724fa01aa490d468742b0b85ebb05f2074e57d1992cbf8855575d2f2be83bfff",
|
||||
"crates/oa-desktop/src/app/source_rename/alias_input.rs": "5888b50e4d6fabd8cd0c6d70986a791d8e95f190453ea37ebb251f2fa19cda90",
|
||||
"crates/oa-desktop/src/app/source_rename/alias_input/tests.rs": "f9dd619c440a6d784792be5e41bad562ef312e07967d193b0e96cdc127debfb2",
|
||||
"crates/oa-desktop/src/app/source_rename/editor_patch.rs": "049c9774c1ca28cda01f9a8e024604bef282366aa52c53c17c1918dbd09c3441",
|
||||
"crates/oa-desktop/src/app/source_rename/navigation_union_tests.rs": "1c1152214abfef70841e9580d21178e1fa40a984f3d6c97afb71a1adea1eadef",
|
||||
"crates/oa-desktop/src/app/source_rename/navigation_union_tests/palette_union.rs": "14cc9fc555243de0e8c97b6b662d69f569e7c58137c11108cc0388e9842f3ba5",
|
||||
"crates/oa-desktop/src/app/source_rename/preview_glyph_tests.rs": "d45a799bd97047c6b77ff5b18334f2a71912b65d5239f2b8ce7017588ccff0f4",
|
||||
"crates/oa-desktop/src/app/source_rename/preview_scroll_tests.rs": "bdd9580bdb3cf2c8f41b92e5d68a79faaa99c0a5a9d72490d4aef07513fd5e13",
|
||||
"crates/oa-desktop/src/app/source_rename/tests.rs": "2d0def9d54473d09a3309be16638ffcfd7ec15e35bd4cb9b68ff30c1f1c7e35c",
|
||||
"crates/oa-desktop/src/app/source_rename_layout_timing_tests.rs": "dadf8ce0ec989b9a370703a330bde4b415a813273917037fc6a732b9bf2f98ce",
|
||||
"crates/oa-desktop/src/app/source_rename_timing_tests.rs": "942753946d00cab9d4a19717bec5f429af58c070e2cd1b97cb4d32ed6fb93967",
|
||||
"crates/oa-desktop/src/app/source_symbols.rs": "22a3facfabe3d76c0491bb2ca6a53ad3074ebc113d8837c8ad71637fd7e16a07",
|
||||
"crates/oa-desktop/src/app/source_symbols/focus_selection_tests.rs": "a7de38f793d31a7a022b18ce74aa10f5decdf537fea8d9d61e5e61a6bb4f125b",
|
||||
"crates/oa-desktop/src/app/source_symbols/tests.rs": "a9154d49f36c8102acd1c4d2c6d35c10821be9e1c36d12f23aaf0bcb4eaaae18",
|
||||
"crates/oa-desktop/src/app/source_symbols/tests/departure_selection.rs": "81c8ab94a7dc388d5864bff2fe744df443b49a0915bc154fc8f77980440b0825",
|
||||
"crates/oa-desktop/src/app/state_attachment_escape_tests.rs": "b2ddaa3a1f4e4beed2354505a297d58c201da18a07ad01109c3aea27270f166c",
|
||||
"crates/oa-desktop/src/app/state_bend_tests.rs": "d3a95fa19ddb0ed60347bb682721efef63db6bb93aa2814f54d54a97267f6b3b",
|
||||
"crates/oa-desktop/src/app/state_diagram.rs": "0e55b99a4e2e3558f55df7c013ee14f138d99779a8afa82503dffa026f783110",
|
||||
"crates/oa-desktop/src/app/state_diagram_tests.rs": "1733e159fb4f4e6217b3a1d371fc53dfa2ae10b48285553000fd1db19f8ac940",
|
||||
"crates/oa-desktop/src/app/state_multiselect_focus_tests.rs": "a094994cf77f698f69895226dc62d2aecf1be6f722fdfae2ae96194b9a015558",
|
||||
"crates/oa-desktop/src/app/state_multiselect_tests.rs": "e08e65e6be3161c63e666b942d5b65d4d9c0536d59fb0df508d35e11eccdc76f",
|
||||
"crates/oa-desktop/src/app/state_picker_escape_tests.rs": "ac757d2428a536e24d08f5d0caaec03af41dfd96c726524464f2dfd27e36c1a3",
|
||||
"crates/oa-desktop/src/app/text_workspace.rs": "cb8d351796893b047cb28e3f124afb2424370bc8890722e30429964c194bdfee",
|
||||
"crates/oa-desktop/src/app/text_workspace/lifecycle_tests.rs": "077cb8592297ef04c506abf8a97773cee142078def0ea5428fb859356c7b7206",
|
||||
"crates/oa-desktop/src/app/text_workspace/tests.rs": "ec35fe120e5bd55b9c575f084231669dc9749bdad7b4716bdb1e4e55d9a2be61",
|
||||
"crates/oa-desktop/src/app/traceability_navigation_tests.rs": "c5d867a0d2a8628734563dad931dd91d2add34dd861f91924f0f3c5a587e39a5",
|
||||
"crates/oa-desktop/src/app/transition_context_tests.rs": "cc9a98efa2d4d72bf93c352b069dd739b98c5b8c505e2ead1cc4e9a50cee179d",
|
||||
"crates/oa-desktop/src/app/view_copy_tests.rs": "296821c51dcde568e7d717039aa26bfc1b56a9074aa16051007bb351b33a4d44",
|
||||
"crates/oa-desktop/src/behavior.rs": "1d8a0d8edf3c22f4055d1d62756e71a9d36488839b79973fcb3ee85b117fc604",
|
||||
"crates/oa-desktop/src/behavior/canvas_workspace.rs": "8612528ba287ecc70ea50062c8db27f98b0fd2ebcd20a681302b4c1d1c586901",
|
||||
"crates/oa-desktop/src/behavior/canvas_workspace/control.rs": "2b8d97b8c0e3ff1b1a2c32187b1d0eb25edf39ba582b5fb3303bc880f674f7ba",
|
||||
"crates/oa-desktop/src/behavior/canvas_workspace/focus.rs": "33447003980847033e40550f9926d8617fa61e2c3e73ec9c6121d1afed946c33",
|
||||
"crates/oa-desktop/src/behavior/canvas_workspace/tests.rs": "1c2493113542e8760d894cf9ff8959591886c922964c366359c899a1f13b68f2",
|
||||
"crates/oa-desktop/src/behavior/canvas_workspace/viewport.rs": "d31f2d3f48c15c203d46d77dc53f93bc90ec082f36cdf862c6abfda0b9072039",
|
||||
"crates/oa-desktop/src/behavior/contextual_source_tests.rs": "09debbd1f2980290e476a1dc3700a38033a2f498d606ccdca638e0fd0fe604f5",
|
||||
"crates/oa-desktop/src/behavior/creation.rs": "5e2babc4afee003058fa8579b2ef9e36236cda29d2fd2df0e4f14162d4bc279d",
|
||||
"crates/oa-desktop/src/behavior/creation/app_tests.rs": "35540d6c21cb0aaa12f88d99012c890e6be3bbf48bb1068fb33b520979de3da8",
|
||||
"crates/oa-desktop/src/behavior/creation/tests.rs": "a0d1daf9fb3920f7e6181a9802d84fbe056cbf0043b9e41dba5fdda687d9818a",
|
||||
"crates/oa-desktop/src/behavior/form_session.rs": "f5027fb3a5a5599a182e7d1a10dba56dd66eb1f23a4ba4c4a1744c9a6a5b6e61",
|
||||
"crates/oa-desktop/src/behavior/scenario.rs": "23362d99418648c276f4d99df94e26e80369808db2ef7cafa739489a72f4bba0",
|
||||
"crates/oa-desktop/src/behavior/scenario/camera.rs": "150d2ae0b77a3965e3c01f0c8b798922059bad253370aeffe8df272006769e7e",
|
||||
"crates/oa-desktop/src/behavior/scenario/focus.rs": "78114adb8558ca79f2b43cc92474fb9ce5e75d3ea2026fa72699ba50b742e877",
|
||||
"crates/oa-desktop/src/behavior/scenario/presentation_tests.rs": "e5552ba65c253580b1e5f18ba8ba77fb5aa188e009aecbd5b7406f06db3b2455",
|
||||
"crates/oa-desktop/src/behavior/scenario/spacing.rs": "cb110cd7864b9cba0415de99dee50e843da6580977d772726a8e85357c4034c3",
|
||||
"crates/oa-desktop/src/behavior/scenario/spacing/controller.rs": "03e5ff299ce33bdcd279db0f12d0fe89a18618dd1a145dac466a25c54df3d365",
|
||||
"crates/oa-desktop/src/behavior/scenario/spacing/controller/tests.rs": "7659d2c2555072868f057c9c0ee41f9d1afc50c216821dd5945fb8f68b83f164",
|
||||
"crates/oa-desktop/src/behavior/scenario/spacing/controller/timing_tests.rs": "ad2e2fab53e712a7bf93cd951f6d95019199675e2f55223b01e02d8344ee3a2e",
|
||||
"crates/oa-desktop/src/behavior/scenario/spacing/tests.rs": "7b8c4b6defe32edb248c4b42cf7bb5e1d56c3686019a0f9d51c5fda592f738c2",
|
||||
"crates/oa-desktop/src/behavior/scenario/usage.rs": "1a0ac9837e0def7f6fe4f386b8ba46cafec9cc521a82f0a09e0bcf7bb57efee9",
|
||||
"crates/oa-desktop/src/behavior/scenario/usage/tests.rs": "67d0e36d8de6d8a41191bbf82c7b708da330c9e0b6263c9bea285e976c0118c3",
|
||||
"crates/oa-desktop/src/behavior/scenario/workspace.rs": "7296a764cc00f86d42be5b321396af0428ed2472aaf3c914cd7208e0361240ad",
|
||||
"crates/oa-desktop/src/behavior/scenario/workspace/details_tests.rs": "a758ad747ff1e7277128af703113603e475ccd3f853a40182354dc0b144f6310",
|
||||
"crates/oa-desktop/src/behavior/scenario/workspace/spacing_tests.rs": "59c1986abd2ed6f77e98015cc802a88e0265961458cd002b13bd933e641c7858",
|
||||
"crates/oa-desktop/src/behavior/scenario/workspace/tests.rs": "a9c10f1f99e8e6224dc5d6d146a2eecab29f07dfb4da2988bea8120b603a0f7a",
|
||||
"crates/oa-desktop/src/behavior/scenario_activation.rs": "6dfc8eccf7245954319f2bec84072330a652bf208cc636c7043781d49dcd1ada",
|
||||
"crates/oa-desktop/src/behavior/scenario_activation/tests.rs": "8b855dc067f1b15aed79c7eddf52c89001a8b961d9de4887f235c03b46264a1b",
|
||||
"crates/oa-desktop/src/behavior/scenario_hit_tests.rs": "8ff68105b37b922d82f1aa39b729a4e7a19ea2793b0fa57f649a96a903986148",
|
||||
"crates/oa-desktop/src/behavior/scenario_label.rs": "621f7e50372d0162f0643219a41f5e374fc9cf28a3a42aee6b8728e5271ec6c4",
|
||||
"crates/oa-desktop/src/behavior/scenario_label/corridor.rs": "2da0edfaaa0410056a0c069ba5cca149b5545414dc1c229deb4df3e92a300379",
|
||||
"crates/oa-desktop/src/behavior/scenario_label/tests.rs": "cdd5510ff45645a9f82272d8868d1862111ebcd054577242e06103443658165b",
|
||||
"crates/oa-desktop/src/behavior/scenario_layout.rs": "d5e2c25c1565cb18fd29e84d0aa33970459cb9df896e578b5598d995c20c49b2",
|
||||
"crates/oa-desktop/src/behavior/scenario_layout/presentation.rs": "ff2143e43bc4a556d985acb66ebff00b6e62dbeeef3974bd116dbf59aa21a29b",
|
||||
"crates/oa-desktop/src/behavior/scenario_layout/presentation/tests.rs": "92539225530d963385f0a4036cdcc49824bdd8016d3da866977b98ac568b0a20",
|
||||
"crates/oa-desktop/src/behavior/scenario_layout/tests.rs": "5d2b2ab78012693000c5fe68f5bec211ccb424307c6d3868a780ec1c5a40719a",
|
||||
"crates/oa-desktop/src/behavior/scenario_lifecycle.rs": "c94f3738e4bf027e3f055e32d709b90777d8fb2dea70b563713af8df000af13a",
|
||||
"crates/oa-desktop/src/behavior/scenario_lifecycle/render.rs": "ebebc4eea70f5c063385e295db815865604a6f84fe1a461ab1c113fdd290ebcc",
|
||||
"crates/oa-desktop/src/behavior/scenario_lifecycle/render_tests.rs": "e0274c2aeb0463e0633f6cf7c4ce3b1a0f6f95b4084a1c1fb6cb6053288b2f1d",
|
||||
"crates/oa-desktop/src/behavior/scenario_lifecycle/tests.rs": "01b576993b329810cd78a859b45ac52c66f5a14549065661dd1308196e0b724d",
|
||||
"crates/oa-desktop/src/behavior/scenario_lifecycle/union_tests.rs": "0e0a1a03641322c6c4d55ecf1561649e8886f8a217333231ce0f61d21978492b",
|
||||
"crates/oa-desktop/src/behavior/scenario_operation_tests.rs": "89fb71c690e07eac314f7a5e05862d5c1e2f9c21039673bf22c4ac894d21c977",
|
||||
"crates/oa-desktop/src/behavior/source_draft.rs": "08190823090bd8b0305809099abadcc0de431175f7044f1d5abdceda52612ee1",
|
||||
"crates/oa-desktop/src/behavior/state_bend_tests.rs": "476d1493754b45477d7dfabd49e38568772d9263fd6880f263b5b10ef6744639",
|
||||
"crates/oa-desktop/src/behavior/state_canvas.rs": "9ccf0682bf1b97db408662a750d9c29b627eb7dc51cb61056e11ceeac62baf88",
|
||||
"crates/oa-desktop/src/behavior/state_edit.rs": "99613f46d12ab370bbcd5477a3b756fb5c7a7fea70c6db88035ac1701eb3cd33",
|
||||
"crates/oa-desktop/src/behavior/state_menu.rs": "87f2a534ef2651b8f527af832913894875f457d87a07d45df09c6cabe14783d4",
|
||||
"crates/oa-desktop/src/behavior/state_multiselect_canvas_tests.rs": "15e0848e86ce99a31fd670ef29600184934279ff2350801959fc4dc5a69d6bfc",
|
||||
"crates/oa-desktop/src/behavior/state_view.rs": "adcbe4800d0428b9e610e6cd3a97c22d64e1095ff649b96cc83ca73dc6727b6d",
|
||||
"crates/oa-desktop/src/behavior/states.rs": "8986ed9492960e20f1254e9dfe3ff91d79ce9fef66f3bda738f9933278043954",
|
||||
"crates/oa-desktop/src/behavior/topology.rs": "70aac7088b4b4468e00a943abbc693705a500c4bfbff11dc7733d2bafad8acc4",
|
||||
"crates/oa-desktop/src/behavior/topology/contextual.rs": "5d0ac5b3e258fc70987a00ede1e603681d99746596f2fcc46ad2b3667d65203a",
|
||||
"crates/oa-desktop/src/behavior/topology/contextual_routing.rs": "d914b0e82aa8d253fe64be2ef74443d1fd2e6cec26d1cf30620a57646e1d426c",
|
||||
"crates/oa-desktop/src/behavior/topology/direct.rs": "cd26aa0806521fda6fe5c8a1ae4185c07745d5ba65e74197b5be781bc6b86a22",
|
||||
"crates/oa-desktop/src/behavior/topology/direct_selection.rs": "2da5731396fdf04ffe6687925da79b56b1950936d034f54a272cfaa8d01e520d",
|
||||
"crates/oa-desktop/src/behavior/topology/direct_selection_tests.rs": "494e88246eb62c4c359d3ee5a6f626a54ede27226f42ad8a5846135971db28e2",
|
||||
"crates/oa-desktop/src/behavior/topology/direct_tests.rs": "d253848d561402573256239b991811f8e6debc10216f405315cb21a74a7b0026",
|
||||
"crates/oa-desktop/src/behavior/topology/group_reorder.rs": "6160b530541c3d5a5d8ec0586a40e8485a971fdc36878d658ca4db67eb1fd792",
|
||||
"crates/oa-desktop/src/behavior/topology/group_reorder/tests.rs": "2af01543cdc34415c284e498886b1248ffc2aaecd7cc0863f143d7bf1faf7255",
|
||||
"crates/oa-desktop/src/behavior/topology/layout_tests.rs": "993d700d6590757fb329cab3029dd0202e71c2c2f852e13fe1228417b01d8fd9",
|
||||
"crates/oa-desktop/src/behavior/topology/usages.rs": "3c2f5c8cfcbc4d7e8c95986efe1ba7032fffb7c26b86765e8d8e0f6e12914d46",
|
||||
"crates/oa-desktop/src/behavior/topology/usages/tests.rs": "61aebacefc4c5f4d7326c59390823abe56b6ae74f6678be60b5889cca59e75bd",
|
||||
"crates/oa-desktop/src/behavior/topology/view.rs": "fcf6dd9ca32c9bc3939d23564583ceef2cd2c9a3779751e07ddc315c6bcd88ef",
|
||||
"crates/oa-desktop/src/connection_allocation_tests.rs": "33e8b7d238240f40be0fe64e796f8af4d8d3e6945315e07b70bc553fe45ea15b",
|
||||
"crates/oa-desktop/src/connection_edit.rs": "218ec5ad41e13cd327456fd30e195037a2c4ca6ecd7caa0d62146ce150433bb9",
|
||||
"crates/oa-desktop/src/connection_tool.rs": "43cb9fb4c0da31a28c2a59ef8bbf5a66989b4ab58cb532a237e9595b34ebe5eb",
|
||||
"crates/oa-desktop/src/context_menu.rs": "03990ad926c6221d091b07e496b0614f264c9785f1489896be16e3711570ca55",
|
||||
"crates/oa-desktop/src/data_values.rs": "e1deb1cad551f0a9fdef9653aad257cfb36e6d9c0f93b6245061ec3fab8fd365",
|
||||
"crates/oa-desktop/src/data_view.rs": "158b852be5d3db450aa5cda8b68a35bd96bddce1d8d053a7a23582095c06362d",
|
||||
"crates/oa-desktop/src/diagnostic_input.rs": "7083fbd65cf0b55ef0a71979eadaea8b298fe5770b9b0738fb8aa1edc06d0a98",
|
||||
"crates/oa-desktop/src/diagnostic_input/tests.rs": "1b1544054a214625df67049bd6a321575fcea220f03d70b75c7b5fde635676db",
|
||||
"crates/oa-desktop/src/diagram.rs": "542d2acaf2cc9bef6357f0da15618645c2389470e80406a153dbf351818e9aae",
|
||||
"crates/oa-desktop/src/diagram/allocation_group_tests.rs": "855e974f8903785167f0613050c2b3f3790a2fbd51bd8cdc04d66ee4fef1ae80",
|
||||
"crates/oa-desktop/src/diagram/attachment_tests.rs": "f1ac82893330983075069a9161e98505604e406bbdacd5d594b5f7f9e9a2a2d6",
|
||||
"crates/oa-desktop/src/diagram/focus.rs": "a5c37af824aa5cebaad5984bc61e3347e4707f38b9441633014fd543c67e3965",
|
||||
"crates/oa-desktop/src/diagram/group_gesture.rs": "31c5fc34ebc22a50b9794718da01f4ce016ec29757be1b5473b21f746462f8e9",
|
||||
"crates/oa-desktop/src/diagram/lifecycle_tests.rs": "968f0e1c39e00432fc4bb255fb9dece2093b1d5280a558814f1c31e4022fb4e2",
|
||||
"crates/oa-desktop/src/diagram/multiselect.rs": "8cb5d7866fa092ecbcf7355ffc41bd060666d271fccd0bdafea68ae442cde78f",
|
||||
"crates/oa-desktop/src/diagram/multiselect_tests.rs": "ef2d2b1719e3bb9aa378e0370af9cebcbe2b79e088cedf562b911c13f5d4135a",
|
||||
"crates/oa-desktop/src/diagram/pointer_tests.rs": "51403daacf248f4faff365e4fdc5c3faedf1221fcab8cf4e162c88f814959fd9",
|
||||
"crates/oa-desktop/src/diagram/port_hit_tests.rs": "b21c2110852b9532e6333d4ce0b7336515ea355bd8d4cc7bc5e483b9e43b8039",
|
||||
"crates/oa-desktop/src/diagram/port_labels.rs": "684b16219188e1123566d6d2de6715b7ea2693371ce5102a5ccce9f90bcbecb9",
|
||||
"crates/oa-desktop/src/diagram/view_options_tests.rs": "9d43f4dc3fdbadc90e5da3a6a9b63c7e075845b7d30f63ebe91d0c92629f3bfa",
|
||||
"crates/oa-desktop/src/diagram/work_drag.rs": "d3fbc2e63ce4c003cb597288bd97c909339031396bcda00bd0cf3298b8a65d1f",
|
||||
"crates/oa-desktop/src/diagram_selection.rs": "a105f2877f2cb5d0bbcec6fbbd770178347895ee3a2b281a1ed93ad4b1c7d4b8",
|
||||
"crates/oa-desktop/src/diagram_usage.rs": "ba104de48ce06e49de62cc8cc6896d80790657c39a3a0d867b73d7178ec61302",
|
||||
"crates/oa-desktop/src/diagram_usage/coverage_tests.rs": "7b665bafd98f13cc2c0e3f608763929a732da0bfb60f3287b66cd5be9c69adc3",
|
||||
"crates/oa-desktop/src/diagram_usage/tests.rs": "e35c8a60a566a33fee829161b77fb60ab0ee36e258bc78a2833725a152b4fa26",
|
||||
"crates/oa-desktop/src/diagram_usage/view.rs": "4cfd71f476dcf4e4ede36937e32b652b634e87fbf91c3fe02bfe5b2f1996544e",
|
||||
"crates/oa-desktop/src/document.rs": "e87d7ce456bcab1dab55249021eaa8056f41b888723d143428486e31cb9ca4b1",
|
||||
"crates/oa-desktop/src/document_alias_rename.rs": "ce64d723a51a834699ebcf74d89d406a77b6ccc5419bcd3f63cf0e27e9fd25cb",
|
||||
"crates/oa-desktop/src/document_alias_rename_tests.rs": "847dca1e50970d8a3d75c7e881754453e801f4f3d34f03a88410a334899783bc",
|
||||
"crates/oa-desktop/src/document_save_as_tests.rs": "83dc243a25f4e97674ff0d86c794a490cc923e98f1f9fb05bb038f622f11e20b",
|
||||
"crates/oa-desktop/src/explorer.rs": "fa01c59219564ba9ae5553abc2b6f94e9849f9148d5812ef99b40318fe5dcf9a",
|
||||
"crates/oa-desktop/src/explorer_view.rs": "a720d7484bcc3a24729332a00e005d1cc6f5f945a9cc3965134e0024dffb581f",
|
||||
"crates/oa-desktop/src/forms.rs": "7722645933a95c4a6b5c81a2752dda4270ff39d448f2362c51bd143c6585e706",
|
||||
"crates/oa-desktop/src/forms/context_fields.rs": "eaacd474f6500dd344d5a8b63ccb1d1dd86fe2aea78ab708afa8a80d62ce69bf",
|
||||
"crates/oa-desktop/src/forms/data_fields.rs": "a540a0d451bff56a96b1d28643b2e8c0649f3846350c25c5b154729c0538e77c",
|
||||
"crates/oa-desktop/src/forms/history_fields.rs": "e7100474cde18bdfe95562bcb3c055f29b4b15aa64788a578194464aebcd046e",
|
||||
"crates/oa-desktop/src/forms/history_fields/tests.rs": "16d3c308138c063f45986d53c621ad83c7dc2ce853edd31b7204e2939a382df9",
|
||||
"crates/oa-desktop/src/forms/port_fields.rs": "3fe684d651d5be6203fd9db3979c772913e1af5ac1514e67be386ffd88dc8127",
|
||||
"crates/oa-desktop/src/forms/reference_chooser.rs": "b60af363be333af048859e51c28e8c989cb5c95d696cad16c8eec15f78d57a12",
|
||||
"crates/oa-desktop/src/forms/reference_chooser/adapter.rs": "87ab4bc3bf27fd4b5a458791cf62c7ba8e44e9ebf846d8b851b8b6ffb454cd64",
|
||||
"crates/oa-desktop/src/forms/reference_chooser/tests.rs": "8e98f8a3fcb814d4a201cc85b3db87a689fd3d2ce487f8a2fe312fc296f93377",
|
||||
"crates/oa-desktop/src/forms/reference_chooser/trigger.rs": "5c180c18775186eae997814e9c0050813edb202c535cee665e046d40aadb52d7",
|
||||
"crates/oa-desktop/src/forms/reference_chooser/trigger/focus.rs": "f1e79f4b03b2f9fd1cdc335054d4ae177dc9e759b6ea3d334b2bcca39b57191b",
|
||||
"crates/oa-desktop/src/forms/reference_chooser/widget.rs": "57f21b2e438e7fc89a0a33412f8e43d9b3f7e59042c48a025bbbebe3d59467c8",
|
||||
"crates/oa-desktop/src/forms/view_fields.rs": "5184d438f0dfd4e29f284ff048e19da73e71be1f24a6e9f123c7e922aecb85fa",
|
||||
"crates/oa-desktop/src/guide.rs": "31d9aad51eeb6497228599f6024568bbf886eb99401073e3d1c7b43d83971f52",
|
||||
"crates/oa-desktop/src/highlight.rs": "8222e2deed5d60ea522241ccb2c653caf50ecc1d4404d633f54e0193c5f622e9",
|
||||
"crates/oa-desktop/src/home.rs": "f03c1071334894f586a5a7c86026602a694db3c2faf902822e1dbe5156a859c2",
|
||||
"crates/oa-desktop/src/home/tests.rs": "3b03ccfc97dfb75954e1e0ed568bb87a632295c07b96cedc05ed381f87b378f3",
|
||||
"crates/oa-desktop/src/hosted/client.rs": "54b24b4b50f8d87d7c92946ea5a2cff4c98760e70555b3ce3d90be27115e95d4",
|
||||
"crates/oa-desktop/src/hosted/client_tests.rs": "3ef70907ca75e098d631c41b90badd4cff1e1eb1b3c78e15a9f811c13ba8ffbe",
|
||||
"crates/oa-desktop/src/hosted/mod.rs": "1b75e11c80fcb38fd20bb3487e3f88729fdac73e471c5ae64da28c249f77c63b",
|
||||
"crates/oa-desktop/src/hosted/view.rs": "862180b048e6ae28fa188aa889ab5b584061604c7cd7670ab83ec4dbf8c9ae91",
|
||||
"crates/oa-desktop/src/main.rs": "df6c70df05dbfd769cddfa42e45b70a6080e1172a721b3656b4b7efdfa3cb203",
|
||||
"crates/oa-desktop/src/model_menu.rs": "acc9cb82889ec554c5f44a80d669118e67d952a280066b726cbe2b39c8215373",
|
||||
"crates/oa-desktop/src/model_menu_traceability_tests.rs": "a5e54360a7b79afcf7e734c9016e2059fc6c29c3da65c97cd174440fe310e17a",
|
||||
"crates/oa-desktop/src/navigation.rs": "7b2bc56ea9ea1ecbe14536d905b6e18e8e72d11c0c31024e625187886bf3bb65",
|
||||
"crates/oa-desktop/src/port_edit.rs": "70b99f577c4fdeda8780c8dc1ee7b1639fd77aaca06658f06d725139fb3efb6d",
|
||||
"crates/oa-desktop/src/port_preview.rs": "069487c4ece737d52e8ae4a84dcfb132236aca6799e841c91c8e7244320cf871",
|
||||
"crates/oa-desktop/src/port_workflow.rs": "bf37d9c4ca3e24265e365810da5cd98235f27d8ea7eb594ffd55d1443671d5ce",
|
||||
"crates/oa-desktop/src/recovery.rs": "57e22e5fbb0e994e64ae03e69450271fe9f04a951db1670c2a018abe99db4489",
|
||||
"crates/oa-desktop/src/recovery/fixtures/README.md": "a90002df1e087d2dac7be26926864243568a0de7c4103e4927435b34178e8016",
|
||||
"crates/oa-desktop/src/recovery/fixtures/profiles.json": "2c12ae96b50e6da5d60ce2b7fa00204be71dfbe2fd51311fc337a0de6a4332ff",
|
||||
"crates/oa-desktop/src/recovery/fixtures/source5-bundle.json": "12715995e6f7f35414aea53b3ad720b9b31873ffdeaf4444231db1a5ac3c463e",
|
||||
"crates/oa-desktop/src/recovery/fixtures/source5-model.json": "5535a706df9ec21715e8b6a5aa6dbd79636ae461450aee61305559016167612b",
|
||||
"crates/oa-desktop/src/recovery/fixtures/source6-bundle.json": "ea88890a33f47d67dfb06d6dbbcc179058cf28dad76ae7d615c00aaf30dbbbfe",
|
||||
"crates/oa-desktop/src/recovery/fixtures/source6-model.json": "5bb8db0b6394276666323483f977ac3586f88554c81a148c6d508fca0ef4576f",
|
||||
"crates/oa-desktop/src/recovery/source5.rs": "0f0a763f0c98c2c908d4c2b95d8a4c042b6a255ee1cc16260e5404669691f642",
|
||||
"crates/oa-desktop/src/recovery/source5_tests.rs": "cfd17a742f1f27b523fce7cd9309f0a07a30413395f6c58659fb91d02834a7a3",
|
||||
"crates/oa-desktop/src/recovery/source7_tests.rs": "7ee61de66c75bf949489ba51ddc9677e2b4efc68801cf27909c9b894bfdc9288",
|
||||
"crates/oa-desktop/src/route_edit.rs": "cb39d188c283eb80d24f145efbd8fd3bf3582c90c96c6b249c3233e9c107af91",
|
||||
"crates/oa-desktop/src/semantic_review.rs": "8dd1e93783d63a663c7e56c28775a5c389cfd7a6b562013d6a95c891afa21ed8",
|
||||
"crates/oa-desktop/src/semantic_review/artifacts.rs": "a79236a8150597ff808597e59cf4f1e9e61c74c4901d9c8cf42d3427bb52a9a8",
|
||||
"crates/oa-desktop/src/semantic_review/artifacts_recovery_falsifier_tests.rs": "3f255d04e4bffa5a467b75e3cbf6f7149aa3269a02f2a89fbaa1db0e5df9ae62",
|
||||
"crates/oa-desktop/src/semantic_review/artifacts_tests.rs": "04d0dd2ac453705185663f705282aa4a3a31433466a5be305764248da1a03c32",
|
||||
"crates/oa-desktop/src/semantic_review/artifacts_view.rs": "2100ff73565765d8293c84f6ce2d8e566610c1524c50e32ce3c37dc33b6686e4",
|
||||
"crates/oa-desktop/src/semantic_review/attachments.rs": "de0ec0c740895b160ff5b847332c087c3f1a48931583464069470cc61315ed59",
|
||||
"crates/oa-desktop/src/semantic_review/attachments_tests.rs": "8b26be0324e0f33b1e7cf7af340350df8ed98963002d6da8589ee0e6f6900bea",
|
||||
"crates/oa-desktop/src/semantic_review/attachments_view.rs": "932f231554d77c7cb119d33bb06b9879db5e12127aed187e67013e77b6458e78",
|
||||
"crates/oa-desktop/src/semantic_review/batch.rs": "a462eb627ae19348072775cf8e60b17d62f6b682b74cc413906d493fba1f0b3c",
|
||||
"crates/oa-desktop/src/semantic_review/batch_tests.rs": "b67c4d7dcabac710cf3d30730a8db323180a558a415e2d117c868cdfa4804619",
|
||||
"crates/oa-desktop/src/semantic_review/compose.rs": "c6adcc9882dde65e9f04f6eda7f0c855c5cbb285cf421123029169112793ce9b",
|
||||
"crates/oa-desktop/src/semantic_review/compose_diagnostic_tests.rs": "440daf8d43278ef981c620d1818d0daaed8fb83193b8e8ff67b2e42fbf882938",
|
||||
"crates/oa-desktop/src/semantic_review/constructor_tests.rs": "85149521ad7df62918f6f2fab6da2b3346c342ab4bce7100de3f6206c1e81096",
|
||||
"crates/oa-desktop/src/semantic_review/constructors.rs": "0982163439b06a42d9af1643530364109282e63f05b323a35a76d8241f3caa32",
|
||||
"crates/oa-desktop/src/semantic_review/history.rs": "df038c2464eab8093a983f3104f0d04ca8f602010a50e1d3eeb8e53f2309e201",
|
||||
"crates/oa-desktop/src/semantic_review/keyed.rs": "bdd6c0fdc7108056bc5d7433254503449eed2209813680e717ead24b9d0ce834",
|
||||
"crates/oa-desktop/src/semantic_review/keyed_rows.rs": "4b7bf25806471ad22d89253b9af8000f5acff53b4cc0aa1c1a83138e7b270eca",
|
||||
"crates/oa-desktop/src/semantic_review/keyed_rows_tests.rs": "1a3b09e7ed100411cdf8043f9a78defc664a873ee0201117709185c201a5c57f",
|
||||
"crates/oa-desktop/src/semantic_review/keyed_tests.rs": "9dc9b989e5a9d408427ca1baf86b2454f9dd3baf40a551c70ca2359dc18a1227",
|
||||
"crates/oa-desktop/src/semantic_review/message_kind.rs": "4ed19952ea511d1b3226564a138e3322f994371d6b49794c14bc3689d1393e7f",
|
||||
"crates/oa-desktop/src/semantic_review/message_kind_tests.rs": "5fcf9930e949aebeb12eab0327264f32a2388d62dbf098bd2365711e22623c52",
|
||||
"crates/oa-desktop/src/semantic_review/ordered.rs": "6a1ed894d1b9d0110bc784a75c49444f1d14f1f74620d701f22203b2d8d0a813",
|
||||
"crates/oa-desktop/src/semantic_review/ordered_tests.rs": "5c8ea3a69e3da3b94d13f50f12aa7dc53246ccba25ffcace9552a0eb76b0bf36",
|
||||
"crates/oa-desktop/src/semantic_review/recovery.rs": "88e69044d90c481593bab7cc125f0401c05202fccdde07698cec9483947fe8be",
|
||||
"crates/oa-desktop/src/semantic_review/row.rs": "b08fdc503c6c4d2ff99adac7e18c9baa861bf86155e00b55d1e492430980d0f2",
|
||||
"crates/oa-desktop/src/semantic_review/scalar.rs": "9ad202174934dd9a63fbf9cba43135aead571db60846c8b7b1d3019a11df26c2",
|
||||
"crates/oa-desktop/src/semantic_review/source7_tests.rs": "5e8221d77b068fa30c1571988448cb8829711735881d810f3d333cbc95d34a9f",
|
||||
"crates/oa-desktop/src/semantic_review/tests.rs": "077ec6501dfaab0b05ea3987943b204f08352952296d313555051d998ccd232e",
|
||||
"crates/oa-desktop/src/semantic_review/view.rs": "7cc7dd213ab01dadad5debaee868c30a1099466602869a799ebdf9b680e8eaf0",
|
||||
"crates/oa-desktop/src/semantic_review/view_reserved_artifact_tests.rs": "6c766b29c8664d15cb113d2cc71bba9b60ae4ccf7e44c9c5d3c41e1dc4b7ed90",
|
||||
"crates/oa-desktop/src/source_annotations.rs": "517fc93890f10196b5501e8728dbe574a9c4bff5847e43a434060933d6e55465",
|
||||
"crates/oa-desktop/src/source_annotations/anchor_tests.rs": "52580dcc70dddbee9cf96e2e1ef2558183593ea7c45c155e576925d9bfcd2b5d",
|
||||
"crates/oa-desktop/src/source_annotations/layout_tests.rs": "234c72d925f96de43e41c9231b01e2b80f68ffc3519d052bac5f248092f9ff50",
|
||||
"crates/oa-desktop/src/source_annotations/performance_tests.rs": "7d97a449347938067e1d5d70b285f49dab01f0e29f7e2e378319a8f020216879",
|
||||
"crates/oa-desktop/src/source_annotations/tests.rs": "3de4f79fa2f31e9d94fd6dc97ccdb33fee81d1b5bb7bcf9770ea1af3513bd29b",
|
||||
"crates/oa-desktop/src/source_annotations/widget.rs": "f8043aa33e626c71e5945dc3fdd2533cbb4aac504686e123794179a5ce905771",
|
||||
"crates/oa-desktop/src/source_annotations/widget/tests.rs": "9b2f3b10df672866dbe0ecfac693ebf3eaa7d6172dbbfce36af44946b1d9020a",
|
||||
"crates/oa-desktop/src/source_indentation.rs": "c7107c2c977f5b7e5209459b3a7f782914225e14c2c802a2e75f6227404d3d8a",
|
||||
"crates/oa-desktop/src/source_indentation/keyboard_tests.rs": "56ccd9e2b36c84cfa464cda880c83a7e2240b376297c0c1a05312637121d8595",
|
||||
"crates/oa-desktop/src/source_indentation/tests.rs": "b13cc4f0abd2b80381a6da1a1294a26b0d39349b3b80e2a5bf5f9c24e0ec7696",
|
||||
"crates/oa-desktop/src/source_navigation.rs": "3e5a1753140fc50e4d3833bf6fcf572b8d387aa1c8a8af85d3b5fcde7026ffbc",
|
||||
"crates/oa-desktop/src/source_navigation/controller.rs": "938b8264c2d5166bd979439723d6fbec34c4cc5ed7f872f58c2cb9ba06a4a93d",
|
||||
"crates/oa-desktop/src/source_navigation/interaction.rs": "c6efa6105b8ba46655e27a89b9fa59e6fc01a4c6f331f886e035af1e70cbf8a9",
|
||||
"crates/oa-desktop/src/source_navigation/interaction/tests.rs": "6cf5c5944b9c46c275c9039f7166b627fa9176187b39348152f67040c92c95e1",
|
||||
"crates/oa-desktop/src/source_navigation/query.rs": "4f6ff7d708bb99c64b7370a1761a6099d5f97be4b319582ab4faa7bb8aa42bb9",
|
||||
"crates/oa-desktop/src/source_navigation/query_tests.rs": "121f197dbae319f47ec59ff875733f1985c7607badb2e4c3d97ef8746d13afa9",
|
||||
"crates/oa-desktop/src/source_navigation/selection.rs": "e2a6516f80f65d5b2157fdb57da07638bf3962fc34482c00e3ccd9d0307a61e7",
|
||||
"crates/oa-desktop/src/source_navigation/symbols.rs": "01296588309f2273d83659b9dd5c132878423dd0c94389cfe9f35a1e575d77da",
|
||||
"crates/oa-desktop/src/source_rename.rs": "1ead2a66d9fd7429b8cfca1338b038e7dd97e21ae76cb722f3ca4c43a520ca00",
|
||||
"crates/oa-desktop/src/source_rename/origin.rs": "11f619310fe837f20a8d495b13e557cafe258270350815c6ac8c848f51cd76ac",
|
||||
"crates/oa-desktop/src/source_rename/preview.rs": "bf96e31986d143fce43ad1634d7028f1225aebcd9013955ca2ba18cc4fd1190d",
|
||||
"crates/oa-desktop/src/source_tree.rs": "4d55e0110a1eb128a5d26c28f3aae5853d79b7245812b7c82bd275f38440a5aa",
|
||||
"crates/oa-desktop/src/source_workspace.rs": "388c71f63829c0c76526a1700ebf5f240c86c467a65aae4fca4cd6c6a88c468b",
|
||||
"crates/oa-desktop/src/style.rs": "28b9527137d03850b617e3c248828e8111743e3b2d0ca8def31d8f381889ddd2",
|
||||
"crates/oa-desktop/src/style/tests.rs": "b771d9656beafc1489ecef5e97d9b006ac385f77933930c42769cc35ca8ddffe",
|
||||
"crates/oa-desktop/src/style/tests/readonly.rs": "89ec0fc1678e4673bf1495bece8c0d3b1a12260b2246f33f92029947c3afd2c1",
|
||||
"crates/oa-desktop/src/tests.rs": "0552b7557b223bdf114ed37429940dc84e4c2830dd6ca30ca26744b29762311b",
|
||||
"crates/oa-desktop/src/view_copy.rs": "4d2ef73b02c5e09692838325ec2b9abff07f2d600cfbeb9c2b612fe03cda10b4",
|
||||
"crates/oa-desktop/src/views.rs": "ea3449ce9d41b3f91b8657f449a220aa95fb22a6e3061762f81987ec8c54a417",
|
||||
"crates/oa-desktop/src/views/source_symbols.rs": "9956b999c5a98703eda704f84f848c545abceb8f1fa37c6115bf42726f851796",
|
||||
"crates/oa-desktop/src/views/text_workspace.rs": "389aed598746c159bdaf9e7a6737047e549a98dcfeca04b8ebb2af4b0a288065",
|
||||
"crates/oa-desktop/src/views/text_workspace/summary_visibility_tests.rs": "486cccb022402e39babda703781fd3d98d575e1eda8b12356105a01119a65179",
|
||||
"crates/oa-desktop/src/views/traceability.rs": "4c89dcf35c25e737d8cce4d543a5989ca104a5678e6cf40df47e457773d2ef94",
|
||||
"crates/oa-desktop/src/workflow.rs": "b61e11e9dbdc66ec08623e699ab55f18b7754f58cf908f610d66053f7948dd64",
|
||||
"crates/oa-desktop/src/workflow/communication.rs": "34d121ad97706012722e4bb6210072ecc3a5b4718b0ceab0c3d0f14d4cd780a1",
|
||||
"crates/oa-desktop/src/workflow/counterparts.rs": "bd86279ffbc42e9b502341db4aeaa093665916f1f45aac03eac38c8ea1341e20",
|
||||
"crates/oa-desktop/src/workflow/recovery.rs": "09f4c6aa81ecf3a491162c7c0c426c9758c722e391f1b1db495c238f6259020a",
|
||||
"crates/oa-desktop/src/workflow/transition_tests.rs": "616b66a62564770cca1bcd013ab1722292c7cb77ced748a004c1385f46c6a2a7",
|
||||
"crates/oa-server/Cargo.toml": "527ff5e15010a8b560a6659aa1ae4bf0c8df0d9e019f03c47529ec6a750c0982",
|
||||
"crates/oa-server/build.rs": "ab1be6ae1fd8bf3ebaeef6348347a9d5e666bde693126f199e041659e0b094a9",
|
||||
"crates/oa-server/src/error.rs": "93817f1502386e4662c96ee1c7d1c4afc4e33bda6fd64e46c86c8e1afd5a9368",
|
||||
"crates/oa-server/src/lib.rs": "20bef350350ed6339588b23af684abf745f97916c7edf3853ee042abe687e664",
|
||||
"crates/oa-server/src/main.rs": "d59a6a2b5c7034bd2e00a516d2d49abb16391bf0316c1c357ddafb9c82d2d1e3",
|
||||
"crates/oa-server/src/preparation_http_tests.rs": "454b90d0055d35644c8d990cd7576ddf675a9b271ca07fe9b0efce25d8ddbc07",
|
||||
"crates/oa-server/src/routes.rs": "3f9a0c22043dbf28f7a8e587d2fc3abf869f76f4d23a46fe0aeb9c4392454a4b",
|
||||
"crates/oa-server/src/store.rs": "37dcfb845b7993d5bced2bcf25fedfcd2a9c20a748f9315afe6cca1f41f89ae5",
|
||||
"crates/oa-server/src/store_preparation_gate.rs": "427c390da3c0281ba1fe115a93ea90800e3b45fce6bcb9185e71982ddd885fec",
|
||||
"crates/oa-server/tests/http.rs": "9acc17c35622a8a3536305857dacd4a17f7479d01a5d4bbbc9be9ca2f14bbd43",
|
||||
"crates/oa-server/tests/http/communication_limits.rs": "ba5474dd8e494a0176fffb4548c8e3deb2081b15de33bfce82394c764e0a2c16",
|
||||
"crates/oa-server/tests/http/data_contracts.rs": "8cd456db83f301baca380acf025a5a99db4edf7d8839537dfbd02c4307e10e6b",
|
||||
"crates/oa-server/tests/http/edge_attachments.rs": "1220a1c4b5818e6796d476c938c3e67316e27df18872a5272c9974ab1381ee7b",
|
||||
"crates/oa-server/tests/http/port_anchors.rs": "a00d59a9fc78850c39e6207b2706cfa1ee95b3bcf205f7104efb33c7cd5bc7c8",
|
||||
"crates/oa-server/tests/http/scenario_layout.rs": "e3a03acb147e9413a2477a992f7ace95daf8bde61b918a6d393bdc621a92f8d7",
|
||||
"crates/oa-server/tests/http/semantic_transitions.rs": "856d1c9b633ffb581e3e631f4e5a607789a02181dbf060667f208a569bfa4920",
|
||||
"crates/oa-server/tests/http/source6_views.rs": "c97c206c6fa4495813be8be1ec185fa0e23af01e54c0dfc64fe2b47f61b075f0",
|
||||
"crates/oa-server/tests/http/source7_contracts.rs": "0e0a467186da7263c0754890aff69ebd0d92d36b5224e4865bb6854b18dab7e8",
|
||||
"crates/oa-server/tests/http/source_projects.rs": "8ff580f317ec267d1cd72ae14d8638f74a77cdf99ca4c78f66f4290c2d77460a",
|
||||
"crates/oa-server/tests/semantic_http_contract.py": "4737ff94f10761f94151a694414c5c62737e0391ec820bca132830652bc4cca2",
|
||||
"crates/oa-server/tests/semantic_schema.py": "d3249f1e81dfa44734c69f16994882d78e0142569d809534b0d02e6051b470f4",
|
||||
"crates/oa-server/tests/state_representation.rs": "6686c826f28248ae4b63ec3f2112e193d4a347724b87974483217fcfee49cd61",
|
||||
"crates/vendor/iced_widget/.cargo_vcs_info.json": "58c111ee6197295ae381d021ab7afd7ac38b02acce90d0ff0f82ae2e1e5f864c",
|
||||
"crates/vendor/iced_widget/ARCADIA-PATCH.md": "d3acb98e5f423970b0bf4198373efddaea6f3a51aa282da571cf95c0f5690504",
|
||||
"crates/vendor/iced_widget/Cargo.toml": "e8e6b9a72fc0f97c84b8fb2703fe6cdccd738c6c01d2fadf018a012182de93b5",
|
||||
"crates/vendor/iced_widget/Cargo.toml.orig": "f9bed37f4aa1edebea47824c1f9678411d410a1546401a14ce6f8b4108c96448",
|
||||
"crates/vendor/iced_widget/LICENSE": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398",
|
||||
"crates/vendor/iced_widget/UPSTREAM-MANIFEST.json": "1be311c5d9c328dd986231deac742ceff1d53a309aa06569f8928546770a7393",
|
||||
"crates/vendor/iced_widget/assets/iced-logo.svg": "8ac9e30de1b466b3d594c77c984299b1a229f6f0f010e6804139a2f41ea8345e",
|
||||
"crates/vendor/iced_widget/src/action.rs": "feb91a386073280d76eaf37dbb8e7380e5a6bf9b8003110107b32bb8f577a239",
|
||||
"crates/vendor/iced_widget/src/button.rs": "a8019fee3ad691fb2395afcdf302bec5f9e1cc592552aff4d8c8bbd1c6149f5a",
|
||||
"crates/vendor/iced_widget/src/canvas.rs": "1fecfd50d593c5ff9bc9317c9f1624d0c22eddc7cf39a795684bf70eb5954acd",
|
||||
"crates/vendor/iced_widget/src/canvas/program.rs": "637bbc6d10a623f355c6d44af1162a10331861806cec094e2a8f2a97313acab9",
|
||||
"crates/vendor/iced_widget/src/checkbox.rs": "4409c11d6473baf4baefafbb3ed253cd9353f04136cac6009422a876a84ec6b1",
|
||||
"crates/vendor/iced_widget/src/column.rs": "f880d15ebdb33e2f6ebb6067bb0b124c8fafdc73a6e5112a6f5b5e012fddadf4",
|
||||
"crates/vendor/iced_widget/src/combo_box.rs": "973901d05003f684be1d13da5adc88ef7e030dc31db07c1bb0717cf9210d2391",
|
||||
"crates/vendor/iced_widget/src/container.rs": "f8162a4e1c25c602070da482ecccfd5e62f456ffe1caa5394a11243d339f6114",
|
||||
"crates/vendor/iced_widget/src/float.rs": "9bc2d313052c97338dcdec2a5be7e0301b6261fe7975b511050fe4abeeb1c571",
|
||||
"crates/vendor/iced_widget/src/grid.rs": "01e4deea4638fba19e3859d7ae27bd8090bca3fcee6f31c48bf5ad9a19f5c151",
|
||||
"crates/vendor/iced_widget/src/helpers.rs": "3989cad7e901ff8171a16024d93ec40cb32e7859c2e76a98359f5f230a2fabcb",
|
||||
"crates/vendor/iced_widget/src/image.rs": "dab4c761c846ba2006ba5298c9b3573b0a2787272b2f74376cde6c35ae7f3694",
|
||||
"crates/vendor/iced_widget/src/image/viewer.rs": "ce372e8ba97248cea915abd52046020c0f5c318efc2af1d7f4d74958b14629bf",
|
||||
"crates/vendor/iced_widget/src/keyed.rs": "f1e572a13d0717816a6d4467d14f07fa613956a6bf9a2b234d79789b0bcb1d57",
|
||||
"crates/vendor/iced_widget/src/keyed/column.rs": "09adcae868d561443e8d8b6f3cdfefd6831d8e94adf2b22c551c85b9669eb620",
|
||||
"crates/vendor/iced_widget/src/lazy.rs": "8c127fcd044cf887564856ce3a38ecf41ff3ff24d832d72095ec4e92f9907673",
|
||||
"crates/vendor/iced_widget/src/lazy/cache.rs": "59445853fb59941d73904689e5ee34bc6e9f7159c58bbea0cac8ef87f69a1b10",
|
||||
"crates/vendor/iced_widget/src/lazy/component.rs": "05e4e793f3462e5f164ef1191a5cf2c6daf32d410aeebbe1bb4e951fb04fdef7",
|
||||
"crates/vendor/iced_widget/src/lazy/helpers.rs": "d6bab15685469619bc3833afacc1df22112adacbdf944d6dbb5ea1b9e5f9d993",
|
||||
"crates/vendor/iced_widget/src/lib.rs": "4da7f1a83cd8edc30bd9983e59ba3537f44a475814d0b9c80dcf7e97873badb4",
|
||||
"crates/vendor/iced_widget/src/markdown.rs": "5269582001d26d79f9de4e6ee60ebed587adf552f6f91c8ffcd757782319d161",
|
||||
"crates/vendor/iced_widget/src/mouse_area.rs": "01686e65f76a4b89356ec6e5900b4411c856b15a56236ed3f6ef410ec3a4983d",
|
||||
"crates/vendor/iced_widget/src/overlay.rs": "104d339ba61626ebf0877f5c942e9f67b3db6261d6c9ae5e4805859508e1bc49",
|
||||
"crates/vendor/iced_widget/src/overlay/menu.rs": "e033eccc779bfc565525ef6e49934acd7532967e723b4b48b9af9fc2df441c8a",
|
||||
"crates/vendor/iced_widget/src/pane_grid.rs": "4eb5ae3be82237f5193b550d407c1a5a9f099a4d554442dbe8f51b0b72edb213",
|
||||
"crates/vendor/iced_widget/src/pane_grid/axis.rs": "4e7a842bc60bee64ee86c4d512c0ac034e4c6bf16b1fe0777c16b8a0b5b28751",
|
||||
"crates/vendor/iced_widget/src/pane_grid/configuration.rs": "0d70a26e2fcafc1f1403e43f6c0790698940c92a0966d4ea4acec216778d65cd",
|
||||
"crates/vendor/iced_widget/src/pane_grid/content.rs": "13ec6f9517e0cbd7d2a311ef57b963eff2c5fe6f05264cca29e54d14a0ea8151",
|
||||
"crates/vendor/iced_widget/src/pane_grid/controls.rs": "c7eeffce253b8aa0e7853baaf33ab3e926261df7e6b809a566aa17a4e779d560",
|
||||
"crates/vendor/iced_widget/src/pane_grid/direction.rs": "f79259afb26fb38f4af7efdaea5fc6527b5426fe195ef89f72d16405d0bc3076",
|
||||
"crates/vendor/iced_widget/src/pane_grid/draggable.rs": "3688ee1a543a281c036d2f4f156a3c529263219b50e3945ef50fa4bb63b02ae3",
|
||||
"crates/vendor/iced_widget/src/pane_grid/node.rs": "bcda8a561581849163a3bb75f34d310d6463ff58ea6660e9fa33c8ea22d40c9f",
|
||||
"crates/vendor/iced_widget/src/pane_grid/pane.rs": "2305c36971b405368a99394ad6b3cd0cf21638d9933f8cbd445fd764a257a4df",
|
||||
"crates/vendor/iced_widget/src/pane_grid/split.rs": "8be472fc48c37055bbb62395839c599559abc3d70cb7806892f869bd95e90804",
|
||||
"crates/vendor/iced_widget/src/pane_grid/state.rs": "a13125af64ea6da1bcb5a13469f4ffdd51106776b27871e10ce3ac037d865b63",
|
||||
"crates/vendor/iced_widget/src/pane_grid/title_bar.rs": "7556b48c85811d64e3c11c4ce901c579568f4ec056ff5f1bd5e38066b2f059ce",
|
||||
"crates/vendor/iced_widget/src/pick_list.rs": "12d46d3cf4e651d92a88d4cc24b7b08a582ddcf87538d4fa9cde1c668bb93f4e",
|
||||
"crates/vendor/iced_widget/src/pin.rs": "f0b0ab2089300c9ceab8f8f3c75cd96409c7a84f3fa26d68645aab3f92247426",
|
||||
"crates/vendor/iced_widget/src/progress_bar.rs": "38e9bb585a9b764ff6cb947e17d65259a6a32cf3a69b23844e4c28e6c5a1c3ba",
|
||||
"crates/vendor/iced_widget/src/qr_code.rs": "712482a35eab47e0559fafd5452a2b934c5fbc5ae07c14c0c71781713b4dbfcc",
|
||||
"crates/vendor/iced_widget/src/radio.rs": "7b0f42744416e07f8af9409c5bae997b07a0653492ccc2e71911bdeed9c747b2",
|
||||
"crates/vendor/iced_widget/src/responsive.rs": "2a3ff68573825a1407e3be27adbfb4851c7aae7cd100c7a0b670911bc3a08393",
|
||||
"crates/vendor/iced_widget/src/row.rs": "2566e8868bced1a6b7d455c9f1ab61c7025772b27bf843ee806d5c7806fcd44b",
|
||||
"crates/vendor/iced_widget/src/rule.rs": "b7b23722d314864d712e21c8ef260d2b747d0ac795bb3263a0a762c38467d6b9",
|
||||
"crates/vendor/iced_widget/src/scrollable.rs": "a176ea537bbe717550fe4dc17af31c801d9ec118edbcc44aab59ea1a78d1a7a2",
|
||||
"crates/vendor/iced_widget/src/sensor.rs": "cb95d1220fa5663b7a148d6b159081da2200debe6fea982a04f48e27dd072973",
|
||||
"crates/vendor/iced_widget/src/shader.rs": "8753516e6a6b422a76632d07c716e047ff9b5e8f32bf9a9463c612c292306382",
|
||||
"crates/vendor/iced_widget/src/shader/program.rs": "4ce3711b2899b1c1d53544ff0c1f823e108542d356d5e374cb7b149c776d6918",
|
||||
"crates/vendor/iced_widget/src/slider.rs": "778d6a4c37b334cba62ada5f4958e5909a91404961fe5c1104acd9dad12a18d5",
|
||||
"crates/vendor/iced_widget/src/space.rs": "40839bf1dfa93e02c9e3d7e2ab5bb41b328b1fe863846f6f1feca11d9885453b",
|
||||
"crates/vendor/iced_widget/src/stack.rs": "b12776ae1dd46ba95ca0ca52f6a095be1e0121c51f3180ad7cc202cd55764594",
|
||||
"crates/vendor/iced_widget/src/svg.rs": "a8167836a19806f4c2cc633a265953b8c4348f279082d7577b69f817e3166aeb",
|
||||
"crates/vendor/iced_widget/src/table.rs": "373f600db363090412e6bfc9c338bd6bc9516e78f7a43fb00f20d5ed21eb625c",
|
||||
"crates/vendor/iced_widget/src/text.rs": "a7d125fc7d07ba51e05f86ca97f4ffad5db37ef2e6d4320bbcd8475d99d51ed3",
|
||||
"crates/vendor/iced_widget/src/text/rich.rs": "7deababcbe5f0294b605822fe2651eb0fee6e9c1bee37883dbc570481df6a0b9",
|
||||
"crates/vendor/iced_widget/src/text_editor.rs": "70e2b5fa29b38ceb9b7688982712647bbb60b751681bfb6c3ca6bcacb51a08e1",
|
||||
"crates/vendor/iced_widget/src/text_input.rs": "7cd64b0f79c6000f2985cc290642698a814be5dcd7b7a56ae463b13ffe5c3b47",
|
||||
"crates/vendor/iced_widget/src/text_input/cursor.rs": "eeb9241a25110583fd2e169da47834aeb39fd55335ea3b77aa3d1e43016c28d8",
|
||||
"crates/vendor/iced_widget/src/text_input/editor.rs": "cd0e0168e9de1ad2599120a4dda8a5a373813a4ffc3a3c035929d18144544408",
|
||||
"crates/vendor/iced_widget/src/text_input/value.rs": "03fba71d34f2c31df15431ac7e22f854b655336f86ef854ae34cc1e0bfbcefdd",
|
||||
"crates/vendor/iced_widget/src/themer.rs": "4b87e866b8f2a4420f30674ad64c605d24e3004f867bf95a1210b5338ed7a81e",
|
||||
"crates/vendor/iced_widget/src/toggler.rs": "70f1ef0ee8e2b6c0d7b93095eab1e615783bb81d945b4ff066af025dde8f5dcb",
|
||||
"crates/vendor/iced_widget/src/tooltip.rs": "7fcaa3899ad1a946bb1a51d34d344497005aaccdf825f510e98105d1bc40bc63",
|
||||
"crates/vendor/iced_widget/src/vertical_slider.rs": "75777345ced463b4a3b059fbe6c59efc952169987154b0597d87ce4da2076e3b",
|
||||
"docs/acceptance.md": "32ed3e4fed63f5c464e0937221f401d71623b276dd70f083531ac6ccebbf9050",
|
||||
"docs/api.html": "d06e32b3da838e519630a4151111bb40a383ccf294eb18443118a7498700a993",
|
||||
"docs/api.rst": "85abb2d1d3923a695f03f422d9345138bc38ec1fc5f354e70d6b31d561f49e97",
|
||||
"docs/architecture.md": "a50bd1645222ef3e574fa07a6ab9aa213431ee8833c6c0158ad91ce0f799543c",
|
||||
"docs/language.html": "77c14e548f5d2deea0c21a3a931347e1c87449f5be8503f6c51ceaef37be9a33",
|
||||
"docs/language.rst": "00351386d69828466227628b92a97046cdee85623ca41cbc3011ae12e9fe0a47",
|
||||
"docs/linux-packaging.md": "41a1bb1095801df37a1411341c5791d55ea8c365739314d599a60269ac4bc7a0",
|
||||
"docs/openapi.json": "846ef09d0de9cc194c1232b56d10c29717f89f4f07d92afb9305659d584370a8",
|
||||
"docs/preview-human-checks.md": "50b56c328c22587e1187f6e614bc914210095cf635c301f335e82de11201b890",
|
||||
"docs/research-v2-architecture.md": "1b3809f2dabe6b1805dbf3d3f2625c471cd21356c7bd603916466a8c4ced8090",
|
||||
"docs/research-v2-semantics.md": "b7ec3677300248926a88b84da3e7ffe52f5b73524c7712c927a0e951569db04f",
|
||||
"docs/research-v2-service.md": "4828df5bc679e7c71f49b4b85c9e7dacbec52f46d416fed48af70e08886d4e4e",
|
||||
"docs/research-v2-ui.md": "894e721b59a8ca85f0d95b6706ed5f130a0372def460cf33aeefc9e5e38cde0a",
|
||||
"docs/scope.md": "a1d0cfbfc06fc8bc2bf0f71343aacc8853cbbbfc00a1fc9852ddf41c8245ffca",
|
||||
"docs/server.md": "4eb24937d424ba8b384e1734e02a6f8e963ae11d2715889c92b14b39ff2e4a48",
|
||||
"docs/source-cli.md": "34f12b72cc6d6ead4f47c7818c1897db592f8ad493c70776db2ab48c24d8f051",
|
||||
"docs/v2-interface.md": "9788f23f2b5647685b165d6e4376c7119ff4bf7c688847dc5f3c4094a96fb0a0",
|
||||
"docs/validation.md": "f46ea950ed1a54409aa07c4f9e90f39f8e2d00c1dd1b6cb805caa3993845967f",
|
||||
"docs/windows-packaging.md": "2d5ef8a9a8741e6e7f6df9db9fd9a839f806ea782a6f2063c3a98725ecfd36b6"
|
||||
},
|
||||
"steps": []
|
||||
}
|
||||
|
Before Width: | Height: | Size: 160 KiB |
@@ -1,6 +0,0 @@
|
||||
|
||||
running 1 test
|
||||
test populated_mixed_projection_switch_conserves_authorship_and_restores_geometry ... ok
|
||||
|
||||
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.61s
|
||||
|
||||