Files
ARCADIA-Studio/harness/fixtures/release1-native/communication-means/generate-communication-means.rs
T

875 lines
27 KiB
Rust

//! Direct core fixture/oracle helper; no native input or accepted-file mutation.
use oa_core::{transitions::*, view_options::ViewOptions, *};
use std::{
collections::{BTreeMap, BTreeSet},
fs,
path::Path,
};
fn id(n: u32) -> Id {
format!("c6060906-0000-4000-8000-{n:012x}").parse().unwrap()
}
fn element(
p: &mut Project,
n: u32,
kind: Kind,
layer: Layer,
name: &str,
parent: Option<Id>,
) -> Id {
let key = id(n);
p.elements.insert(
key,
Element {
id: key,
alias: format!("cm_{n}"),
name: name.into(),
description: String::new(),
parent,
body: if kind == Kind::Port {
ElementBody::shared_in(
layer,
SharedBody::Port {
contract: PortContract::Component {
kind: ComponentPortKind::Standard,
orientation: ComponentPortOrientation::Unset,
},
legacy: None,
},
)
} else {
ElementBody::new(kind, layer).unwrap()
},
},
);
key
}
fn relation(p: &mut Project, n: u32, kind: RelationKind, name: &str, source: Id, target: Id) -> Id {
let key = id(n);
p.relations.insert(
key,
Relation {
id: key,
alias: format!("cm_{n}"),
name: name.into(),
description: String::new(),
body: RelationBody::new(kind, source, target),
},
);
key
}
fn trace(p: &mut Project, n: u32, oa: Id, sa: Id, kind: TraceKind, name: &str) {
p.traces.insert(
id(n),
Trace {
id: id(n),
alias: format!("cm_{n}"),
name: name.into(),
description: String::new(),
oa,
sa,
kind,
baseline: None,
},
);
}
fn diagram(p: &mut Project, n: u32, layer: Layer, name: &str, rows: &[(Id, f32, f32, f32, f32)]) {
let nodes = rows
.iter()
.enumerate()
.map(|(i, &(element, x, y, width, height))| {
let key = id(n + 10 + i as u32);
(
key,
NodePlacement {
id: key,
alias: format!("cm_view_{}", n + i as u32),
element,
position: Position { x, y },
size: Size { width, height },
collapsed: false,
port_anchors: vec![],
},
)
})
.collect();
p.diagrams.insert(
id(n),
Diagram {
id: id(n),
alias: format!("cm_diagram_{n}"),
name: name.into(),
description: String::new(),
layer,
projection: ProjectionKind::Overview,
view_options: ViewOptions::default(),
nodes,
routes: vec![],
},
);
}
fn port(p: &mut Project, n: u32, owner: Id, name: &str) -> Id {
let key = element(p, n, Kind::Port, Layer::Sa, name, Some(owner));
if let Some(SharedBody::Port { contract, .. }) = p.elements.get_mut(&key).unwrap().shared_mut()
{
*contract = PortContract::Component {
kind: ComponentPortKind::Standard,
orientation: ComponentPortOrientation::Unset,
};
} else {
panic!("port");
}
key
}
fn base() -> Project {
let mut p = Project::empty();
p.id = id(1);
p.alias = "communication_native".into();
p.name = "Rescue communication engineering".into();
let a = element(&mut p, 10, Kind::Entity, Layer::Oa, "Rescue team", None);
let b = element(&mut p, 11, Kind::Actor, Layer::Oa, "Control centre", None);
let x = element(
&mut p,
12,
Kind::Activity,
Layer::Oa,
"Report rescue status",
None,
);
let y = element(
&mut p,
13,
Kind::Activity,
Layer::Oa,
"Assess rescue status",
None,
);
relation(
&mut p,
20,
RelationKind::Allocation,
"Team performs reporting",
a,
x,
);
relation(
&mut p,
21,
RelationKind::Allocation,
"Control performs assessment",
b,
y,
);
relation(&mut p, 22, RelationKind::Interaction, "Rescue status", x, y);
let sa = element(&mut p, 30, Kind::System, Layer::Sa, "Rescue radio", None);
let sb = element(
&mut p,
31,
Kind::SystemActor,
Layer::Sa,
"Control radio",
None,
);
port(&mut p, 32, sa, "Existing team socket");
port(&mut p, 33, sb, "Existing control socket");
trace(
&mut p,
40,
a,
sa,
TraceKind::Participant,
"Team radio correspondence",
);
trace(
&mut p,
41,
b,
sb,
TraceKind::Participant,
"Control radio correspondence",
);
diagram(
&mut p,
1000,
Layer::Oa,
"Draw operational communication",
&[
(a, 80., 70., 250., 160.),
(b, 550., 70., 250., 160.),
(x, 80., 330., 250., 110.),
(y, 550., 330., 250., 110.),
],
);
diagram(
&mut p,
1100,
Layer::Sa,
"Explicit system endpoints",
&[(sa, 80., 140., 250., 170.), (sb, 550., 140., 250., 170.)],
);
valid(&p);
p
}
fn valid(p: &Project) {
validate_candidate(p).unwrap();
assert_eq!(language::parse(&language::format(p)).unwrap(), *p);
assert_eq!(
persistence::decode(&serde_json::to_vec(p).unwrap()).unwrap(),
*p
);
}
fn forward(p: &Project, mean: Id, create: Option<Id>) -> TransitionRequest {
let mut r = TransitionRequest::new(TransitionDirection::OaToSa, vec![mean]);
r.mappings = vec![
TransitionMapping {
source: id(10),
target: Some(id(30)),
target_kind: None,
},
TransitionMapping {
source: id(11),
target: Some(id(31)),
target_kind: None,
},
];
r.resolutions = r
.mappings
.iter()
.map(|m| FieldResolution {
source: m.source,
field: TransitionField::Name,
decision: FieldDecision::KeepTarget,
})
.collect();
let target_port = if let Some(key) = create {
let name = format!("{} · target port", p.named(mean));
CommunicationPortChoice::Create {
id: key,
alias: p.fresh_alias(&name, "port", key),
name,
description: String::new(),
kind: ComponentPortKind::Standard,
orientation: ComponentPortOrientation::Unset,
}
} else {
CommunicationPortChoice::Reuse { port: id(33) }
};
r.communication_endpoints
.push(CommunicationEndpointDecision {
source: mean,
source_port: CommunicationPortChoice::Reuse { port: id(32) },
target_port,
});
r
}
fn apply(p: &Project, r: &TransitionRequest) -> Project {
let plan = plan_transition(p, r).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
let mut q = apply_transition(p, &plan).unwrap();
// Desktop Workflow::Transition::Apply adds presentation after the core
// transition. Predict its policy from the plan and prior diagram, never
// adopt placement coordinates/size/alias from the observed native result.
let diagram = r
.destination_diagram
.or_else(|| {
q.diagrams
.values()
.find(|d| d.layer == r.direction.target_layer())
.map(|d| d.id)
})
.expect("These fixtures provide an existing destination-layer diagram");
for change in &plan.changes {
if q.elements.contains_key(&change.target) {
let count = q.diagrams[&diagram].nodes.len();
q.place(
diagram,
change.target,
Position {
x: 60. + (count % 3) as f32 * 280.,
y: 60. + (count / 3) as f32 * 150.,
},
);
}
}
valid(&q);
q
}
// Separate policy oracle: the explicit scenario root opens in Behavior, so no
// architecture placement is requested. Keep apply() above for the legacy binary.
fn apply_reverse_behavior(p: &Project, r: &TransitionRequest) -> Project {
assert_eq!(r.sources, vec![id(200)]);
assert_eq!(p.elements[&id(200)].kind(), Kind::Scenario);
assert!(r.destination_diagram.is_none());
let plan = plan_transition(p, r).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
let q = apply_transition(p, &plan).unwrap();
assert_eq!(
q.diagrams, p.diagrams,
"Behavior must preserve every saved view"
);
reverse_assertions(p, &q);
valid(&q);
q
}
fn generate_reverse_behavior(before: &Path, out: &Path) {
let disk =
source_files::load_project(before, source_project::ProjectLimits::default()).unwrap();
let compiled = source_project::compile_bundle(
&disk.reachable_bundle(),
source_project::ProjectLimits::default(),
)
.unwrap();
let p = &compiled.decoded.project;
let request = reverse(p);
let q = deterministic(p, apply_reverse_behavior(p, &request));
let updated =
source_project::update_bundle(&compiled, &q, source_project::ProjectLimits::default())
.unwrap();
fs::create_dir_all(out).unwrap();
write_bundle(out, &updated.bundle);
fs::write(
out.join("expected.json"),
serde_json::to_vec_pretty(&q).unwrap(),
)
.unwrap();
fs::write(
out.join("request.json"),
serde_json::to_vec_pretty(&request).unwrap(),
)
.unwrap();
println!("PASS generate-reverse-behavior: core candidate, all saved views unchanged");
}
// Only actual identity fields and references are remapped, never names/text/data.
fn remap(mut p: Project, mappings: &BTreeMap<Id, Id>) -> Project {
for (&old, &new) in mappings {
if old == new {
continue;
}
assert!(!p.contains(new));
references::remap_references(&mut p, old, new);
if let Some(mut e) = p.elements.remove(&old) {
e.id = new;
p.elements.insert(new, e);
}
if let Some(mut e) = p.relations.remove(&old) {
e.id = new;
p.relations.insert(new, e);
}
if let Some(mut e) = p.traces.remove(&old) {
e.id = new;
p.traces.insert(new, e);
}
if let Some(mut e) = p.artifact_links.remove(&old) {
e.id = new;
p.artifact_links.insert(new, e);
}
if let Some(mut e) = p.diagrams.remove(&old) {
e.id = new;
p.diagrams.insert(new, e);
}
for d in p.diagrams.values_mut() {
if let Some(mut e) = d.nodes.remove(&old) {
e.id = new;
d.nodes.insert(new, e);
}
}
}
valid(&p);
p
}
fn deterministic(base: &Project, q: Project) -> Project {
let mut fresh: Vec<_> = q
.identities()
.into_iter()
.filter(|(key, _)| !base.contains(*key))
.collect();
fresh.sort_by(|a, b| a.1.cmp(&b.1));
let mut next = 8000;
let map = fresh
.into_iter()
.map(|(old, _)| {
while base.contains(id(next)) {
next += 1;
}
let new = id(next);
next += 1;
(old, new)
})
.collect();
remap(q, &map)
}
fn emit(dir: &Path, name: &str, p: &Project) {
valid(p);
fs::write(dir.join(format!("{name}.arcadia")), language::format(p)).unwrap();
fs::write(
dir.join(format!("{name}.json")),
serde_json::to_vec_pretty(p).unwrap(),
)
.unwrap();
}
fn emit_after(dir: &Path, name: &str, base: &Project, after: &Project) {
let entry = "entry.arcadia".to_string();
let bundle = source_project::SourceBundle {
entry: entry.clone(),
files: BTreeMap::from([(entry.clone(), language::format(base))]),
};
let compiled =
source_project::compile_bundle(&bundle, source_project::ProjectLimits::default()).unwrap();
let updated =
source_project::update_bundle(&compiled, after, source_project::ProjectLimits::default())
.unwrap();
let output = dir.join(name);
write_bundle(&output, &updated.bundle);
fs::write(
output.join("expected.json"),
serde_json::to_vec_pretty(after).unwrap(),
)
.unwrap();
}
fn write_bundle(dir: &Path, bundle: &source_project::SourceBundle) {
fs::create_dir_all(dir).unwrap();
for (name, text) in &bundle.files {
let path = dir.join(name);
fs::create_dir_all(path.parent().unwrap()).unwrap();
fs::write(path, text).unwrap();
}
}
fn scenario(p: &mut Project, boundary: Id) -> Id {
let s = element(
p,
200,
Kind::Scenario,
Layer::Sa,
"Reverse rescue request",
None,
);
if let Some(SharedBody::Scenario { viewpoint, .. }) =
p.elements.get_mut(&s).unwrap().shared_mut()
{
*viewpoint = ScenarioViewpoint::BoundaryInterface;
}
let a = element(
p,
201,
Kind::Lifeline,
Layer::Sa,
"Team radio lifeline",
Some(s),
);
let b = element(
p,
202,
Kind::Lifeline,
Layer::Sa,
"Control radio lifeline",
Some(s),
);
for (line, owner) in [(a, id(30)), (b, id(31))] {
*p.elements.get_mut(&line).unwrap().shared_mut().unwrap() = SharedBody::Lifeline {
represented: Some(owner),
};
}
for (n, name, sender, receiver, kind, reply_to) in [
(203, "Request rescue report", b, a, MessageKind::Call, None),
(
204,
"Reply with rescue report",
a,
b,
MessageKind::Reply,
Some(id(203)),
),
] {
let e = element(p, n, Kind::Occurrence, Layer::Sa, name, Some(s));
*p.elements.get_mut(&e).unwrap().shared_mut().unwrap() = SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
exchange: Some(boundary),
sender: Some(sender),
receiver: Some(receiver),
message_kind: kind,
reply_to,
values: vec![],
},
precondition: String::new(),
postcondition: String::new(),
};
}
*p.elements.get_mut(&s).unwrap().steps_mut().unwrap() = vec![id(203), id(204)];
valid(p);
s
}
fn reverse(p: &Project) -> TransitionRequest {
let mean = id(50);
let boundary = p.traces.values().find(|t| t.oa == mean).unwrap().sa;
let mut r = TransitionRequest::new(TransitionDirection::SaToOa, vec![id(200)]);
r.scope = DependencyScope::Subtree;
r.mappings = [(boundary, mean), (id(30), id(10)), (id(31), id(11))]
.into_iter()
.map(|(source, target)| TransitionMapping {
source,
target: Some(target),
target_kind: None,
})
.collect();
r.resolutions = r
.mappings
.iter()
.map(|m| FieldResolution {
source: m.source,
field: TransitionField::Name,
decision: FieldDecision::KeepTarget,
})
.collect();
r
}
fn reverse_assertions(p: &Project, q: &Project) {
use validation::MessageEndpointResolution as R;
let mapped = |source| q.traces.values().find(|t| t.sa == source).unwrap().oa;
let body = |id| match q.elements[&id].shared().unwrap() {
SharedBody::Occurrence { body, .. } => body,
_ => panic!(),
};
let call = mapped(id(203));
let reply = mapped(id(204));
assert_eq!(
validation::resolve_message_endpoints(q, body(call)),
R::Resolved {
source: id(11),
target: id(10)
}
);
assert_eq!(
validation::resolve_message_endpoints(q, body(reply)),
R::Resolved {
source: id(10),
target: id(11)
}
);
assert!(matches!(body(reply),OccurrenceBody::Exchange{reply_to:Some(id),..} if *id==call));
for key in [id(200), id(201), id(202), id(203), id(204)] {
assert_eq!(p.elements[&key], q.elements[&key]);
}
assert_eq!(
q.elements
.values()
.filter(|e| e.layer() == Layer::Oa && e.kind() == Kind::Port)
.count(),
0
);
}
fn generate(dir: &Path) {
fs::create_dir_all(dir).unwrap();
let p = base();
emit(dir, "cm-draw", &p);
let mut ready = p.clone();
relation(
&mut ready,
50,
RelationKind::CommunicationMean,
"Rescue radio channel",
id(10),
id(11),
);
valid(&ready);
emit(dir, "cm-transition-ready", &ready);
for (label, created) in [("reuse", None), ("create", Some(id(60)))] {
let r = forward(&ready, id(50), created);
let q = apply(&ready, &r);
let q = deterministic(&ready, q);
emit_after(dir, &format!("cm-{label}.after-template"), &ready, &q);
fs::write(
dir.join(format!("cm-{label}.request.json")),
serde_json::to_vec_pretty(&r).unwrap(),
)
.unwrap();
}
let mut p = deterministic(&ready, apply(&ready, &forward(&ready, id(50), None)));
let boundary = p.traces.values().find(|t| t.oa == id(50)).unwrap().sa;
scenario(&mut p, boundary);
emit(dir, "cm-reverse-scenario", &p);
let r = reverse(&p);
let q = apply(&p, &r);
reverse_assertions(&p, &q);
let q = deterministic(&p, q);
emit_after(dir, "cm-reverse-scenario.after-template", &p, &q);
fs::write(
dir.join("cm-reverse-scenario.request.json"),
serde_json::to_vec_pretty(&r).unwrap(),
)
.unwrap();
let manifest = serde_json::json!({"schema":6,"native_status":"pending","project":id(1),"oa_participants":[id(10),id(11)],"oa_activities":[id(12),id(13)],"interaction":id(22),"sa_participants":[id(30),id(31)],"existing_ports":[id(32),id(33)],"participant_traces":[id(40),id(41)],"draw_diagram":id(1000),"system_diagram":id(1100),"template_mean":id(50),"reverse_scenario":id(200),"reverse_call":id(203),"correlated_reply":id(204),"templates":"Generated target/trace UUIDs are reference examples, not native exact expectations. Use check with native before/after sources."});
fs::write(
dir.join("manifest.json"),
serde_json::to_vec_pretty(&manifest).unwrap(),
)
.unwrap();
}
fn check_draw(
before: &source_project::CompiledSourceProject,
actual: &source_project::CompiledSourceProject,
out: &Path,
) {
let p = &before.decoded.project;
assert!(
!p.relations
.values()
.any(|r| r.kind() == RelationKind::CommunicationMean)
);
let means: Vec<_> = actual
.decoded
.project
.relations
.values()
.filter(|r| r.kind() == RelationKind::CommunicationMean)
.collect();
assert_eq!(
means.len(),
1,
"Drawing creates exactly one communication means"
);
let identity = means[0].id;
assert!(!p.contains(identity));
let name = format!(
"Communication means: {} — {}",
p.named(id(10)),
p.named(id(11))
);
let drawn = Relation {
id: identity,
alias: p.fresh_alias(&name, "relation", identity),
name,
description: String::new(),
body: RelationBody::CommunicationMean {
source: id(10),
target: id(11),
oriented: false,
items: vec![],
},
};
let create = Command::CreateRelation(drawn.clone());
let after_draw = execute_batch(p, std::slice::from_ref(&create)).unwrap();
let draw_source = source_project::update_bundle(
before,
&after_draw,
source_project::ProjectLimits::default(),
)
.unwrap();
let mut renamed = drawn;
renamed.name = "Rescue radio channel".into();
let rename = Command::EditRelation(identity, renamed);
let expected = execute_batch(&after_draw, std::slice::from_ref(&rename)).unwrap();
let expected_source = source_project::update_bundle(
&draw_source,
&expected,
source_project::ProjectLimits::default(),
)
.unwrap();
assert_eq!(
expected, actual.decoded.project,
"Draw+rename full model differs; only actual new relation UUID was supplied"
);
let mut actual_files = actual.bundle.files.clone();
let entry = actual_files.remove(&actual.bundle.entry).unwrap();
assert!(
actual_files
.insert(expected_source.bundle.entry.clone(), entry)
.is_none()
);
assert_eq!(
expected_source.bundle.files, actual_files,
"Draw+rename complete source bytes differ (entry filename only aligned)"
);
write_bundle(out, &expected_source.bundle);
fs::write(
out.join("expected-draw.json"),
serde_json::to_vec_pretty(&expected).unwrap(),
)
.unwrap();
fs::write(
out.join("draw-rename-commands.json"),
serde_json::to_vec_pretty(&[create, rename]).unwrap(),
)
.unwrap();
println!(
"PASS draw: typed CreateRelation then EditRelation, exact full model/source, only new relation UUID {identity}"
);
}
fn check(mode: &str, before: &Path, after: &Path, out: &Path) {
let before_disk =
source_files::load_project(before, source_project::ProjectLimits::default()).unwrap();
let actual_disk =
source_files::load_project(after, source_project::ProjectLimits::default()).unwrap();
let compiled = source_project::compile_bundle(
&before_disk.reachable_bundle(),
source_project::ProjectLimits::default(),
)
.unwrap();
let actual_compiled = source_project::compile_bundle(
&actual_disk.reachable_bundle(),
source_project::ProjectLimits::default(),
)
.unwrap();
let p = compiled.decoded.project.clone();
let actual = actual_compiled.decoded.project.clone();
valid(&p);
valid(&actual);
if mode == "draw" {
return check_draw(&compiled, &actual_compiled, out);
}
let r = if matches!(mode, "reverse" | "reverse-behavior") {
reverse(&p)
} else {
let means: Vec<_> = p
.relations
.values()
.filter(|r| r.kind() == RelationKind::CommunicationMean)
.collect();
assert_eq!(means.len(), 1);
let mean = means[0].id;
let created = if mode == "create" {
let trace = actual.traces.values().find(|t| t.oa == mean).unwrap();
let key = actual.relations[&trace.sa].target();
assert!(!p.contains(key));
Some(key)
} else {
assert_eq!(mode, "reuse");
None
};
forward(&p, mean, created)
};
let mut expected = if mode == "reverse-behavior" {
apply_reverse_behavior(&p, &r)
} else {
apply(&p, &r)
};
let mut map = BTreeMap::new();
let mut actual_used = BTreeSet::new();
for t in expected
.traces
.values()
.filter(|t| !p.traces.contains_key(&t.id))
{
let source = if r.direction == TransitionDirection::OaToSa {
t.oa
} else {
t.sa
};
let counterparts: Vec<_> = actual
.traces
.values()
.filter(|a| {
if r.direction == TransitionDirection::OaToSa {
a.oa == source
} else {
a.sa == source
}
})
.collect();
assert_eq!(
counterparts.len(),
1,
"each explicit source has one counterpart"
);
let a = counterparts[0];
assert!(!p.contains(a.id));
assert!(actual_used.insert(a.id));
map.insert(t.id, a.id);
let (old, new) = if r.direction == TransitionDirection::OaToSa {
(t.sa, a.sa)
} else {
(t.oa, a.oa)
};
if !p.contains(old) {
assert!(!p.contains(new));
assert!(actual_used.insert(new));
map.insert(old, new);
}
}
// Match fresh presentation identities by exact destination diagram + mapped
// element. Every other placement property remains independently expected.
let mut placement_count = 0;
for (diagram, view) in &expected.diagrams {
for node in view.nodes.values().filter(|node| !p.contains(node.id)) {
let mapped_element = map.get(&node.element).copied().unwrap_or(node.element);
let actual_nodes: Vec<_> = actual
.diagrams
.get(diagram)
.expect("Expected destination diagram remains")
.nodes
.values()
.filter(|n| n.element == mapped_element)
.collect();
assert_eq!(
actual_nodes.len(),
1,
"Each generated placement must identify its exact diagram and mapped element"
);
let actual_id = actual_nodes[0].id;
assert!(!p.contains(actual_id));
assert!(actual_used.insert(actual_id));
map.insert(node.id, actual_id);
placement_count += 1;
}
}
expected = remap(expected, &map);
if matches!(mode, "reverse" | "reverse-behavior") {
reverse_assertions(&p, &expected);
}
assert_eq!(
expected, actual,
"full model oracle differs after UUID-only normalization"
);
let updated = source_project::update_bundle(
&compiled,
&expected,
source_project::ProjectLimits::default(),
)
.unwrap();
let mut actual_files = actual_compiled.bundle.files.clone();
let actual_entry = actual_files.remove(&actual_compiled.bundle.entry).unwrap();
assert!(
actual_files
.insert(updated.bundle.entry.clone(), actual_entry)
.is_none()
);
assert_eq!(
updated.bundle.files, actual_files,
"complete exact source bundle differs (only entry filename normalized for Save As)"
);
fs::create_dir_all(out).unwrap();
write_bundle(out, &updated.bundle);
fs::write(
out.join("expected-native.json"),
serde_json::to_vec_pretty(&expected).unwrap(),
)
.unwrap();
fs::write(
out.join("native-request.json"),
serde_json::to_vec_pretty(&r).unwrap(),
)
.unwrap();
println!(
"PASS {mode}: complete model and exact source; {} generated UUID substitutions only ({placement_count} placement identities)",
map.len()
);
}
fn main() {
let args: Vec<_> = std::env::args().collect();
match args.get(1).map(String::as_str) {
Some("generate") if args.len() == 3 => generate(Path::new(&args[2])),
Some("generate-reverse-behavior") if args.len() == 4 => {
generate_reverse_behavior(Path::new(&args[2]), Path::new(&args[3]))
}
Some("check") if args.len() == 6 => check(
&args[2],
Path::new(&args[3]),
Path::new(&args[4]),
Path::new(&args[5]),
),
_ => panic!(
"generate DIRECTORY | generate-reverse-behavior BEFORE.arcadia OUTPUT_DIRECTORY | check draw|reuse|create|reverse|reverse-behavior BEFORE.arcadia AFTER.arcadia OUTPUT_DIRECTORY"
),
}
}