Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
//! Generate only the new teaching-project files; old compatibility examples stay untouched.
|
||||
#[path = "../../../examples/release1/model.rs"]
|
||||
mod teaching;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let directory = std::env::args_os()
|
||||
.nth(1)
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| "examples/release1".into());
|
||||
let lesson = teaching::build();
|
||||
let bundle = teaching::checked_bundle(&lesson.project);
|
||||
for (name, source) in &bundle.files {
|
||||
let path = directory.join(name);
|
||||
std::fs::create_dir_all(path.parent().unwrap())?;
|
||||
std::fs::write(path, source)?;
|
||||
}
|
||||
for (name, value) in [
|
||||
("model.json", serde_json::to_value(&lesson.project)?),
|
||||
(
|
||||
"transition-before.json",
|
||||
serde_json::to_value(&lesson.before_transition)?,
|
||||
),
|
||||
(
|
||||
"transition-request.json",
|
||||
serde_json::to_value(&lesson.request)?,
|
||||
),
|
||||
(
|
||||
"expected-diagnostics.json",
|
||||
serde_json::to_value(oa_core::validation::validate(&lesson.project))?,
|
||||
),
|
||||
] {
|
||||
std::fs::write(
|
||||
directory.join(name),
|
||||
serde_json::to_string_pretty(&value)? + "\n",
|
||||
)?;
|
||||
}
|
||||
println!(
|
||||
"Validated {} elements, {} relations, {} views, {} traces; wrote {} source files to {}",
|
||||
lesson.project.elements.len(),
|
||||
lesson.project.relations.len(),
|
||||
lesson.project.diagrams.len(),
|
||||
lesson.project.traces.len(),
|
||||
bundle.files.len(),
|
||||
directory.display()
|
||||
);
|
||||
for issue in oa_core::validation::validate(&lesson.project) {
|
||||
println!(
|
||||
"{:?} {} {:?}: {}",
|
||||
issue.severity, issue.code, issue.element, issue.message
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
//! Deterministic dense-view fixture and semantic oracle. No GUI or authored routing.
|
||||
//! cargo run -p oa-core --example release_dense_views -- OUTPUT_DIRECTORY
|
||||
use oa_core::{view_options::*, *};
|
||||
use serde_json::json;
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
error::Error,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
fn id(n: u32) -> Id {
|
||||
format!("d4060906-0000-4000-8000-{n:012x}").parse().unwrap()
|
||||
}
|
||||
|
||||
fn element(p: &mut Project, n: u32, kind: Kind, alias: &str, name: &str, parent: Option<Id>) -> Id {
|
||||
let identity = id(n);
|
||||
assert!(
|
||||
p.elements
|
||||
.insert(
|
||||
identity,
|
||||
Element {
|
||||
id: identity,
|
||||
alias: alias.into(),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
parent,
|
||||
body: ElementBody::new(kind, Layer::Oa).unwrap()
|
||||
}
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
identity
|
||||
}
|
||||
|
||||
fn relation(p: &mut Project, n: u32, alias: &str, name: &str, body: RelationBody) -> Id {
|
||||
let identity = id(n);
|
||||
assert!(
|
||||
p.relations
|
||||
.insert(
|
||||
identity,
|
||||
Relation {
|
||||
id: identity,
|
||||
alias: alias.into(),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
body
|
||||
}
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
identity
|
||||
}
|
||||
|
||||
fn fixture() -> (Project, Id, Vec<Id>, Vec<Id>, Vec<Id>) {
|
||||
let mut p = Project::empty();
|
||||
p.id = id(1);
|
||||
p.alias = "dense_service_views".into();
|
||||
p.name = "Forty service exchanges".into();
|
||||
p.description = "Deterministic release fixture. Forty actual work exchanges share two endpoints; no authored routes or synthetic endpoints.".into();
|
||||
let requester = element(&mut p, 10, Kind::Entity, "requester", "Requester", None);
|
||||
let provider = element(&mut p, 11, Kind::Entity, "provider", "Provider", None);
|
||||
let a = element(
|
||||
&mut p,
|
||||
20,
|
||||
Kind::Activity,
|
||||
"request_service",
|
||||
"Request service",
|
||||
None,
|
||||
);
|
||||
let b = element(
|
||||
&mut p,
|
||||
21,
|
||||
Kind::Activity,
|
||||
"provide_service",
|
||||
"Provide service",
|
||||
None,
|
||||
);
|
||||
relation(
|
||||
&mut p,
|
||||
3000,
|
||||
"requester_work",
|
||||
"Requester responsibility",
|
||||
RelationBody::Allocation {
|
||||
source: requester,
|
||||
target: a,
|
||||
},
|
||||
);
|
||||
relation(
|
||||
&mut p,
|
||||
3001,
|
||||
"provider_work",
|
||||
"Provider responsibility",
|
||||
RelationBody::Allocation {
|
||||
source: provider,
|
||||
target: b,
|
||||
},
|
||||
);
|
||||
let group_names = ["Commands", "Status", "Alerts", "Measurements"];
|
||||
let items: Vec<_> = group_names
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, name)| {
|
||||
element(
|
||||
&mut p,
|
||||
100 + i as u32,
|
||||
Kind::ExchangeItem,
|
||||
&format!("payload_{}", i + 1),
|
||||
&format!("{name} payload"),
|
||||
None,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let exchanges: Vec<_> = (0..40)
|
||||
.map(|i| {
|
||||
relation(
|
||||
&mut p,
|
||||
2000 + i,
|
||||
&format!("service_data_{:02}", i + 1),
|
||||
&format!("Service data {:02}", i + 1),
|
||||
RelationBody::Interaction {
|
||||
source: a,
|
||||
target: b,
|
||||
items: vec![items[(i / 10) as usize]],
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let mut categories = vec![];
|
||||
for (i, name) in group_names.iter().enumerate() {
|
||||
let category = element(
|
||||
&mut p,
|
||||
200 + i as u32,
|
||||
Kind::ExchangeCategory,
|
||||
&format!("concern_{}", i + 1),
|
||||
name,
|
||||
None,
|
||||
);
|
||||
*p.elements.get_mut(&category).unwrap().shared_mut().unwrap() =
|
||||
SharedBody::ExchangeCategory {
|
||||
family: ExchangeFamily::Work,
|
||||
exchanges: exchanges[i * 10..i * 10 + 10].to_vec(),
|
||||
};
|
||||
categories.push(category);
|
||||
}
|
||||
let overlapping = element(
|
||||
&mut p,
|
||||
204,
|
||||
Kind::ExchangeCategory,
|
||||
"critical_concern",
|
||||
"Critical cross-cutting exchanges",
|
||||
None,
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&overlapping)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::ExchangeCategory {
|
||||
family: ExchangeFamily::Work,
|
||||
exchanges: [0, 10, 20, 30].map(|i| exchanges[i]).to_vec(),
|
||||
};
|
||||
categories.push(overlapping);
|
||||
let mut contexts = vec![];
|
||||
for (group, name) in group_names[..3].iter().enumerate() {
|
||||
let process = element(
|
||||
&mut p,
|
||||
300 + group as u32,
|
||||
Kind::Process,
|
||||
&format!("process_{}", group + 1),
|
||||
&format!("{name} process"),
|
||||
None,
|
||||
);
|
||||
for offset in 0..10 {
|
||||
let index = group * 10 + offset;
|
||||
let occurrence = element(
|
||||
&mut p,
|
||||
1000 + index as u32,
|
||||
Kind::Occurrence,
|
||||
&format!("use_data_{:02}", index + 1),
|
||||
&format!("Use service data {:02}", index + 1),
|
||||
Some(process),
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&occurrence)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange {
|
||||
exchange: Some(exchanges[index]),
|
||||
sender: None,
|
||||
receiver: None,
|
||||
message_kind: MessageKind::Asynchronous,
|
||||
reply_to: None,
|
||||
values: vec![],
|
||||
},
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
};
|
||||
p.elements
|
||||
.get_mut(&process)
|
||||
.unwrap()
|
||||
.steps_mut()
|
||||
.unwrap()
|
||||
.push(occurrence);
|
||||
}
|
||||
contexts.push(process);
|
||||
}
|
||||
let diagram = id(4000);
|
||||
let nodes = [(a, 80.), (b, 760.)]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (element, x))| {
|
||||
let id = id(4100 + i as u32);
|
||||
(
|
||||
id,
|
||||
NodePlacement {
|
||||
id,
|
||||
alias: format!("service_placement_{}", i + 1),
|
||||
element,
|
||||
position: Position { x, y: 160. },
|
||||
size: Size {
|
||||
width: 230.,
|
||||
height: 150.,
|
||||
},
|
||||
collapsed: false,
|
||||
port_anchors: vec![],
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
p.diagrams.insert(diagram, Diagram { id: diagram, alias: "forty_exchange_view".into(), name: "Forty service exchanges · all concerns".into(), description: "Start with All. Compare first category, overlapping concerns, global coverage and chosen processes. No hand-routed edges.".into(), layer: Layer::Oa, projection: ProjectionKind::FunctionalDataflow, view_options: ViewOptions::default(), nodes, routes: vec![] });
|
||||
(p, diagram, exchanges, categories, contexts)
|
||||
}
|
||||
|
||||
fn projected(p: &Project, diagram: &Diagram) -> BTreeSet<Id> {
|
||||
projection::project_diagram(p, diagram)
|
||||
.edges
|
||||
.into_iter()
|
||||
.map(|edge| edge.id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn overlap(a: routing::RoutingBounds, b: routing::RoutingBounds) -> bool {
|
||||
a.position.x < b.position.x + b.size.width
|
||||
&& b.position.x < a.position.x + a.size.width
|
||||
&& a.position.y < b.position.y + b.size.height
|
||||
&& b.position.y < a.position.y + a.size.height
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let output = PathBuf::from(
|
||||
std::env::args_os()
|
||||
.nth(1)
|
||||
.ok_or("Supply an output directory for the dense-view fixture.")?,
|
||||
);
|
||||
let (p, diagram, exchanges, categories, contexts) = fixture();
|
||||
let issues = validation::validate(&p);
|
||||
assert!(
|
||||
!issues
|
||||
.iter()
|
||||
.any(|issue| issue.severity == validation::Severity::Error),
|
||||
"{issues:?}"
|
||||
);
|
||||
let pristine = p.clone();
|
||||
let original = &p.diagrams[&diagram];
|
||||
assert!(original.routes.is_empty());
|
||||
let all = projection::project_view(&p, diagram);
|
||||
assert_eq!(all.nodes.len(), 2);
|
||||
assert_eq!(all.edges.len(), 40);
|
||||
assert_eq!(
|
||||
all.edges
|
||||
.iter()
|
||||
.map(|edge| (edge.source, edge.target))
|
||||
.collect::<BTreeSet<_>>()
|
||||
.len(),
|
||||
1
|
||||
);
|
||||
let sets = |ids: &[Id]| ids.iter().copied().collect::<BTreeSet<_>>();
|
||||
let cases = vec![
|
||||
("all", ViewOptions::default(), sets(&exchanges)),
|
||||
(
|
||||
"first_category",
|
||||
ViewOptions {
|
||||
work_categories: CategorySelection::Any(vec![categories[0]]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
sets(&exchanges[..10]),
|
||||
),
|
||||
(
|
||||
"first_and_overlap",
|
||||
ViewOptions {
|
||||
work_categories: CategorySelection::Any(vec![categories[0], categories[4]]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
exchanges[..10]
|
||||
.iter()
|
||||
.copied()
|
||||
.chain([exchanges[10], exchanges[20], exchanges[30]])
|
||||
.collect(),
|
||||
),
|
||||
(
|
||||
"in_behavior",
|
||||
ViewOptions {
|
||||
coverage: Coverage::InBehavior,
|
||||
..ViewOptions::default()
|
||||
},
|
||||
sets(&exchanges[..30]),
|
||||
),
|
||||
(
|
||||
"outside_behavior",
|
||||
ViewOptions {
|
||||
coverage: Coverage::OutsideBehavior,
|
||||
..ViewOptions::default()
|
||||
},
|
||||
sets(&exchanges[30..]),
|
||||
),
|
||||
(
|
||||
"second_process",
|
||||
ViewOptions {
|
||||
work_contexts: ContextSelection::Any(vec![contexts[1]]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
sets(&exchanges[10..20]),
|
||||
),
|
||||
(
|
||||
"outside_second_process",
|
||||
ViewOptions {
|
||||
coverage: Coverage::OutsideBehavior,
|
||||
work_contexts: ContextSelection::Any(vec![contexts[1]]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
"empty_categories",
|
||||
ViewOptions {
|
||||
work_categories: CategorySelection::Any(vec![]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
"empty_contexts",
|
||||
ViewOptions {
|
||||
work_contexts: ContextSelection::Any(vec![]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
"no_lines",
|
||||
ViewOptions {
|
||||
relation_kinds: RelationKinds::Only(vec![]),
|
||||
..ViewOptions::default()
|
||||
},
|
||||
BTreeSet::new(),
|
||||
),
|
||||
(
|
||||
"first_hide_05",
|
||||
ViewOptions {
|
||||
hidden_edges: vec![exchanges[4]],
|
||||
work_categories: CategorySelection::Any(vec![categories[0]]),
|
||||
exchange_labels: ExchangeLabels::NameAndItems,
|
||||
..ViewOptions::default()
|
||||
},
|
||||
exchanges[..10]
|
||||
.iter()
|
||||
.filter(|edge| **edge != exchanges[4])
|
||||
.copied()
|
||||
.collect(),
|
||||
),
|
||||
];
|
||||
let mut case_results = BTreeMap::new();
|
||||
for (name, options, expected) in cases {
|
||||
assert!(validate_options(&p, Layer::Oa, &options).is_empty());
|
||||
let mut changed = original.clone();
|
||||
changed.view_options = options;
|
||||
assert_eq!(projected(&p, &changed), expected, "{name}");
|
||||
let view = projection::project_diagram(&p, &changed);
|
||||
assert_eq!(
|
||||
view.nodes, all.nodes,
|
||||
"{name}: visibility must not move nodes"
|
||||
);
|
||||
assert_eq!(
|
||||
view.ports, all.ports,
|
||||
"{name}: visibility must not move ports"
|
||||
);
|
||||
assert!(changed.routes.is_empty());
|
||||
case_results.insert(name, json!({"count":expected.len(),"edges":expected}));
|
||||
}
|
||||
assert_eq!(
|
||||
ExchangeLabels::Items.label(&p, exchanges[0]),
|
||||
"Commands payload"
|
||||
);
|
||||
assert_eq!(
|
||||
ExchangeLabels::NameAndItems.label(&p, exchanges[39]),
|
||||
"Service data 40 · Measurements payload"
|
||||
);
|
||||
let began = Instant::now();
|
||||
let routed = routing::route_diagram(&p, original, &all);
|
||||
let route_ms = began.elapsed().as_millis();
|
||||
assert_eq!(routed.edges.len(), 40);
|
||||
let edges: Vec<_> = routed.edges.values().collect();
|
||||
let mut label_overlaps = vec![];
|
||||
let mut coincident_paths = vec![];
|
||||
for (i, first) in edges.iter().enumerate() {
|
||||
for second in &edges[i + 1..] {
|
||||
if overlap(first.label.bounds, second.label.bounds) {
|
||||
label_overlaps.push([first.id, second.id]);
|
||||
}
|
||||
if first.points == second.points {
|
||||
coincident_paths.push([first.id, second.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let source = language::format(&p);
|
||||
assert_eq!(
|
||||
language::parse(&source).expect("Canonical fixture source must reopen"),
|
||||
p
|
||||
);
|
||||
let native_json = serde_json::to_vec_pretty(&p)?;
|
||||
assert_eq!(persistence::decode(&native_json)?, p);
|
||||
assert_eq!(
|
||||
p, pristine,
|
||||
"Oracles and routing must not mutate model facts"
|
||||
);
|
||||
let report = json!({"schema":p.schema_version,"project":p.id,"diagram":diagram,"semantic_oracles":"passed","elements":p.elements.len(),"relations":p.relations.len(),"cases":case_results,
|
||||
"routing_ms":route_ms,"label_overlap_pairs":label_overlaps,"exactly_coincident_path_pairs":coincident_paths,
|
||||
"routing_warnings":edges.iter().map(|edge| json!({"edge":edge.id,"warnings":edge.warnings.iter().map(ToString::to_string).collect::<Vec<_>>()})).collect::<Vec<_>>(),
|
||||
"native_readability":"pending; metrics do not establish a usability pass"});
|
||||
fs::create_dir_all(&output)?;
|
||||
fs::write(output.join("dense-40.arcadia"), source)?;
|
||||
fs::write(output.join("dense-40.json"), native_json)?;
|
||||
fs::write(
|
||||
output.join("dense-40-oracle.json"),
|
||||
serde_json::to_vec_pretty(&report)?,
|
||||
)?;
|
||||
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user