Establish OA/SA prototype baseline and first-release work
This commit is contained in:
@@ -0,0 +1,710 @@
|
||||
use crate::*;
|
||||
use std::collections::BTreeMap;
|
||||
fn add(p: &mut Project, kind: Kind, layer: Layer, name: &str, parent: Option<Id>) -> Id {
|
||||
p.add_element(kind, layer, name, parent).unwrap()
|
||||
}
|
||||
fn occurrence(
|
||||
p: &mut Project,
|
||||
owner: Id,
|
||||
layer: Layer,
|
||||
name: &str,
|
||||
exchange: Id,
|
||||
sender: Option<Id>,
|
||||
receiver: Option<Id>,
|
||||
) -> Id {
|
||||
let id = add(p, Kind::Occurrence, layer, name, Some(owner));
|
||||
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange {
|
||||
exchange: Some(exchange),
|
||||
sender,
|
||||
receiver,
|
||||
message_kind: MessageKind::Asynchronous,
|
||||
reply_to: None,
|
||||
values: vec![],
|
||||
},
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
};
|
||||
p.elements
|
||||
.get_mut(&owner)
|
||||
.unwrap()
|
||||
.steps_mut()
|
||||
.unwrap()
|
||||
.push(id);
|
||||
id
|
||||
}
|
||||
fn port(p: &mut Project, owner: Id, name: &str, direction: PortDirection, interface: Id) -> Id {
|
||||
let id = add(p, Kind::Port, Layer::Sa, name, Some(owner));
|
||||
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Port {
|
||||
direction,
|
||||
interface: Some(interface),
|
||||
};
|
||||
id
|
||||
}
|
||||
fn diagram(
|
||||
p: &mut Project,
|
||||
layer: Layer,
|
||||
projection: ProjectionKind,
|
||||
name: &str,
|
||||
elements: &[Id],
|
||||
) -> Id {
|
||||
let id = Id::new_v4();
|
||||
let alias = p.fresh_alias(name, "diagram", id);
|
||||
p.diagrams.insert(
|
||||
id,
|
||||
Diagram {
|
||||
id,
|
||||
alias,
|
||||
name: name.into(),
|
||||
description: "A focused projection of the shared engineering model.".into(),
|
||||
layer,
|
||||
projection,
|
||||
nodes: BTreeMap::new(),
|
||||
},
|
||||
);
|
||||
for (index, e) in elements.iter().enumerate() {
|
||||
p.place(
|
||||
id,
|
||||
*e,
|
||||
Position {
|
||||
x: 60. + (index % 4) as f32 * 280.,
|
||||
y: 70. + (index / 4) as f32 * 170.,
|
||||
},
|
||||
);
|
||||
}
|
||||
id
|
||||
}
|
||||
fn trace(p: &mut Project, oa: Id, sa: Id, kind: TraceKind) {
|
||||
let id = Id::new_v4();
|
||||
let name = format!("{} ↔ {}", p.named(oa), p.named(sa));
|
||||
let alias = p.fresh_alias(&format!("trace_{}", p.named(oa)), "trace", id);
|
||||
p.traces.insert(
|
||||
id,
|
||||
Trace {
|
||||
id,
|
||||
alias,
|
||||
name,
|
||||
description: "Reviewed OA/SA correspondence in the coastal rescue example.".into(),
|
||||
oa,
|
||||
sa,
|
||||
kind,
|
||||
baseline: Some(TraceBaseline {
|
||||
oa: p.properties(oa).unwrap(),
|
||||
sa: p.properties(sa).unwrap(),
|
||||
}),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn coastal_rescue() -> Project {
|
||||
let mut p = Project::empty();
|
||||
p.name = "Coastal rescue — operational need to system behavior".into();
|
||||
p.description="A worked OA/SA example. Compare allocation and functional decomposition views; inspect traces, repeated scenario messages, interface contracts, degraded modes, and external engineering artifacts.".into();
|
||||
let coordination = add(&mut p, Kind::Entity, Layer::Oa, "Rescue coordination", None);
|
||||
let vessel = add(&mut p, Kind::Actor, Layer::Oa, "Vessel in distress", None);
|
||||
let role = add(&mut p, Kind::Role, Layer::Oa, "Incident coordinator", None);
|
||||
let oa_parent = add(
|
||||
&mut p,
|
||||
Kind::Activity,
|
||||
Layer::Oa,
|
||||
"Respond to distress",
|
||||
None,
|
||||
);
|
||||
let report = add(
|
||||
&mut p,
|
||||
Kind::Activity,
|
||||
Layer::Oa,
|
||||
"Report distress",
|
||||
Some(oa_parent),
|
||||
);
|
||||
let assess = add(
|
||||
&mut p,
|
||||
Kind::Activity,
|
||||
Layer::Oa,
|
||||
"Assess incident",
|
||||
Some(oa_parent),
|
||||
);
|
||||
let oa_cap = add(
|
||||
&mut p,
|
||||
Kind::Capability,
|
||||
Layer::Oa,
|
||||
"Protect life at sea",
|
||||
None,
|
||||
);
|
||||
let process = add(
|
||||
&mut p,
|
||||
Kind::Process,
|
||||
Layer::Oa,
|
||||
"Distress to response",
|
||||
None,
|
||||
);
|
||||
let oa_item = add(
|
||||
&mut p,
|
||||
Kind::ExchangeItem,
|
||||
Layer::Oa,
|
||||
"Distress report",
|
||||
None,
|
||||
);
|
||||
p.relate(RelationKind::Allocation, "Reports distress", vessel, report);
|
||||
p.relate(
|
||||
RelationKind::Allocation,
|
||||
"Assesses incident",
|
||||
coordination,
|
||||
assess,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::RoleRealization,
|
||||
"Fulfils coordinator role",
|
||||
coordination,
|
||||
role,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::RoleAssignment,
|
||||
"Coordinates assessment",
|
||||
role,
|
||||
assess,
|
||||
);
|
||||
let interaction = p.relate(RelationKind::Interaction, "Distress signal", report, assess);
|
||||
if let RelationBody::Interaction { items, .. } =
|
||||
&mut p.relations.get_mut(&interaction).unwrap().body
|
||||
{
|
||||
items.push(oa_item);
|
||||
}
|
||||
p.relate(
|
||||
RelationKind::CapabilityInvolvement,
|
||||
"Operational contribution",
|
||||
assess,
|
||||
oa_cap,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::ChainInvolvement,
|
||||
"Operational realization",
|
||||
process,
|
||||
oa_cap,
|
||||
);
|
||||
let oa_occurrence = occurrence(
|
||||
&mut p,
|
||||
process,
|
||||
Layer::Oa,
|
||||
"Distress received",
|
||||
interaction,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let system = add(
|
||||
&mut p,
|
||||
Kind::System,
|
||||
Layer::Sa,
|
||||
"Rescue coordination system",
|
||||
None,
|
||||
);
|
||||
let actor = add(
|
||||
&mut p,
|
||||
Kind::SystemActor,
|
||||
Layer::Sa,
|
||||
"Vessel communication service",
|
||||
None,
|
||||
);
|
||||
let sa_parent = add(
|
||||
&mut p,
|
||||
Kind::Function,
|
||||
Layer::Sa,
|
||||
"Manage distress response",
|
||||
None,
|
||||
);
|
||||
let transmit = add(
|
||||
&mut p,
|
||||
Kind::Function,
|
||||
Layer::Sa,
|
||||
"Transmit distress report",
|
||||
Some(sa_parent),
|
||||
);
|
||||
let coordinate = add(
|
||||
&mut p,
|
||||
Kind::Function,
|
||||
Layer::Sa,
|
||||
"Classify incident and alert operator",
|
||||
Some(sa_parent),
|
||||
);
|
||||
let sa_cap = add(
|
||||
&mut p,
|
||||
Kind::SystemCapability,
|
||||
Layer::Sa,
|
||||
"Coordinate rescue response",
|
||||
None,
|
||||
);
|
||||
let chain = add(
|
||||
&mut p,
|
||||
Kind::FunctionalChain,
|
||||
Layer::Sa,
|
||||
"Digital distress handling",
|
||||
None,
|
||||
);
|
||||
let mission = add(
|
||||
&mut p,
|
||||
Kind::Mission,
|
||||
Layer::Sa,
|
||||
"Support timely rescue decisions",
|
||||
None,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::Allocation,
|
||||
"Vessel transmission",
|
||||
actor,
|
||||
transmit,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::Allocation,
|
||||
"System assessment",
|
||||
system,
|
||||
coordinate,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::CapabilityInvolvement,
|
||||
"System capability support",
|
||||
coordinate,
|
||||
sa_cap,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::ChainInvolvement,
|
||||
"Functional chain support",
|
||||
chain,
|
||||
sa_cap,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::MissionCapability,
|
||||
"Mission capability",
|
||||
mission,
|
||||
sa_cap,
|
||||
);
|
||||
let information = add(
|
||||
&mut p,
|
||||
Kind::ExchangeItem,
|
||||
Layer::Sa,
|
||||
"Digital distress report",
|
||||
None,
|
||||
);
|
||||
let material = add(
|
||||
&mut p,
|
||||
Kind::ExchangeItem,
|
||||
Layer::Sa,
|
||||
"Rescue supply package",
|
||||
None,
|
||||
);
|
||||
*p.elements.get_mut(&material).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem {
|
||||
kind: ExchangeItemKind::Material,
|
||||
};
|
||||
let energy = add(
|
||||
&mut p,
|
||||
Kind::ExchangeItem,
|
||||
Layer::Sa,
|
||||
"Backup electrical energy",
|
||||
None,
|
||||
);
|
||||
*p.elements.get_mut(&energy).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem {
|
||||
kind: ExchangeItemKind::Energy,
|
||||
};
|
||||
let field = add(
|
||||
&mut p,
|
||||
Kind::DataField,
|
||||
Layer::Sa,
|
||||
"Reported latitude",
|
||||
Some(information),
|
||||
);
|
||||
*p.elements.get_mut(&field).unwrap().shared_mut().unwrap() = SharedBody::DataField {
|
||||
type_ref: TypeRef::Real,
|
||||
minimum: 1,
|
||||
maximum: Some(1),
|
||||
unit: "degrees north".into(),
|
||||
};
|
||||
let interface = add(
|
||||
&mut p,
|
||||
Kind::Interface,
|
||||
Layer::Sa,
|
||||
"Distress reporting interface",
|
||||
None,
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&interface)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Interface {
|
||||
items: vec![information],
|
||||
};
|
||||
let vessel_port = port(
|
||||
&mut p,
|
||||
actor,
|
||||
"Vessel reporting output",
|
||||
PortDirection::Out,
|
||||
interface,
|
||||
);
|
||||
let system_port = port(
|
||||
&mut p,
|
||||
system,
|
||||
"System reporting input",
|
||||
PortDirection::In,
|
||||
interface,
|
||||
);
|
||||
let function_out = port(
|
||||
&mut p,
|
||||
transmit,
|
||||
"Report function output",
|
||||
PortDirection::Out,
|
||||
interface,
|
||||
);
|
||||
let function_in = port(
|
||||
&mut p,
|
||||
coordinate,
|
||||
"Assessment function input",
|
||||
PortDirection::In,
|
||||
interface,
|
||||
);
|
||||
let exchange = p.relate(
|
||||
RelationKind::FunctionalExchange,
|
||||
"Validated distress report",
|
||||
transmit,
|
||||
coordinate,
|
||||
);
|
||||
p.relations.get_mut(&exchange).unwrap().body = RelationBody::FunctionalExchange {
|
||||
source: transmit,
|
||||
target: coordinate,
|
||||
source_port: Some(function_out),
|
||||
target_port: Some(function_in),
|
||||
items: vec![information],
|
||||
};
|
||||
let boundary = p.relate(
|
||||
RelationKind::BoundaryExchange,
|
||||
"Distress reporting connection",
|
||||
vessel_port,
|
||||
system_port,
|
||||
);
|
||||
if let RelationBody::BoundaryExchange { items, .. } =
|
||||
&mut p.relations.get_mut(&boundary).unwrap().body
|
||||
{
|
||||
items.push(information);
|
||||
}
|
||||
p.relate(
|
||||
RelationKind::ExchangeAllocation,
|
||||
"Report carried by connection",
|
||||
boundary,
|
||||
exchange,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::InterfaceProvision,
|
||||
"Reporting service provided",
|
||||
actor,
|
||||
interface,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::InterfaceRequirement,
|
||||
"Reporting service required",
|
||||
system,
|
||||
interface,
|
||||
);
|
||||
let sa_occurrence = occurrence(
|
||||
&mut p,
|
||||
chain,
|
||||
Layer::Sa,
|
||||
"Report handled",
|
||||
exchange,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let scenario = add(
|
||||
&mut p,
|
||||
Kind::Scenario,
|
||||
Layer::Sa,
|
||||
"Repeated distress reports",
|
||||
None,
|
||||
);
|
||||
*p.elements.get_mut(&scenario).unwrap().shared_mut().unwrap() = SharedBody::Scenario {
|
||||
viewpoint: ScenarioViewpoint::BoundaryInterface,
|
||||
context: Some(sa_cap),
|
||||
precondition: "A vessel has detected an emergency and the reporting service is available."
|
||||
.into(),
|
||||
postcondition: "The operator has a classified incident with the latest vessel report."
|
||||
.into(),
|
||||
steps: vec![],
|
||||
};
|
||||
let vessel_line = add(
|
||||
&mut p,
|
||||
Kind::Lifeline,
|
||||
Layer::Sa,
|
||||
"Vessel service lifeline",
|
||||
Some(scenario),
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&vessel_line)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Lifeline {
|
||||
represented: Some(actor),
|
||||
};
|
||||
let system_line = add(
|
||||
&mut p,
|
||||
Kind::Lifeline,
|
||||
Layer::Sa,
|
||||
"Coordination system lifeline",
|
||||
Some(scenario),
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&system_line)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Lifeline {
|
||||
represented: Some(system),
|
||||
};
|
||||
occurrence(
|
||||
&mut p,
|
||||
scenario,
|
||||
Layer::Sa,
|
||||
"Initial distress report",
|
||||
exchange,
|
||||
Some(vessel_line),
|
||||
Some(system_line),
|
||||
);
|
||||
let update = occurrence(
|
||||
&mut p,
|
||||
scenario,
|
||||
Layer::Sa,
|
||||
"Updated distress report",
|
||||
exchange,
|
||||
Some(vessel_line),
|
||||
Some(system_line),
|
||||
);
|
||||
if let Some(SharedBody::Occurrence {
|
||||
precondition,
|
||||
postcondition,
|
||||
..
|
||||
}) = p.elements.get_mut(&update).unwrap().shared_mut()
|
||||
{
|
||||
*precondition = "The vessel has more recent position information.".into();
|
||||
*postcondition =
|
||||
"The incident position is refreshed without creating a second incident.".into();
|
||||
}
|
||||
|
||||
let machine = add(
|
||||
&mut p,
|
||||
Kind::StateMachine,
|
||||
Layer::Sa,
|
||||
"Reporting operation modes",
|
||||
Some(system),
|
||||
);
|
||||
let region = add(
|
||||
&mut p,
|
||||
Kind::Region,
|
||||
Layer::Sa,
|
||||
"Reporting quality region",
|
||||
Some(machine),
|
||||
);
|
||||
let initial = add(
|
||||
&mut p,
|
||||
Kind::Initial,
|
||||
Layer::Sa,
|
||||
"Start reporting",
|
||||
Some(region),
|
||||
);
|
||||
let normal = add(
|
||||
&mut p,
|
||||
Kind::Mode,
|
||||
Layer::Sa,
|
||||
"Normal reporting",
|
||||
Some(region),
|
||||
);
|
||||
let degraded = add(
|
||||
&mut p,
|
||||
Kind::Mode,
|
||||
Layer::Sa,
|
||||
"Degraded reporting",
|
||||
Some(region),
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::StateTransition,
|
||||
"Initialize reporting",
|
||||
initial,
|
||||
normal,
|
||||
);
|
||||
let degrade = p.relate(
|
||||
RelationKind::StateTransition,
|
||||
"Signal degraded",
|
||||
normal,
|
||||
degraded,
|
||||
);
|
||||
let timeout = add(&mut p, Kind::Event, Layer::Sa, "Report timeout", None);
|
||||
*p.elements.get_mut(&timeout).unwrap().shared_mut().unwrap() = SharedBody::Event {
|
||||
body: EventBody::Time {
|
||||
kind: TimeEventKind::After,
|
||||
expression: "30 s".into(),
|
||||
},
|
||||
};
|
||||
p.relations.get_mut(°rade).unwrap().body = RelationBody::StateTransition {
|
||||
source: normal,
|
||||
target: degraded,
|
||||
transition_kind: TransitionKind::External,
|
||||
triggers: vec![timeout],
|
||||
effects: vec![],
|
||||
trigger: "report_timeout".into(),
|
||||
guard: "seconds_since_last_report > 30".into(),
|
||||
effect: "Flag the incident and request an operator review.".into(),
|
||||
};
|
||||
let restore = p.relate(
|
||||
RelationKind::StateTransition,
|
||||
"Signal restored",
|
||||
degraded,
|
||||
normal,
|
||||
);
|
||||
let report_received = add(
|
||||
&mut p,
|
||||
Kind::Event,
|
||||
Layer::Sa,
|
||||
"Validated report received",
|
||||
None,
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&report_received)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Event {
|
||||
body: EventBody::Signal {
|
||||
items: vec![information],
|
||||
},
|
||||
};
|
||||
p.relations.get_mut(&restore).unwrap().body = RelationBody::StateTransition {
|
||||
source: degraded,
|
||||
target: normal,
|
||||
transition_kind: TransitionKind::External,
|
||||
triggers: vec![report_received],
|
||||
effects: vec![],
|
||||
trigger: "validated_report_received".into(),
|
||||
guard: "position_is_current".into(),
|
||||
effect: "Clear the degraded-reporting alert.".into(),
|
||||
};
|
||||
p.relate(
|
||||
RelationKind::Availability,
|
||||
"Assessment in normal mode",
|
||||
coordinate,
|
||||
normal,
|
||||
);
|
||||
p.relate(
|
||||
RelationKind::Availability,
|
||||
"Assessment in degraded mode",
|
||||
coordinate,
|
||||
degraded,
|
||||
);
|
||||
let action = p.relate(
|
||||
RelationKind::StateAction,
|
||||
"Review on degraded entry",
|
||||
degraded,
|
||||
coordinate,
|
||||
);
|
||||
p.relations.get_mut(&action).unwrap().body = RelationBody::StateAction {
|
||||
source: degraded,
|
||||
target: coordinate,
|
||||
phase: StateActionKind::Entry,
|
||||
};
|
||||
let constraint = add(
|
||||
&mut p,
|
||||
Kind::Constraint,
|
||||
Layer::Sa,
|
||||
"Response time budget",
|
||||
None,
|
||||
);
|
||||
*p.elements
|
||||
.get_mut(&constraint)
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Constraint {
|
||||
expression: "incident_alert_latency <= 120 s".into(),
|
||||
constrained: vec![coordinate, exchange],
|
||||
};
|
||||
for (owner, name, uri, revision, relation) in [
|
||||
(
|
||||
coordinate,
|
||||
"Response implementation",
|
||||
"git+https://example.org/rescue/control?path=src/response.rs",
|
||||
"design-v2",
|
||||
ArtifactRelation::Implements,
|
||||
),
|
||||
(
|
||||
system,
|
||||
"Console enclosure CAD",
|
||||
"cad://rescue-console/enclosure.step",
|
||||
"B.3",
|
||||
ArtifactRelation::References,
|
||||
),
|
||||
] {
|
||||
let id = Id::new_v4();
|
||||
let alias = p.fresh_alias(name, "artifact", id);
|
||||
p.artifact_links.insert(id,ArtifactLink{id,alias,name:name.into(),description:"Illustrative external engineering reference; opening a model does not fetch it.".into(),owner,relation,uri:uri.into(),revision:Some(revision.into()),fragment:None});
|
||||
}
|
||||
for (oa, sa, kind) in [
|
||||
(coordination, system, TraceKind::Participant),
|
||||
(vessel, actor, TraceKind::Participant),
|
||||
(oa_parent, sa_parent, TraceKind::Refinement),
|
||||
(report, transmit, TraceKind::Refinement),
|
||||
(assess, coordinate, TraceKind::Refinement),
|
||||
(oa_cap, sa_cap, TraceKind::Capability),
|
||||
(process, chain, TraceKind::Sequence),
|
||||
(oa_occurrence, sa_occurrence, TraceKind::Scenario),
|
||||
(interaction, exchange, TraceKind::Exchange),
|
||||
(oa_item, information, TraceKind::Item),
|
||||
] {
|
||||
trace(&mut p, oa, sa, kind);
|
||||
}
|
||||
let oa_view = diagram(
|
||||
&mut p,
|
||||
Layer::Oa,
|
||||
ProjectionKind::Allocation,
|
||||
"OA participants and activities",
|
||||
&[coordination, vessel, report, assess],
|
||||
);
|
||||
let sa_view = diagram(
|
||||
&mut p,
|
||||
Layer::Sa,
|
||||
ProjectionKind::Allocation,
|
||||
"SA system boundary and functions",
|
||||
&[system, actor, transmit, coordinate],
|
||||
);
|
||||
let functions = diagram(
|
||||
&mut p,
|
||||
Layer::Sa,
|
||||
ProjectionKind::FunctionalDataflow,
|
||||
"SA functional decomposition",
|
||||
&[sa_parent, transmit, coordinate],
|
||||
);
|
||||
let oa_capabilities = diagram(
|
||||
&mut p,
|
||||
Layer::Oa,
|
||||
ProjectionKind::Capabilities,
|
||||
"OA capabilities and participants",
|
||||
&[coordination, vessel, role, oa_cap],
|
||||
);
|
||||
let sa_capabilities = diagram(
|
||||
&mut p,
|
||||
Layer::Sa,
|
||||
ProjectionKind::Capabilities,
|
||||
"SA missions and capabilities",
|
||||
&[system, actor, mission, sa_cap],
|
||||
);
|
||||
let activities = diagram(
|
||||
&mut p,
|
||||
Layer::Oa,
|
||||
ProjectionKind::FunctionalDataflow,
|
||||
"OA activity dataflow",
|
||||
&[oa_parent, report, assess],
|
||||
);
|
||||
execute_batch(
|
||||
&p,
|
||||
&[
|
||||
Command::Layout(oa_view),
|
||||
Command::Layout(sa_view),
|
||||
Command::Layout(functions),
|
||||
Command::Layout(oa_capabilities),
|
||||
Command::Layout(sa_capabilities),
|
||||
Command::Layout(activities),
|
||||
],
|
||||
)
|
||||
.expect("The bundled OA/SA example is structurally valid")
|
||||
}
|
||||
Reference in New Issue
Block a user