Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
//! Existing-core expressivity experiment, not a proposed production DTO.
|
||||
use oa_core::validation::{Severity, validate};
|
||||
use oa_core::*;
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
fn id(n: u128) -> Id {
|
||||
Id::from_u128(0x52060906000040008000000000000000 + n)
|
||||
}
|
||||
fn add(p: &mut Project, n: u128, k: Kind, name: &str, parent: Option<Id>) -> Id {
|
||||
let mut e = if k == Kind::Port {
|
||||
Element {
|
||||
id: id(n),
|
||||
alias: format!("operation_probe_{n}"),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
parent,
|
||||
body: ElementBody::shared_in(
|
||||
Layer::Sa,
|
||||
SharedBody::Port {
|
||||
contract: PortContract::Component {
|
||||
kind: ComponentPortKind::Standard,
|
||||
orientation: ComponentPortOrientation::Unset,
|
||||
},
|
||||
legacy: None,
|
||||
},
|
||||
),
|
||||
}
|
||||
} else {
|
||||
p.make_element(k, Layer::Sa, name, parent).unwrap()
|
||||
};
|
||||
e.id = id(n);
|
||||
e.alias = format!("operation_probe_{n}");
|
||||
p.elements.insert(e.id, e);
|
||||
id(n)
|
||||
}
|
||||
fn body(p: &mut Project, n: u128, b: SharedBody) {
|
||||
*p.elements.get_mut(&id(n)).unwrap().shared_mut().unwrap() = b;
|
||||
}
|
||||
fn rel(p: &mut Project, n: u128, k: RelationKind, a: u128, b: u128) {
|
||||
p.relations.insert(
|
||||
id(n),
|
||||
Relation {
|
||||
id: id(n),
|
||||
alias: format!("operation_probe_{n}"),
|
||||
name: format!("{k:?} {n}"),
|
||||
description: String::new(),
|
||||
body: RelationBody::new(k, id(a), id(b)),
|
||||
},
|
||||
);
|
||||
}
|
||||
fn valid(p: &Project) {
|
||||
let errors: Vec<_> = validate(p)
|
||||
.into_iter()
|
||||
.filter(|i| i.severity == Severity::Error)
|
||||
.collect();
|
||||
assert!(errors.is_empty(), "{errors:?}");
|
||||
}
|
||||
fn message(p: &Project, n: u128) -> &OccurrenceBody {
|
||||
let Some(SharedBody::Occurrence { body, .. }) = p.elements[&id(n)].shared() else {
|
||||
panic!()
|
||||
};
|
||||
body
|
||||
}
|
||||
fn set_exchange(p: &mut Project, n: u128, target: Id) {
|
||||
let Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { exchange, .. },
|
||||
..
|
||||
}) = p.elements.get_mut(&id(n)).unwrap().shared_mut()
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
*exchange = Some(target);
|
||||
}
|
||||
fn initial() -> Project {
|
||||
let mut p = Project::empty();
|
||||
p.id = id(1);
|
||||
p.name = "Two operations, one interface".into();
|
||||
add(&mut p, 10, Kind::SystemActor, "Operator console", None);
|
||||
add(&mut p, 11, Kind::System, "Valve service", None);
|
||||
for (port, owner) in [(12, 10), (13, 11)] {
|
||||
add(&mut p, port, Kind::Port, "Service socket", Some(id(owner)));
|
||||
body(
|
||||
&mut p,
|
||||
port,
|
||||
SharedBody::Port {
|
||||
contract: PortContract::Component {
|
||||
kind: ComponentPortKind::Standard,
|
||||
orientation: ComponentPortOrientation::Unset,
|
||||
},
|
||||
legacy: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
for (operation, name) in [(20, "Reset"), (21, "ReadStatus")] {
|
||||
add(&mut p, operation, Kind::ExchangeItem, name, None);
|
||||
body(
|
||||
&mut p,
|
||||
operation,
|
||||
SharedBody::ExchangeItem {
|
||||
kind: ExchangeItemKind::Information,
|
||||
mechanism: ExchangeMechanism::Operation,
|
||||
supertype: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
add(&mut p, 22, Kind::Interface, "Valve commands", None);
|
||||
for (allocation, item) in [(30, 20), (31, 21)] {
|
||||
rel(
|
||||
&mut p,
|
||||
allocation,
|
||||
RelationKind::InterfaceItemAllocation,
|
||||
22,
|
||||
item,
|
||||
);
|
||||
let RelationBody::InterfaceItemAllocation { transmission, .. } =
|
||||
&mut p.relations.get_mut(&id(allocation)).unwrap().body
|
||||
else {
|
||||
panic!()
|
||||
};
|
||||
*transmission = TransmissionProtocol::Synchronous;
|
||||
}
|
||||
rel(&mut p, 32, RelationKind::InterfaceRequirement, 12, 22);
|
||||
rel(&mut p, 33, RelationKind::InterfaceProvision, 13, 22);
|
||||
rel(&mut p, 34, RelationKind::BoundaryExchange, 12, 13);
|
||||
p.relations.get_mut(&id(34)).unwrap().body = RelationBody::BoundaryExchange {
|
||||
source: id(12),
|
||||
target: id(13),
|
||||
oriented: false,
|
||||
items: vec![id(20), id(21)],
|
||||
};
|
||||
add(&mut p, 40, Kind::Scenario, "Reset then read status", None);
|
||||
body(
|
||||
&mut p,
|
||||
40,
|
||||
SharedBody::Scenario {
|
||||
viewpoint: ScenarioViewpoint::BoundaryInterface,
|
||||
context: None,
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
steps: vec![id(43), id(44)],
|
||||
},
|
||||
);
|
||||
for (line, participant) in [(41, 10), (42, 11)] {
|
||||
add(
|
||||
&mut p,
|
||||
line,
|
||||
Kind::Lifeline,
|
||||
"Service participant",
|
||||
Some(id(40)),
|
||||
);
|
||||
body(
|
||||
&mut p,
|
||||
line,
|
||||
SharedBody::Lifeline {
|
||||
represented: Some(id(participant)),
|
||||
},
|
||||
);
|
||||
}
|
||||
for (call, name) in [(43, "Call Reset"), (44, "Call ReadStatus")] {
|
||||
add(&mut p, call, Kind::Occurrence, name, Some(id(40)));
|
||||
body(
|
||||
&mut p,
|
||||
call,
|
||||
SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange {
|
||||
exchange: Some(id(34)),
|
||||
sender: Some(id(41)),
|
||||
receiver: Some(id(42)),
|
||||
message_kind: MessageKind::Call,
|
||||
reply_to: None,
|
||||
values: vec![],
|
||||
},
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
valid(&p);
|
||||
p
|
||||
}
|
||||
fn refined(p: &Project) -> Project {
|
||||
let mut q = p.clone();
|
||||
for (work, owner, name) in [
|
||||
(50, 10, "Issue service request"),
|
||||
(51, 11, "Execute service request"),
|
||||
] {
|
||||
add(&mut q, work, Kind::Function, name, None);
|
||||
rel(&mut q, work + 10, RelationKind::Allocation, owner, work);
|
||||
}
|
||||
for (item, output, input, exchange) in [(20, 70, 71, 80), (21, 72, 73, 81)] {
|
||||
add(&mut q, output, Kind::Port, "Operation output", Some(id(50)));
|
||||
add(&mut q, input, Kind::Port, "Operation input", Some(id(51)));
|
||||
body(
|
||||
&mut q,
|
||||
output,
|
||||
SharedBody::Port {
|
||||
contract: PortContract::FunctionOutput {
|
||||
outgoing_items: vec![id(item)],
|
||||
},
|
||||
legacy: None,
|
||||
},
|
||||
);
|
||||
body(
|
||||
&mut q,
|
||||
input,
|
||||
SharedBody::Port {
|
||||
contract: PortContract::FunctionInput {
|
||||
incoming_items: vec![id(item)],
|
||||
},
|
||||
legacy: None,
|
||||
},
|
||||
);
|
||||
rel(&mut q, exchange, RelationKind::FunctionalExchange, 50, 51);
|
||||
q.relations.get_mut(&id(exchange)).unwrap().body = RelationBody::FunctionalExchange {
|
||||
source: id(50),
|
||||
target: id(51),
|
||||
source_port: id(output),
|
||||
target_port: id(input),
|
||||
items: vec![id(item)],
|
||||
};
|
||||
rel(
|
||||
&mut q,
|
||||
output + 20,
|
||||
RelationKind::PortAllocation,
|
||||
12,
|
||||
output,
|
||||
);
|
||||
rel(&mut q, input + 20, RelationKind::PortAllocation, 13, input);
|
||||
rel(
|
||||
&mut q,
|
||||
exchange + 20,
|
||||
RelationKind::ExchangeAllocation,
|
||||
34,
|
||||
exchange,
|
||||
);
|
||||
}
|
||||
set_exchange(&mut q, 43, id(80));
|
||||
set_exchange(&mut q, 44, id(81));
|
||||
valid(&q);
|
||||
q
|
||||
}
|
||||
fn write(p: &Project, out: &Path, name: &str) {
|
||||
let source = language::format(p);
|
||||
let bundle = source_project::SourceBundle {
|
||||
entry: "entry.arcadia".into(),
|
||||
files: BTreeMap::from([("entry.arcadia".into(), source.clone())]),
|
||||
};
|
||||
assert_eq!(
|
||||
&source_project::compile_bundle(&bundle, Default::default())
|
||||
.unwrap()
|
||||
.decoded
|
||||
.project,
|
||||
p
|
||||
);
|
||||
assert_eq!(
|
||||
&serde_json::from_value::<Project>(serde_json::to_value(p).unwrap()).unwrap(),
|
||||
p
|
||||
);
|
||||
fs::write(out.join(format!("{name}.arcadia")), source).unwrap();
|
||||
fs::write(
|
||||
out.join(format!("{name}.json")),
|
||||
serde_json::to_vec_pretty(p).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
fn main() {
|
||||
let directory = std::env::args().nth(1).expect("probe OUTPUT_DIRECTORY");
|
||||
let out = Path::new(&directory);
|
||||
fs::create_dir_all(out).unwrap();
|
||||
let p = initial();
|
||||
write(&p, out, "boundary-only");
|
||||
assert_eq!(
|
||||
message(&p, 43),
|
||||
message(&p, 44),
|
||||
"Only prose distinguishes the intended operations"
|
||||
);
|
||||
assert!(!references::element_references(&p.elements[&id(43)]).contains(&id(20)));
|
||||
assert!(!references::element_references(&p.elements[&id(44)]).contains(&id(21)));
|
||||
for target in [20, 30] {
|
||||
let mut invalid = p.clone();
|
||||
set_exchange(&mut invalid, 43, id(target));
|
||||
assert!(
|
||||
validate(&invalid)
|
||||
.iter()
|
||||
.any(|i| i.code == "occurrence_exchange" && i.element == Some(id(43)))
|
||||
);
|
||||
}
|
||||
let mut proposed = serde_json::to_value(message(&p, 43)).unwrap();
|
||||
proposed["Exchange"]["operation"] = serde_json::to_value(id(30)).unwrap();
|
||||
assert!(
|
||||
serde_json::from_value::<OccurrenceBody>(proposed)
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.contains("unknown field")
|
||||
);
|
||||
let mut session = Session::new(p.clone());
|
||||
let mut reset = p.elements[&id(20)].clone();
|
||||
reset.name = "EmergencyReset".into();
|
||||
session
|
||||
.execute(Command::EditElement(id(20), reset))
|
||||
.unwrap();
|
||||
assert_eq!(message(session.project(), 43), message(&p, 43));
|
||||
session.execute(Command::Delete(id(20))).unwrap();
|
||||
assert_eq!(message(session.project(), 43), message(&p, 43));
|
||||
assert!(session.project().elements.contains_key(&id(43)));
|
||||
valid(session.project());
|
||||
write(session.project(), out, "after-reset-deletion");
|
||||
let q = refined(&p);
|
||||
write(&q, out, "explicit-functional-refinement");
|
||||
assert_eq!(q.relations[&id(34)], p.relations[&id(34)]);
|
||||
assert_ne!(message(&q, 43), message(&q, 44));
|
||||
for n in [43, 44] {
|
||||
assert!(
|
||||
references::element_references(&q.elements[&id(n)])
|
||||
.iter()
|
||||
.any(|v| [id(80), id(81)].contains(v))
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"PASS validated boundary-only model: {} elements / {} relations, two parameterless Operations",
|
||||
p.elements.len(),
|
||||
p.relations.len()
|
||||
);
|
||||
println!(
|
||||
"PASS identical typed Calls; item/allocation exchange substitutions rejected; operation DTO field rejected"
|
||||
);
|
||||
println!(
|
||||
"PASS operation rename/delete leaves intended Call's typed body unchanged; Call remains structurally valid"
|
||||
);
|
||||
println!(
|
||||
"PASS distinct FE Calls require this explicit refinement: +{} elements / +{} relations; same original boundary",
|
||||
q.elements.len() - p.elements.len(),
|
||||
q.relations.len() - p.relations.len()
|
||||
);
|
||||
println!("PASS all three complete source/JSON round trips; no production mutation");
|
||||
}
|
||||
Reference in New Issue
Block a user