1use crate::traits::ConnectomeService;
12use crate::types::*;
13use async_trait::async_trait;
14use feagi_brain_development::models::CorticalAreaExt;
15use feagi_brain_development::ConnectomeManager;
16use feagi_evolutionary::{get_default_neural_properties, MemoryAreaProperties};
17use feagi_npu_burst_engine::BurstLoopRunner;
18use feagi_structures::genomic::brain_regions::{BrainRegion, RegionID, RegionType};
19use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::{
20 FrameChangeHandling, PercentageNeuronPositioning,
21};
22use feagi_structures::genomic::cortical_area::CorticalID;
23use feagi_structures::genomic::cortical_area::IOCorticalAreaConfigurationFlag;
24use feagi_structures::genomic::cortical_area::{
25 CorticalArea, CorticalAreaDimensions, CorticalAreaType,
26};
27use feagi_structures::genomic::{MotorCorticalUnit, SensoryCorticalUnit};
28use parking_lot::RwLock;
30use serde_json::Value;
31use std::collections::{HashMap, HashSet};
32use std::sync::Arc;
33use tracing::{debug, info, trace, warn};
34
35fn derive_friendly_cortical_name(cortical_id: &CorticalID) -> Option<String> {
36 let bytes = cortical_id.as_bytes();
37 let is_input = bytes[0] == b'i';
38 let is_output = bytes[0] == b'o';
39 if !is_input && !is_output {
40 return None;
41 }
42
43 let unit_ref: [u8; 3] = [bytes[1], bytes[2], bytes[3]];
44 let subunit_index = bytes[6];
45 let unit_index = bytes[7];
46
47 if is_input {
48 for unit in SensoryCorticalUnit::list_all() {
49 if unit.get_cortical_id_unit_reference() == unit_ref {
50 let unit_name = unit.get_friendly_name();
51 let has_subunits = unit.get_number_cortical_areas() > 1;
52 let name = if has_subunits {
53 format!(
54 "{} Subunit {} Unit {}",
55 unit_name, subunit_index, unit_index
56 )
57 } else {
58 format!("{} Unit {}", unit_name, unit_index)
59 };
60 return Some(name);
61 }
62 }
63 } else {
64 for unit in MotorCorticalUnit::list_all() {
65 if unit.get_cortical_id_unit_reference() == unit_ref {
66 let unit_name = unit.get_friendly_name();
67 let has_subunits = unit.get_number_cortical_areas() > 1;
68 let name = if matches!(unit, MotorCorticalUnit::Gaze) {
69 let subunit_name = match subunit_index {
70 0 => "Eccentricity",
71 1 => "Modulation",
72 _ => "Subunit",
73 };
74 format!("{} ({}) Unit {}", unit_name, subunit_name, unit_index)
75 } else if has_subunits {
76 format!(
77 "{} Subunit {} Unit {}",
78 unit_name, subunit_index, unit_index
79 )
80 } else {
81 format!("{} Unit {}", unit_name, unit_index)
82 };
83 return Some(name);
84 }
85 }
86 }
87
88 None
89}
90
91fn merge_memory_area_properties(
94 base: HashMap<String, Value>,
95 extra: Option<&HashMap<String, Value>>,
96) -> HashMap<String, Value> {
97 let mut defaults = get_default_neural_properties();
98 let memory_defaults = MemoryAreaProperties::default();
99 defaults
100 .entry("cortical_group".to_string())
101 .or_insert(Value::from("MEMORY"));
102 defaults
103 .entry("is_mem_type".to_string())
104 .or_insert(Value::from(true));
105 defaults
106 .entry("temporal_depth".to_string())
107 .or_insert(Value::from(memory_defaults.temporal_depth));
108 defaults
109 .entry("longterm_mem_threshold".to_string())
110 .or_insert(Value::from(memory_defaults.longterm_threshold));
111 defaults
112 .entry("lifespan_growth_rate".to_string())
113 .or_insert(Value::from(memory_defaults.lifespan_growth_rate));
114 defaults
115 .entry("init_lifespan".to_string())
116 .or_insert(Value::from(memory_defaults.init_lifespan));
117
118 defaults.extend(base);
119 if let Some(extra_props) = extra {
120 defaults.extend(extra_props.clone());
121 }
122 defaults
123}
124
125fn frame_handling_label(frame: FrameChangeHandling) -> &'static str {
126 match frame {
127 FrameChangeHandling::Absolute => "Absolute",
128 FrameChangeHandling::Incremental => "Incremental",
129 }
130}
131
132fn positioning_label(positioning: PercentageNeuronPositioning) -> &'static str {
133 match positioning {
134 PercentageNeuronPositioning::Linear => "Linear",
135 PercentageNeuronPositioning::Fractional => "Fractional",
136 }
137}
138
139fn signage_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
140 match flag {
141 IOCorticalAreaConfigurationFlag::SignedPercentage(..)
142 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(..)
143 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(..)
144 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(..) => "Percentage Signed",
145 IOCorticalAreaConfigurationFlag::Percentage(..)
146 | IOCorticalAreaConfigurationFlag::Percentage2D(..)
147 | IOCorticalAreaConfigurationFlag::Percentage3D(..)
148 | IOCorticalAreaConfigurationFlag::Percentage4D(..) => "Percentage Unsigned",
149 IOCorticalAreaConfigurationFlag::CartesianPlane(..) => "Cartesian Plane",
150 IOCorticalAreaConfigurationFlag::Misc(..) => "Misc",
151 IOCorticalAreaConfigurationFlag::Boolean => "Boolean",
152 }
153}
154
155fn behavior_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
156 match flag {
157 IOCorticalAreaConfigurationFlag::Boolean => "Not Applicable",
158 IOCorticalAreaConfigurationFlag::CartesianPlane(frame)
159 | IOCorticalAreaConfigurationFlag::Misc(frame)
160 | IOCorticalAreaConfigurationFlag::Percentage(frame, _)
161 | IOCorticalAreaConfigurationFlag::Percentage2D(frame, _)
162 | IOCorticalAreaConfigurationFlag::Percentage3D(frame, _)
163 | IOCorticalAreaConfigurationFlag::Percentage4D(frame, _)
164 | IOCorticalAreaConfigurationFlag::SignedPercentage(frame, _)
165 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(frame, _)
166 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(frame, _)
167 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(frame, _) => {
168 frame_handling_label(*frame)
169 }
170 }
171}
172
173fn coding_type_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
174 match flag {
175 IOCorticalAreaConfigurationFlag::Percentage(_, positioning)
176 | IOCorticalAreaConfigurationFlag::Percentage2D(_, positioning)
177 | IOCorticalAreaConfigurationFlag::Percentage3D(_, positioning)
178 | IOCorticalAreaConfigurationFlag::Percentage4D(_, positioning)
179 | IOCorticalAreaConfigurationFlag::SignedPercentage(_, positioning)
180 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(_, positioning)
181 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(_, positioning)
182 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(_, positioning) => {
183 positioning_label(*positioning)
184 }
185 IOCorticalAreaConfigurationFlag::CartesianPlane(_)
186 | IOCorticalAreaConfigurationFlag::Misc(_)
187 | IOCorticalAreaConfigurationFlag::Boolean => "Not Applicable",
188 }
189}
190
191fn io_unit_reference_from_cortical_id(cortical_id: &CorticalID) -> Option<[u8; 3]> {
192 let bytes = cortical_id.as_bytes();
193 if bytes[0] != b'i' && bytes[0] != b'o' {
194 return None;
195 }
196 Some([bytes[1], bytes[2], bytes[3]])
197}
198
199fn io_coding_options_for_unit(cortical_id: &CorticalID) -> Option<IOCodingOptions> {
200 let unit_ref = io_unit_reference_from_cortical_id(cortical_id)?;
201 let is_input = cortical_id.as_bytes()[0] == b'i';
202
203 let (accepted_type, allowed_frames) = if is_input {
204 let unit = SensoryCorticalUnit::list_all()
205 .iter()
206 .find(|u| u.get_cortical_id_unit_reference() == unit_ref)?;
207 (
208 unit.get_accepted_wrapped_io_data_type(),
209 unit.get_allowed_frame_change_handling(),
210 )
211 } else {
212 let unit = MotorCorticalUnit::list_all()
213 .iter()
214 .find(|u| u.get_cortical_id_unit_reference() == unit_ref)?;
215 (
216 unit.get_accepted_wrapped_io_data_type(),
217 unit.get_allowed_frame_change_handling(),
218 )
219 };
220
221 let mut signage_options = Vec::new();
222 let mut behavior_options = Vec::new();
223 let mut coding_type_options = Vec::new();
224
225 let io_flag = match cortical_id.extract_io_data_flag() {
226 Ok(flag) => flag,
227 Err(err) => {
228 warn!(
229 target: "feagi-services",
230 "[IO-CODING] {} failed to extract io_flag: {} (accepted_type={})",
231 cortical_id,
232 err,
233 accepted_type
234 );
235 return None;
236 }
237 };
238 signage_options.push(signage_label_from_flag(&io_flag).to_string());
239
240 let supports_frame_handling = !matches!(io_flag, IOCorticalAreaConfigurationFlag::Boolean);
241 if supports_frame_handling {
242 if let Some(frames) = allowed_frames {
243 for frame in frames {
244 behavior_options.push(frame_handling_label(*frame).to_string());
245 }
246 } else {
247 behavior_options.push("Absolute".to_string());
248 behavior_options.push("Incremental".to_string());
249 }
250 } else {
251 behavior_options.push("Not Applicable".to_string());
252 }
253
254 let supports_positioning = matches!(
255 io_flag,
256 IOCorticalAreaConfigurationFlag::Percentage(..)
257 | IOCorticalAreaConfigurationFlag::Percentage2D(..)
258 | IOCorticalAreaConfigurationFlag::Percentage3D(..)
259 | IOCorticalAreaConfigurationFlag::Percentage4D(..)
260 | IOCorticalAreaConfigurationFlag::SignedPercentage(..)
261 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(..)
262 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(..)
263 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(..)
264 );
265 if supports_positioning {
266 coding_type_options.push("Linear".to_string());
267 coding_type_options.push("Fractional".to_string());
268 } else {
269 coding_type_options.push("Not Applicable".to_string());
270 }
271
272 if signage_options.is_empty() {
273 warn!(
274 target: "feagi-services",
275 "[IO-CODING] {} empty signage_options (accepted_type={}, io_flag={:?})",
276 cortical_id,
277 accepted_type,
278 io_flag
279 );
280 }
281 Some(IOCodingOptions {
282 signage_options,
283 behavior_options,
284 coding_type_options,
285 })
286}
287
288fn update_cortical_mapping_dst_in_properties(
294 properties: &mut HashMap<String, serde_json::Value>,
295 dst_area_id: &str,
296 mapping_data: &[serde_json::Value],
297) -> ServiceResult<()> {
298 if mapping_data.is_empty() {
299 let Some(existing) = properties.get_mut("cortical_mapping_dst") else {
300 return Ok(());
301 };
302 let Some(mapping_dst) = existing.as_object_mut() else {
303 return Err(ServiceError::Backend(
304 "cortical_mapping_dst is not a JSON object".to_string(),
305 ));
306 };
307
308 mapping_dst.remove(dst_area_id);
309 if mapping_dst.is_empty() {
310 properties.remove("cortical_mapping_dst");
311 }
312 return Ok(());
313 }
314
315 let entry = properties
316 .entry("cortical_mapping_dst".to_string())
317 .or_insert_with(|| serde_json::json!({}));
318
319 let Some(mapping_dst) = entry.as_object_mut() else {
320 return Err(ServiceError::Backend(
321 "cortical_mapping_dst is not a JSON object".to_string(),
322 ));
323 };
324
325 mapping_dst.insert(dst_area_id.to_string(), serde_json::json!(mapping_data));
326 Ok(())
327}
328
329fn get_cortical_mapping_dst_from_properties(
330 properties: &HashMap<String, serde_json::Value>,
331 dst_area_id: &str,
332) -> Option<Vec<serde_json::Value>> {
333 properties
334 .get("cortical_mapping_dst")
335 .and_then(|value| value.as_object())
336 .and_then(|mapping_dst| mapping_dst.get(dst_area_id))
337 .and_then(|value| value.as_array())
338 .map(|rules| rules.to_vec())
339}
340
341fn replace_morphology_id_in_value(
346 value: &mut Value,
347 old_id: &str,
348 new_id: &str,
349 replaced_count: &mut usize,
350) {
351 match value {
352 Value::Object(obj) => {
353 if let Some(morphology_id) = obj.get_mut("morphology_id") {
354 if morphology_id.as_str() == Some(old_id) {
355 *morphology_id = Value::String(new_id.to_string());
356 *replaced_count += 1;
357 }
358 }
359 for child in obj.values_mut() {
360 replace_morphology_id_in_value(child, old_id, new_id, replaced_count);
361 }
362 }
363 Value::Array(arr) => {
364 if let Some(first) = arr.first_mut() {
365 if first.as_str() == Some(old_id) {
366 *first = Value::String(new_id.to_string());
367 *replaced_count += 1;
368 }
369 }
370 for child in arr.iter_mut() {
371 replace_morphology_id_in_value(child, old_id, new_id, replaced_count);
372 }
373 }
374 _ => {}
375 }
376}
377
378pub struct ConnectomeServiceImpl {
380 connectome: Arc<RwLock<ConnectomeManager>>,
381 current_genome: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
384 #[cfg(feature = "connectome-io")]
386 runtime_service: Arc<RwLock<Option<Arc<dyn crate::traits::RuntimeService + Send + Sync>>>>,
387 burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
389}
390
391impl ConnectomeServiceImpl {
392 pub fn new(
393 connectome: Arc<RwLock<ConnectomeManager>>,
394 current_genome: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
395 ) -> Self {
396 Self {
397 connectome,
398 current_genome,
399 #[cfg(feature = "connectome-io")]
400 runtime_service: Arc::new(RwLock::new(None)),
401 burst_runner: None,
402 }
403 }
404
405 pub fn set_burst_runner(&mut self, burst_runner: Arc<RwLock<BurstLoopRunner>>) {
407 self.burst_runner = Some(burst_runner);
408 }
409
410 fn refresh_burst_runner_cache(&self) {
412 if let Some(ref burst_runner) = self.burst_runner {
413 let manager = self.connectome.read();
414 let mappings = manager.get_all_cortical_idx_to_id_mappings();
415 let chunk_sizes = manager.get_all_visualization_granularities();
416 let mapping_count = mappings.len();
417 let burst_runner_write = burst_runner.write();
418 burst_runner_write.refresh_cortical_id_mappings(mappings);
419 burst_runner_write.refresh_visualization_granularities(chunk_sizes);
420 debug!(target: "feagi-services", "Refreshed burst runner cache with {} cortical areas", mapping_count);
421 }
422 }
423
424 #[cfg(feature = "connectome-io")]
429 pub fn set_runtime_service(
430 &self,
431 runtime_service: Arc<dyn crate::traits::RuntimeService + Send + Sync>,
432 ) {
433 *self.runtime_service.write() = Some(runtime_service);
434 info!(target: "feagi-services", "RuntimeService connected to ConnectomeService for connectome I/O");
435 }
436
437 fn region_type_to_string(region_type: &RegionType) -> String {
439 match region_type {
440 RegionType::Undefined => "Undefined".to_string(),
441 }
442 }
443
444 fn string_to_region_type(s: &str) -> Result<RegionType, ServiceError> {
446 match s {
447 "Undefined" | "Sensory" | "Motor" | "Memory" | "Custom" => Ok(RegionType::Undefined),
448 _ => Err(ServiceError::InvalidInput(format!(
449 "Invalid region type: {}",
450 s
451 ))),
452 }
453 }
454}
455
456#[async_trait]
457impl ConnectomeService for ConnectomeServiceImpl {
458 async fn create_cortical_area(
463 &self,
464 params: CreateCorticalAreaParams,
465 ) -> ServiceResult<CorticalAreaInfo> {
466 info!(target: "feagi-services","Creating cortical area: {}", params.cortical_id);
467
468 let cortical_id_typed = CorticalID::try_from_base_64(¶ms.cortical_id)
470 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
471
472 let area_type = cortical_id_typed.as_cortical_type().map_err(|e| {
474 ServiceError::InvalidInput(format!("Failed to determine cortical area type: {}", e))
475 })?;
476
477 let mut area = CorticalArea::new(
479 cortical_id_typed,
480 0, params.name.clone(),
482 CorticalAreaDimensions::new(
483 params.dimensions.0 as u32,
484 params.dimensions.1 as u32,
485 params.dimensions.2 as u32,
486 )?,
487 params.position.into(), area_type,
489 )?;
490
491 if let Some(visible) = params.visible {
496 area.add_property_mut("visible".to_string(), serde_json::json!(visible));
497 }
498 if let Some(sub_group) = params.sub_group {
499 area.add_property_mut("sub_group".to_string(), serde_json::json!(sub_group));
500 }
501 if let Some(neurons_per_voxel) = params.neurons_per_voxel {
502 area.add_property_mut(
503 "neurons_per_voxel".to_string(),
504 serde_json::json!(neurons_per_voxel),
505 );
506 }
507 if let Some(postsynaptic_current) = params.postsynaptic_current {
508 area.add_property_mut(
509 "postsynaptic_current".to_string(),
510 serde_json::json!(postsynaptic_current),
511 );
512 }
513 if let Some(plasticity_constant) = params.plasticity_constant {
514 area.add_property_mut(
515 "plasticity_constant".to_string(),
516 serde_json::json!(plasticity_constant),
517 );
518 }
519 if let Some(degeneration) = params.degeneration {
520 area.add_property_mut("degeneration".to_string(), serde_json::json!(degeneration));
521 }
522 if let Some(psp_uniform_distribution) = params.psp_uniform_distribution {
523 area.add_property_mut(
524 "psp_uniform_distribution".to_string(),
525 serde_json::json!(psp_uniform_distribution),
526 );
527 }
528 if let Some(firing_threshold_increment) = params.firing_threshold_increment {
529 area.add_property_mut(
530 "firing_threshold_increment".to_string(),
531 serde_json::json!(firing_threshold_increment),
532 );
533 }
534 if let Some(firing_threshold_limit) = params.firing_threshold_limit {
535 area.add_property_mut(
536 "firing_threshold_limit".to_string(),
537 serde_json::json!(firing_threshold_limit),
538 );
539 }
540 if let Some(consecutive_fire_count) = params.consecutive_fire_count {
541 area.add_property_mut(
542 "consecutive_fire_limit".to_string(),
543 serde_json::json!(consecutive_fire_count),
544 );
545 }
546 if let Some(snooze_period) = params.snooze_period {
547 area.add_property_mut(
548 "snooze_period".to_string(),
549 serde_json::json!(snooze_period),
550 );
551 }
552 if let Some(refractory_period) = params.refractory_period {
553 area.add_property_mut(
554 "refractory_period".to_string(),
555 serde_json::json!(refractory_period),
556 );
557 }
558 if let Some(leak_coefficient) = params.leak_coefficient {
559 area.add_property_mut(
560 "leak_coefficient".to_string(),
561 serde_json::json!(leak_coefficient),
562 );
563 }
564 if let Some(leak_variability) = params.leak_variability {
565 area.add_property_mut(
566 "leak_variability".to_string(),
567 serde_json::json!(leak_variability),
568 );
569 }
570 if let Some(burst_engine_active) = params.burst_engine_active {
571 area.add_property_mut(
572 "burst_engine_active".to_string(),
573 serde_json::json!(burst_engine_active),
574 );
575 }
576
577 let parent_region_id = params
579 .properties
580 .as_ref()
581 .and_then(|props| props.get("parent_region_id"))
582 .and_then(|v| v.as_str())
583 .map(|s| s.to_string());
584
585 let is_memory_area = matches!(area_type, CorticalAreaType::Memory(_));
586 if is_memory_area {
587 let merged =
588 merge_memory_area_properties(area.properties.clone(), params.properties.as_ref());
589 area.properties = merged;
590 } else if let Some(properties) = params.properties {
591 area.properties = properties;
592 }
593
594 self.connectome
596 .write()
597 .add_cortical_area(area)
598 .map_err(ServiceError::from)?;
599
600 self.refresh_burst_runner_cache();
602
603 if let Some(region_id) = parent_region_id {
606 let mut manager = self.connectome.write();
607 if let Some(region) = manager.get_brain_region_mut(®ion_id) {
608 region.add_area(cortical_id_typed);
609 info!(target: "feagi-services",
610 "Added cortical area {} to parent region {}",
611 params.cortical_id, region_id
612 );
613 } else {
614 warn!(target: "feagi-services",
615 "Parent region {} not found for cortical area {}",
616 region_id, params.cortical_id
617 );
618 }
619 }
620
621 self.get_cortical_area(¶ms.cortical_id).await
623 }
624
625 async fn delete_cortical_area(&self, cortical_id: &str) -> ServiceResult<()> {
626 info!(target: "feagi-services","Deleting cortical area: {}", cortical_id);
627
628 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
630 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
631 let deleted_id_base64 = cortical_id_typed.as_base_64();
632 let deleted_cortical_idx = {
633 let manager = self.connectome.read();
634 manager.get_cortical_idx(&cortical_id_typed)
635 };
636 let mut removed_mapping_count = 0usize;
637 let mut removed_upstream_count = 0usize;
638
639 let region_io = {
645 let mut manager = self.connectome.write();
646 let region_ids: Vec<String> = manager
647 .get_brain_region_ids()
648 .into_iter()
649 .cloned()
650 .collect();
651 for region_id in region_ids {
652 if let Some(region) = manager.get_brain_region_mut(®ion_id) {
653 region.remove_area(&cortical_id_typed);
654 }
655 }
656
657 manager
658 .remove_cortical_area(&cortical_id_typed)
659 .map_err(ServiceError::from)?;
660
661 let cortical_ids: Vec<CorticalID> = manager
663 .get_cortical_area_ids()
664 .into_iter()
665 .cloned()
666 .collect();
667 for src_id in cortical_ids {
668 let has_mapping = manager
669 .get_cortical_area(&src_id)
670 .and_then(|area| area.properties.get("cortical_mapping_dst"))
671 .and_then(|value| value.as_object())
672 .map(|mapping| mapping.contains_key(&deleted_id_base64))
673 .unwrap_or(false);
674 if has_mapping {
675 manager
676 .update_cortical_mapping(&src_id, &cortical_id_typed, Vec::new())
677 .map_err(ServiceError::from)?;
678 removed_mapping_count += 1;
679 }
680
681 if let Some(deleted_idx) = deleted_cortical_idx {
682 if let Some(area) = manager.get_cortical_area_mut(&src_id) {
683 if let Some(upstream) = area
684 .properties
685 .get_mut("upstream_cortical_areas")
686 .and_then(|value| value.as_array_mut())
687 {
688 let before = upstream.len();
689 upstream.retain(|value| {
690 value
691 .as_u64()
692 .map(|id| id != deleted_idx as u64)
693 .unwrap_or(true)
694 });
695 if upstream.len() != before {
696 removed_upstream_count += before - upstream.len();
697 }
698 }
699 }
700 }
701 }
702
703 Some(manager.recompute_brain_region_io_registry().map_err(|e| {
704 ServiceError::Backend(format!("Failed to recompute region IO registry: {}", e))
705 })?)
706 };
707
708 if let Some(genome) = self.current_genome.write().as_mut() {
710 let removed = genome.cortical_areas.remove(&cortical_id_typed).is_some();
711 for region in genome.brain_regions.values_mut() {
712 region.remove_area(&cortical_id_typed);
713 }
714 for area in genome.cortical_areas.values_mut() {
715 update_cortical_mapping_dst_in_properties(
716 &mut area.properties,
717 &deleted_id_base64,
718 &[],
719 )?;
720 if let Some(deleted_idx) = deleted_cortical_idx {
721 if let Some(upstream) = area
722 .properties
723 .get_mut("upstream_cortical_areas")
724 .and_then(|value| value.as_array_mut())
725 {
726 upstream.retain(|value| {
727 value
728 .as_u64()
729 .map(|id| id != deleted_idx as u64)
730 .unwrap_or(true)
731 });
732 }
733 }
734 }
735 if let Some(region_io) = region_io {
736 for (region_id, (inputs, outputs)) in region_io {
737 if let Some(region) = genome.brain_regions.get_mut(®ion_id) {
738 if inputs.is_empty() {
739 region.properties.remove("inputs");
740 } else {
741 region
742 .properties
743 .insert("inputs".to_string(), serde_json::json!(inputs));
744 }
745
746 if outputs.is_empty() {
747 region.properties.remove("outputs");
748 } else {
749 region
750 .properties
751 .insert("outputs".to_string(), serde_json::json!(outputs));
752 }
753 } else {
754 warn!(
755 target: "feagi-services",
756 "Region '{}' not found in RuntimeGenome while persisting IO registry",
757 region_id
758 );
759 }
760 }
761 }
762
763 if removed {
764 info!(
765 target: "feagi-services",
766 "[GENOME-UPDATE] Removed cortical area {} from RuntimeGenome",
767 cortical_id
768 );
769 } else {
770 warn!(
771 target: "feagi-services",
772 "[GENOME-UPDATE] Cortical area {} not found in RuntimeGenome - deletion will not persist to saved genome",
773 cortical_id
774 );
775 }
776 } else {
777 warn!(
778 target: "feagi-services",
779 "[GENOME-UPDATE] No RuntimeGenome loaded - deletion will not persist to saved genome"
780 );
781 }
782 if removed_mapping_count > 0 || removed_upstream_count > 0 {
783 info!(
784 target: "feagi-services",
785 "Deleted area cleanup: {} mapping references removed, {} upstream references pruned",
786 removed_mapping_count,
787 removed_upstream_count
788 );
789 }
790
791 self.refresh_burst_runner_cache();
793
794 Ok(())
795 }
796
797 async fn update_cortical_area(
798 &self,
799 cortical_id: &str,
800 _params: UpdateCorticalAreaParams,
801 ) -> ServiceResult<CorticalAreaInfo> {
802 info!(target: "feagi-services","Updating cortical area: {}", cortical_id);
803
804 Err(ServiceError::NotImplemented(
809 "Cortical area updates must go through GenomeService for proper genome synchronization"
810 .to_string(),
811 ))
812 }
813
814 async fn get_cortical_area(&self, cortical_id: &str) -> ServiceResult<CorticalAreaInfo> {
815 trace!(target: "feagi-services", "Getting cortical area: {}", cortical_id);
816
817 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
819 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
820
821 let manager = self.connectome.read();
822
823 let area = manager
824 .get_cortical_area(&cortical_id_typed)
825 .ok_or_else(|| ServiceError::NotFound {
826 resource: "CorticalArea".to_string(),
827 id: cortical_id.to_string(),
828 })?;
829
830 let cortical_idx = manager
831 .get_cortical_idx(&cortical_id_typed)
832 .ok_or_else(|| ServiceError::NotFound {
833 resource: "CorticalArea".to_string(),
834 id: cortical_id.to_string(),
835 })?;
836
837 let neuron_count = manager.get_neuron_count_in_area(
838 &CorticalID::try_from_base_64(cortical_id)
839 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?,
840 );
841 let outgoing_synapse_count = manager.get_outgoing_synapse_count_in_area(&cortical_id_typed);
842 let incoming_synapse_count = manager.get_incoming_synapse_count_in_area(&cortical_id_typed);
843 let synapse_count = outgoing_synapse_count;
844
845 let cortical_group = area.get_cortical_group();
847
848 let memory_props = {
850 use feagi_evolutionary::extract_memory_properties;
851 extract_memory_properties(&area.properties)
852 };
853
854 let cortical_bytes = cortical_id_typed.as_bytes();
855 let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
856 let io_flag = if is_io_area {
857 cortical_id_typed.extract_io_data_flag().ok()
858 } else {
859 None
860 };
861 let cortical_subtype = if is_io_area {
862 String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
863 } else {
864 None
865 };
866 let unit_id = if is_io_area {
867 Some(cortical_bytes[6])
868 } else {
869 None
870 };
871 let coding_signage = io_flag
872 .as_ref()
873 .map(|flag| signage_label_from_flag(flag).to_string());
874 let coding_behavior = io_flag
875 .as_ref()
876 .map(|flag| behavior_label_from_flag(flag).to_string());
877 let coding_type = io_flag
878 .as_ref()
879 .map(|flag| coding_type_label_from_flag(flag).to_string());
880 let coding_options = if is_io_area {
881 io_coding_options_for_unit(&cortical_id_typed)
882 } else {
883 None
884 };
885 if is_io_area {
886 if let Some(opts) = &coding_options {
887 trace!(
888 target: "feagi-services",
889 "[IO-CODING] {} options signage={:?} behavior={:?} type={:?} io_flag={:?}",
890 cortical_id,
891 opts.signage_options,
892 opts.behavior_options,
893 opts.coding_type_options,
894 io_flag
895 );
896 } else {
897 warn!(
898 target: "feagi-services",
899 "[IO-CODING] {} options missing (io_flag={:?})",
900 cortical_id,
901 io_flag
902 );
903 }
904 }
905
906 let name = if area.name.is_empty() || area.name == area.cortical_id.to_string() {
907 derive_friendly_cortical_name(&area.cortical_id).unwrap_or_else(|| area.name.clone())
908 } else {
909 area.name.clone()
910 };
911
912 let mut filtered_properties = area.properties.clone();
913 let duplicate_keys: HashSet<&str> = [
914 "coordinates_3d",
915 "cortical_dimensions",
916 "cortical_dimensions_per_device",
917 "cortical_group",
918 "group_id",
919 "sub_group_id",
920 "neurons_per_voxel",
921 "firing_threshold",
922 "firing_threshold_increment_x",
923 "firing_threshold_increment_y",
924 "firing_threshold_increment_z",
925 "firing_threshold_limit",
926 "consecutive_fire_count",
927 "refractory_period",
928 "snooze_period",
929 "leak_coefficient",
930 "leak_variability",
931 "mp_charge_accumulation",
932 "mp_driven_psp",
933 "neuron_excitability",
934 "postsynaptic_current",
935 "postsynaptic_current_max",
936 "degeneration",
937 "plasticity_constant",
938 "psp_uniform_distribution",
939 "init_lifespan",
940 "lifespan_growth_rate",
941 "longterm_mem_threshold",
942 "visible",
943 ]
944 .into_iter()
945 .collect();
946 filtered_properties.retain(|key, _| !duplicate_keys.contains(key.as_str()));
947
948 Ok(CorticalAreaInfo {
949 cortical_id: cortical_id.to_string(),
950 cortical_id_s: area.cortical_id.to_string(), cortical_idx,
952 name,
953 dimensions: (
954 area.dimensions.width as usize,
955 area.dimensions.height as usize,
956 area.dimensions.depth as usize,
957 ),
958 position: area.position.into(), area_type: cortical_group
960 .clone()
961 .unwrap_or_else(|| "CUSTOM".to_string()),
962 cortical_group: cortical_group
963 .clone()
964 .unwrap_or_else(|| "CUSTOM".to_string()),
965 cortical_type: {
967 if memory_props.is_some() {
968 "memory".to_string()
969 } else if let Some(group) = &cortical_group {
970 match group.as_str() {
971 "IPU" => "sensory".to_string(),
972 "OPU" => "motor".to_string(),
973 "CORE" => "core".to_string(),
974 "MEMORY" => "memory".to_string(),
975 _ => "custom".to_string(),
976 }
977 } else {
978 "custom".to_string()
979 }
980 },
981 neuron_count,
982 synapse_count,
983 incoming_synapse_count,
984 outgoing_synapse_count,
985 visible: area.visible(),
987 sub_group: area.sub_group(),
988 neurons_per_voxel: area.neurons_per_voxel(),
989 postsynaptic_current: area.postsynaptic_current() as f64,
990 postsynaptic_current_max: area.postsynaptic_current_max() as f64,
991 plasticity_constant: area.plasticity_constant() as f64,
992 degeneration: area.degeneration() as f64,
993 psp_uniform_distribution: area.psp_uniform_distribution(),
994 mp_driven_psp: area.mp_driven_psp(),
995 firing_threshold: area.firing_threshold() as f64,
996 firing_threshold_increment: [
997 area.firing_threshold_increment_x() as f64,
998 area.firing_threshold_increment_y() as f64,
999 area.firing_threshold_increment_z() as f64,
1000 ],
1001 firing_threshold_limit: area.firing_threshold_limit() as f64,
1002 consecutive_fire_count: area.consecutive_fire_count(),
1003 snooze_period: area.snooze_period() as u32,
1004 refractory_period: area.refractory_period() as u32,
1005 leak_coefficient: area.leak_coefficient() as f64,
1006 leak_variability: area.leak_variability() as f64,
1007 mp_charge_accumulation: area.mp_charge_accumulation(),
1008 neuron_excitability: area.neuron_excitability() as f64,
1009 burst_engine_active: area.burst_engine_active(),
1010 init_lifespan: area.init_lifespan(),
1011 lifespan_growth_rate: area.lifespan_growth_rate() as f64,
1012 longterm_mem_threshold: area.longterm_mem_threshold(),
1013 temporal_depth: memory_props.map(|p| p.temporal_depth.max(1)),
1014 properties: filtered_properties,
1015 cortical_subtype,
1017 encoding_type: coding_behavior.clone(),
1018 encoding_format: coding_type.clone(),
1019 unit_id,
1020 group_id: None,
1021 coding_signage,
1022 coding_behavior,
1023 coding_type,
1024 coding_options,
1025 parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
1026 dev_count: area
1028 .properties
1029 .get("dev_count")
1030 .and_then(|v| v.as_u64().map(|n| n as usize)),
1031 cortical_dimensions_per_device: {
1032 let from_properties = area
1034 .properties
1035 .get("cortical_dimensions_per_device")
1036 .and_then(|v| v.as_array())
1037 .and_then(|arr| {
1038 if arr.len() == 3 {
1039 Some((
1040 arr[0].as_u64()? as usize,
1041 arr[1].as_u64()? as usize,
1042 arr[2].as_u64()? as usize,
1043 ))
1044 } else {
1045 None
1046 }
1047 });
1048
1049 if from_properties.is_none() {
1051 if let Some(dev_count) = area
1052 .properties
1053 .get("dev_count")
1054 .and_then(|v| v.as_u64().map(|n| n as usize))
1055 {
1056 if dev_count > 0 {
1057 let total_width = area.dimensions.width as usize;
1058 let height = area.dimensions.height as usize;
1059 let depth = area.dimensions.depth as usize;
1060 Some((total_width / dev_count, height, depth))
1061 } else {
1062 from_properties
1063 }
1064 } else {
1065 from_properties
1066 }
1067 } else {
1068 from_properties
1069 }
1070 },
1071 visualization_voxel_granularity: {
1072 area.properties
1075 .get("visualization_voxel_granularity")
1076 .and_then(|v| v.as_array())
1077 .and_then(|arr| {
1078 if arr.len() == 3 {
1079 let x_opt = arr[0]
1080 .as_u64()
1081 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1082 let y_opt = arr[1]
1083 .as_u64()
1084 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1085 let z_opt = arr[2]
1086 .as_u64()
1087 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1088 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1089 Some((x as u32, y as u32, z as u32))
1090 } else {
1091 None
1092 }
1093 } else {
1094 None
1095 }
1096 })
1097 .or(Some((1, 1, 1))) },
1099 })
1100 }
1101
1102 async fn list_cortical_areas(&self) -> ServiceResult<Vec<CorticalAreaInfo>> {
1103 trace!(target: "feagi-services", "Listing all cortical areas");
1104
1105 let cortical_ids: Vec<String> = {
1106 let manager = self.connectome.read();
1107 manager
1108 .get_cortical_area_ids()
1109 .into_iter()
1110 .map(|id| id.as_base_64())
1111 .collect()
1112 };
1113
1114 let mut areas = Vec::new();
1115 for cortical_id in cortical_ids {
1116 if let Ok(area_info) = self.get_cortical_area(&cortical_id).await {
1117 areas.push(area_info);
1118 }
1119 }
1120
1121 Ok(areas)
1122 }
1123
1124 async fn get_cortical_area_ids(&self) -> ServiceResult<Vec<String>> {
1125 debug!(target: "feagi-services","Getting cortical area IDs");
1126
1127 let ids: Vec<String> = {
1130 let manager = match self.connectome.try_read() {
1131 Some(guard) => guard,
1132 None => {
1133 warn!(target: "feagi-services", "⚠️ ConnectomeManager write lock is held - cannot read cortical area IDs");
1134 return Err(ServiceError::Backend("ConnectomeManager is currently being modified (e.g., genome loading in progress). Please try again in a moment.".to_string()));
1135 }
1136 };
1137
1138 let area_count = manager.get_cortical_area_count();
1139 let ids_refs = manager.get_cortical_area_ids();
1140 info!(target: "feagi-services", "Found {} cortical areas in ConnectomeManager", area_count);
1141 info!(target: "feagi-services", "Cortical area IDs (references): {:?}", ids_refs.iter().take(10).collect::<Vec<_>>());
1142 ids_refs.into_iter().map(|id| id.as_base_64()).collect()
1143 }; info!(target: "feagi-services", "Returning {} cortical area IDs: {:?}", ids.len(), ids.iter().take(10).collect::<Vec<_>>());
1145 Ok(ids)
1146 }
1147
1148 async fn cortical_area_exists(&self, cortical_id: &str) -> ServiceResult<bool> {
1149 trace!(target: "feagi-services","Checking if cortical area exists: {}", cortical_id);
1150
1151 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
1153 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
1154
1155 Ok(self.connectome.read().has_cortical_area(&cortical_id_typed))
1156 }
1157
1158 async fn get_cortical_area_properties(
1159 &self,
1160 cortical_id: &str,
1161 ) -> ServiceResult<std::collections::HashMap<String, serde_json::Value>> {
1162 debug!(target: "feagi-services","Getting cortical area properties: {}", cortical_id);
1163
1164 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
1166 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
1167
1168 let manager = self.connectome.read();
1169 manager
1170 .get_cortical_area_properties(&cortical_id_typed)
1171 .ok_or_else(|| ServiceError::NotFound {
1172 resource: "CorticalArea".to_string(),
1173 id: cortical_id.to_string(),
1174 })
1175 }
1176
1177 async fn get_all_cortical_area_properties(
1178 &self,
1179 ) -> ServiceResult<Vec<std::collections::HashMap<String, serde_json::Value>>> {
1180 debug!(target: "feagi-services","Getting all cortical area properties");
1181
1182 let manager = self.connectome.read();
1183 Ok(manager.get_all_cortical_area_properties())
1184 }
1185
1186 async fn get_neuron_properties(
1187 &self,
1188 neuron_id: u64,
1189 ) -> ServiceResult<HashMap<String, serde_json::Value>> {
1190 let manager = self.connectome.read();
1191 manager
1192 .get_neuron_properties(neuron_id)
1193 .ok_or_else(|| ServiceError::NotFound {
1194 resource: "Neuron".to_string(),
1195 id: neuron_id.to_string(),
1196 })
1197 }
1198
1199 async fn create_brain_region(
1204 &self,
1205 params: CreateBrainRegionParams,
1206 ) -> ServiceResult<BrainRegionInfo> {
1207 info!(target: "feagi-services","Creating brain region: {}", params.region_id);
1208
1209 let region_type = Self::string_to_region_type(¶ms.region_type)?;
1211
1212 let (areas, child_regions, properties) = {
1213 let mut properties = params.properties.clone().unwrap_or_default();
1214 let area_values = properties
1215 .remove("areas")
1216 .or_else(|| properties.remove("cortical_areas"))
1217 .map(|value| {
1218 value.as_array().cloned().ok_or_else(|| {
1219 ServiceError::InvalidInput(
1220 "areas must be an array of cortical area IDs".to_string(),
1221 )
1222 })
1223 })
1224 .transpose()?
1225 .unwrap_or_default();
1226 let child_values = properties
1227 .remove("regions")
1228 .or_else(|| properties.remove("child_regions"))
1229 .map(|value| {
1230 value.as_array().cloned().ok_or_else(|| {
1231 ServiceError::InvalidInput(
1232 "regions must be an array of brain region IDs".to_string(),
1233 )
1234 })
1235 })
1236 .transpose()?
1237 .unwrap_or_default();
1238
1239 let mut areas: Vec<String> = Vec::new();
1240 for value in area_values {
1241 let id = value.as_str().ok_or_else(|| {
1242 ServiceError::InvalidInput(
1243 "areas must be an array of cortical area IDs".to_string(),
1244 )
1245 })?;
1246 areas.push(id.to_string());
1247 }
1248
1249 let mut child_regions: Vec<String> = Vec::new();
1250 for value in child_values {
1251 let id = value.as_str().ok_or_else(|| {
1252 ServiceError::InvalidInput(
1253 "regions must be an array of brain region IDs".to_string(),
1254 )
1255 })?;
1256 child_regions.push(id.to_string());
1257 }
1258
1259 (areas, child_regions, properties)
1260 };
1261
1262 let mut region = BrainRegion::new(
1264 RegionID::from_string(¶ms.region_id)
1265 .map_err(|e| ServiceError::InvalidInput(format!("Invalid region ID: {}", e)))?,
1266 params.name.clone(),
1267 region_type,
1268 )
1269 .map_err(ServiceError::from)?;
1270
1271 if !properties.is_empty() {
1273 region = region.with_properties(properties);
1274 }
1275
1276 self.connectome
1278 .write()
1279 .add_brain_region(region, params.parent_id.clone())
1280 .map_err(ServiceError::from)?;
1281
1282 if let Some(genome) = self.current_genome.write().as_mut() {
1288 if let Some(created) = self
1291 .connectome
1292 .read()
1293 .get_brain_region(¶ms.region_id)
1294 .cloned()
1295 {
1296 genome
1297 .brain_regions
1298 .insert(params.region_id.clone(), created);
1299 }
1300 }
1301
1302 if !areas.is_empty() || !child_regions.is_empty() {
1304 let mut manager = self.connectome.write();
1305
1306 for area_id in &areas {
1307 let cortical_id =
1308 feagi_evolutionary::string_to_cortical_id(area_id).map_err(|e| {
1309 ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e))
1310 })?;
1311
1312 if !manager.has_cortical_area(&cortical_id) {
1313 return Err(ServiceError::NotFound {
1314 resource: "CorticalArea".to_string(),
1315 id: area_id.clone(),
1316 });
1317 }
1318
1319 if let Some(existing_parent) = manager.get_parent_region_id_for_area(&cortical_id) {
1320 if let Some(parent_region) = manager.get_brain_region_mut(&existing_parent) {
1321 parent_region.remove_area(&cortical_id);
1322 }
1323 }
1324
1325 let Some(region) = manager.get_brain_region_mut(¶ms.region_id) else {
1326 return Err(ServiceError::NotFound {
1327 resource: "BrainRegion".to_string(),
1328 id: params.region_id.clone(),
1329 });
1330 };
1331 region.add_area(cortical_id);
1332
1333 if let Some(area) = manager.get_cortical_area_mut(&cortical_id) {
1334 area.properties.insert(
1335 "parent_region_id".to_string(),
1336 serde_json::json!(params.region_id),
1337 );
1338 }
1339 }
1340
1341 for child_id in &child_regions {
1342 if child_id == ¶ms.region_id {
1343 return Err(ServiceError::InvalidInput(
1344 "regions cannot include the new region_id".to_string(),
1345 ));
1346 }
1347
1348 if manager.get_brain_region(child_id).is_none() {
1349 return Err(ServiceError::NotFound {
1350 resource: "BrainRegion".to_string(),
1351 id: child_id.clone(),
1352 });
1353 }
1354
1355 manager
1356 .change_brain_region_parent(child_id, ¶ms.region_id)
1357 .map_err(ServiceError::from)?;
1358
1359 if let Some(child_region) = manager.get_brain_region_mut(child_id) {
1360 child_region.add_property(
1361 "parent_region_id".to_string(),
1362 serde_json::json!(params.region_id),
1363 );
1364 }
1365 }
1366 }
1367
1368 if !areas.is_empty() || !child_regions.is_empty() {
1369 if let Some(genome) = self.current_genome.write().as_mut() {
1370 for area_id in &areas {
1371 let Ok(cortical_id) = feagi_evolutionary::string_to_cortical_id(area_id) else {
1372 continue;
1373 };
1374 for region in genome.brain_regions.values_mut() {
1375 region.remove_area(&cortical_id);
1376 }
1377 if let Some(region) = genome.brain_regions.get_mut(¶ms.region_id) {
1378 region.add_area(cortical_id);
1379 }
1380 if let Some(area) = genome.cortical_areas.get_mut(&cortical_id) {
1381 area.properties.insert(
1382 "parent_region_id".to_string(),
1383 serde_json::json!(params.region_id),
1384 );
1385 }
1386 }
1387
1388 for child_id in &child_regions {
1389 if let Some(child_region) = genome.brain_regions.get_mut(child_id) {
1390 child_region.add_property(
1391 "parent_region_id".to_string(),
1392 serde_json::json!(params.region_id),
1393 );
1394 }
1395 }
1396 } else {
1397 warn!(
1398 target: "feagi-services",
1399 "[GENOME-UPDATE] No RuntimeGenome loaded - region membership updates will not persist to saved genome"
1400 );
1401 }
1402 }
1403
1404 self.get_brain_region(¶ms.region_id).await
1406 }
1407
1408 async fn delete_brain_region(&self, region_id: &str) -> ServiceResult<()> {
1409 info!(target: "feagi-services","Deleting brain region: {}", region_id);
1410
1411 let (region_ids, cortical_area_ids) = {
1412 let manager = self.connectome.read();
1413 if manager.get_brain_region(region_id).is_none() {
1414 return Err(ServiceError::NotFound {
1415 resource: "BrainRegion".to_string(),
1416 id: region_id.to_string(),
1417 });
1418 }
1419
1420 let hierarchy = manager.get_brain_region_hierarchy();
1421 let mut region_ids: Vec<String> = hierarchy
1422 .get_all_descendants(region_id)
1423 .into_iter()
1424 .cloned()
1425 .collect();
1426 region_ids.push(region_id.to_string());
1427
1428 let mut region_ids_with_depth: Vec<(String, usize)> = Vec::new();
1429 for region_id in ®ion_ids {
1430 let mut depth = 0;
1431 let mut current = region_id.as_str();
1432 while let Some(parent) = hierarchy.get_parent(current) {
1433 depth += 1;
1434 current = parent;
1435 }
1436 region_ids_with_depth.push((region_id.clone(), depth));
1437 }
1438 region_ids_with_depth.sort_by(|(a_id, a_depth), (b_id, b_depth)| {
1439 b_depth.cmp(a_depth).then_with(|| a_id.cmp(b_id))
1440 });
1441 let region_ids_sorted = region_ids_with_depth
1442 .into_iter()
1443 .map(|(id, _)| id)
1444 .collect::<Vec<String>>();
1445
1446 let cortical_area_ids = hierarchy
1447 .get_all_areas_recursive(region_id)
1448 .into_iter()
1449 .collect::<Vec<String>>();
1450
1451 (region_ids_sorted, cortical_area_ids)
1452 };
1453
1454 for cortical_id in cortical_area_ids {
1455 self.delete_cortical_area(&cortical_id).await?;
1456 }
1457
1458 {
1459 let mut manager = self.connectome.write();
1460 for region_id in ®ion_ids {
1461 manager
1462 .remove_brain_region(region_id)
1463 .map_err(ServiceError::from)?;
1464 }
1465 }
1466
1467 if let Some(genome) = self.current_genome.write().as_mut() {
1468 for region_id in ®ion_ids {
1469 genome.brain_regions.remove(region_id);
1470 }
1471 } else {
1472 warn!(
1473 target: "feagi-services",
1474 "[GENOME-UPDATE] No RuntimeGenome loaded - brain region deletions will not persist to saved genome"
1475 );
1476 }
1477
1478 Ok(())
1479 }
1480
1481 async fn update_brain_region(
1482 &self,
1483 region_id: &str,
1484 properties: std::collections::HashMap<String, serde_json::Value>,
1485 ) -> ServiceResult<BrainRegionInfo> {
1486 info!(target: "feagi-services", "Updating brain region: {}", region_id);
1487
1488 self.connectome
1489 .write()
1490 .update_brain_region_properties(region_id, properties)
1491 .map_err(ServiceError::from)?;
1492
1493 self.get_brain_region(region_id).await
1495 }
1496
1497 async fn get_brain_region(&self, region_id: &str) -> ServiceResult<BrainRegionInfo> {
1498 trace!(target: "feagi-services", "Getting brain region: {}", region_id);
1499
1500 let manager = self.connectome.read();
1501
1502 let region = manager
1503 .get_brain_region(region_id)
1504 .ok_or_else(|| ServiceError::NotFound {
1505 resource: "BrainRegion".to_string(),
1506 id: region_id.to_string(),
1507 })?;
1508
1509 let hierarchy = manager.get_brain_region_hierarchy();
1510 let parent_id = hierarchy.get_parent(region_id).map(|s| s.to_string());
1511 let mut child_regions: Vec<String> = hierarchy
1512 .get_children(region_id)
1513 .into_iter()
1514 .cloned()
1515 .collect();
1516 child_regions.retain(|child_id| child_id != region_id);
1517
1518 Ok(BrainRegionInfo {
1519 region_id: region_id.to_string(),
1520 name: region.name.clone(),
1521 region_type: Self::region_type_to_string(®ion.region_type),
1522 parent_id,
1523 cortical_areas: region
1524 .cortical_areas
1525 .iter()
1526 .map(|id| id.as_base_64())
1527 .collect(), child_regions,
1529 properties: region.properties.clone(),
1530 })
1531 }
1532
1533 async fn list_brain_regions(&self) -> ServiceResult<Vec<BrainRegionInfo>> {
1534 trace!(target: "feagi-services", "Listing all brain regions");
1535
1536 let region_ids: Vec<String> = {
1537 let manager = self.connectome.read();
1538 let ids = manager.get_brain_region_ids();
1539 trace!(target: "feagi-services", "Found {} brain region IDs from ConnectomeManager", ids.len());
1540 ids.into_iter().map(|s| s.to_string()).collect()
1541 };
1542
1543 trace!(target: "feagi-services", "Processing {} regions...", region_ids.len());
1544 let mut regions = Vec::new();
1545 for region_id in region_ids {
1546 trace!(target: "feagi-services", "Getting region: {}", region_id);
1547 match self.get_brain_region(®ion_id).await {
1548 Ok(region_info) => {
1549 trace!(
1550 target: "feagi-services",
1551 "Got region: {} with {} areas",
1552 region_info.name,
1553 region_info.cortical_areas.len()
1554 );
1555 regions.push(region_info);
1556 }
1557 Err(e) => {
1558 warn!(target: "feagi-services", "Failed to get region {}: {}", region_id, e);
1559 }
1560 }
1561 }
1562
1563 trace!(target: "feagi-services", "Returning {} brain regions", regions.len());
1564 Ok(regions)
1565 }
1566
1567 async fn get_brain_region_ids(&self) -> ServiceResult<Vec<String>> {
1568 debug!(target: "feagi-services","Getting brain region IDs");
1569 Ok(self
1570 .connectome
1571 .read()
1572 .get_brain_region_ids()
1573 .into_iter()
1574 .map(|s| s.to_string())
1575 .collect())
1576 }
1577
1578 async fn brain_region_exists(&self, region_id: &str) -> ServiceResult<bool> {
1579 debug!(target: "feagi-services","Checking if brain region exists: {}", region_id);
1580 Ok(self.connectome.read().get_brain_region(region_id).is_some())
1581 }
1582
1583 async fn get_morphologies(&self) -> ServiceResult<HashMap<String, MorphologyInfo>> {
1584 let manager = self.connectome.read();
1585 let registry = manager.get_morphologies();
1586
1587 let mut result = HashMap::new();
1588 for (id, morphology) in registry.iter() {
1589 result.insert(
1590 id.clone(),
1591 MorphologyInfo {
1592 morphology_type: format!("{:?}", morphology.morphology_type).to_lowercase(),
1593 class: morphology.class.clone(),
1594 parameters: serde_json::to_value(&morphology.parameters)
1595 .unwrap_or(serde_json::json!({})),
1596 },
1597 );
1598 }
1599
1600 trace!(target: "feagi-services", "Retrieved {} morphologies", result.len());
1601 Ok(result)
1602 }
1603
1604 async fn create_morphology(
1605 &self,
1606 morphology_id: String,
1607 morphology: feagi_evolutionary::Morphology,
1608 ) -> ServiceResult<()> {
1609 if morphology_id.trim().is_empty() {
1610 return Err(ServiceError::InvalidInput(
1611 "morphology_id must be non-empty".to_string(),
1612 ));
1613 }
1614
1615 let mut genome_guard = self.current_genome.write();
1617 let Some(genome) = genome_guard.as_mut() else {
1618 return Err(ServiceError::InvalidState(
1619 "No RuntimeGenome loaded - cannot create morphology".to_string(),
1620 ));
1621 };
1622
1623 if genome.morphologies.contains(&morphology_id) {
1624 return Err(ServiceError::AlreadyExists {
1625 resource: "morphology".to_string(),
1626 id: morphology_id,
1627 });
1628 }
1629
1630 genome
1631 .morphologies
1632 .add_morphology(morphology_id.clone(), morphology.clone());
1633
1634 self.connectome
1636 .write()
1637 .upsert_morphology(morphology_id, morphology);
1638
1639 Ok(())
1640 }
1641
1642 async fn update_morphology(
1643 &self,
1644 morphology_id: String,
1645 morphology: feagi_evolutionary::Morphology,
1646 ) -> ServiceResult<()> {
1647 if morphology_id.trim().is_empty() {
1648 return Err(ServiceError::InvalidInput(
1649 "morphology_id must be non-empty".to_string(),
1650 ));
1651 }
1652
1653 let mut genome_guard = self.current_genome.write();
1654 let Some(genome) = genome_guard.as_mut() else {
1655 return Err(ServiceError::InvalidState(
1656 "No RuntimeGenome loaded - cannot update morphology".to_string(),
1657 ));
1658 };
1659
1660 if !genome.morphologies.contains(&morphology_id) {
1661 return Err(ServiceError::NotFound {
1662 resource: "morphology".to_string(),
1663 id: morphology_id,
1664 });
1665 }
1666
1667 genome
1668 .morphologies
1669 .add_morphology(morphology_id.clone(), morphology.clone());
1670
1671 self.connectome
1672 .write()
1673 .upsert_morphology(morphology_id, morphology);
1674
1675 Ok(())
1676 }
1677
1678 async fn delete_morphology(&self, morphology_id: &str) -> ServiceResult<()> {
1679 if morphology_id.trim().is_empty() {
1680 return Err(ServiceError::InvalidInput(
1681 "morphology_id must be non-empty".to_string(),
1682 ));
1683 }
1684
1685 let mut genome_guard = self.current_genome.write();
1686 let Some(genome) = genome_guard.as_mut() else {
1687 return Err(ServiceError::InvalidState(
1688 "No RuntimeGenome loaded - cannot delete morphology".to_string(),
1689 ));
1690 };
1691
1692 if !genome.morphologies.remove_morphology(morphology_id) {
1693 return Err(ServiceError::NotFound {
1694 resource: "morphology".to_string(),
1695 id: morphology_id.to_string(),
1696 });
1697 }
1698
1699 self.connectome.write().remove_morphology(morphology_id);
1701
1702 Ok(())
1703 }
1704
1705 async fn rename_morphology(&self, old_id: &str, new_id: &str) -> ServiceResult<()> {
1706 let old_id = old_id.trim();
1707 let new_id = new_id.trim();
1708
1709 if old_id.is_empty() {
1710 return Err(ServiceError::InvalidInput(
1711 "old_id must be non-empty".to_string(),
1712 ));
1713 }
1714 if new_id.is_empty() {
1715 return Err(ServiceError::InvalidInput(
1716 "new_id must be non-empty".to_string(),
1717 ));
1718 }
1719 if old_id == new_id {
1720 return Err(ServiceError::InvalidInput(
1721 "old_id and new_id must differ".to_string(),
1722 ));
1723 }
1724
1725 let mut genome_guard = self.current_genome.write();
1726 let Some(genome) = genome_guard.as_mut() else {
1727 return Err(ServiceError::InvalidState(
1728 "No RuntimeGenome loaded - cannot rename morphology".to_string(),
1729 ));
1730 };
1731
1732 let morphology =
1733 genome
1734 .morphologies
1735 .get(old_id)
1736 .cloned()
1737 .ok_or_else(|| ServiceError::NotFound {
1738 resource: "morphology".to_string(),
1739 id: old_id.to_string(),
1740 })?;
1741
1742 if morphology.class == "core" {
1743 return Err(ServiceError::InvalidInput(format!(
1744 "Core morphologies cannot be renamed: '{}'",
1745 old_id
1746 )));
1747 }
1748
1749 if genome.morphologies.contains(new_id) {
1750 return Err(ServiceError::AlreadyExists {
1751 resource: "morphology".to_string(),
1752 id: new_id.to_string(),
1753 });
1754 }
1755
1756 genome.morphologies.remove_morphology(old_id);
1757 genome
1758 .morphologies
1759 .add_morphology(new_id.to_string(), morphology.clone());
1760
1761 let mut replaced_count: usize = 0;
1762 for area in genome.cortical_areas.values_mut() {
1763 for prop_value in area.properties.values_mut() {
1764 replace_morphology_id_in_value(prop_value, old_id, new_id, &mut replaced_count);
1765 }
1766 }
1767
1768 for region in genome.brain_regions.values_mut() {
1769 for prop_value in region.properties.values_mut() {
1770 replace_morphology_id_in_value(prop_value, old_id, new_id, &mut replaced_count);
1771 }
1772 }
1773
1774 drop(genome_guard);
1775
1776 let mut manager = self.connectome.write();
1777 manager.remove_morphology(old_id);
1778 manager.upsert_morphology(new_id.to_string(), morphology);
1779
1780 info!(
1781 target: "feagi-services",
1782 "Renamed morphology '{}' to '{}' ({} references updated)",
1783 old_id,
1784 new_id,
1785 replaced_count
1786 );
1787
1788 Ok(())
1789 }
1790
1791 async fn update_cortical_mapping(
1792 &self,
1793 src_area_id: String,
1794 dst_area_id: String,
1795 mapping_data: Vec<serde_json::Value>,
1796 ) -> ServiceResult<usize> {
1797 info!(target: "feagi-services", "Updating cortical mapping: {} -> {} with {} connections",
1798 src_area_id, dst_area_id, mapping_data.len());
1799
1800 use feagi_structures::genomic::cortical_area::CorticalID;
1802 let src_id = CorticalID::try_from_base_64(&src_area_id).map_err(|e| {
1803 ServiceError::InvalidInput(format!("Invalid source cortical ID: {}", e))
1804 })?;
1805 let dst_id = CorticalID::try_from_base_64(&dst_area_id).map_err(|e| {
1806 ServiceError::InvalidInput(format!("Invalid destination cortical ID: {}", e))
1807 })?;
1808
1809 let existing_mapping = {
1810 let manager = self.connectome.read();
1811 manager.get_cortical_area(&src_id).and_then(|area| {
1812 get_cortical_mapping_dst_from_properties(&area.properties, &dst_area_id)
1813 })
1814 };
1815
1816 let mapping_has_associative_memory = |rules: &[serde_json::Value]| -> bool {
1817 for rule in rules {
1818 if let Some(obj) = rule.as_object() {
1819 if let Some(morphology_id) = obj.get("morphology_id").and_then(|v| v.as_str()) {
1820 if morphology_id == "associative_memory" {
1821 return true;
1822 }
1823 }
1824 }
1825 }
1826 false
1827 };
1828
1829 let has_associative_memory = mapping_has_associative_memory(&mapping_data)
1830 || existing_mapping
1831 .as_ref()
1832 .is_some_and(|rules| mapping_has_associative_memory(rules));
1833
1834 let mut existing_plasticity_by_morphology: HashMap<
1835 String,
1836 serde_json::Map<String, serde_json::Value>,
1837 > = HashMap::new();
1838 if let Some(existing_rules) = existing_mapping.as_ref() {
1839 for rule in existing_rules {
1840 if let Some(obj) = rule.as_object() {
1841 if let Some(morphology_id) = obj.get("morphology_id").and_then(|v| v.as_str()) {
1842 existing_plasticity_by_morphology
1843 .insert(morphology_id.to_string(), obj.clone());
1844 }
1845 }
1846 }
1847 }
1848
1849 let mut normalized_mapping_data = Vec::with_capacity(mapping_data.len());
1850 for rule in &mapping_data {
1851 if let Some(obj) = rule.as_object() {
1852 let mut normalized = obj.clone();
1853 let morphology_id = normalized
1854 .get("morphology_id")
1855 .and_then(|v| v.as_str())
1856 .unwrap_or("")
1857 .to_string();
1858 let is_associative = morphology_id == "associative_memory";
1859 let mut plasticity_flag = normalized
1860 .get("plasticity_flag")
1861 .and_then(|v| v.as_bool())
1862 .unwrap_or(false);
1863 if is_associative {
1864 normalized.insert("plasticity_flag".to_string(), serde_json::json!(true));
1865 plasticity_flag = true;
1866 }
1867 if plasticity_flag || is_associative {
1868 let required = [
1869 "plasticity_constant",
1870 "ltp_multiplier",
1871 "ltd_multiplier",
1872 "plasticity_window",
1873 ];
1874 if let Some(existing) = existing_plasticity_by_morphology.get(&morphology_id) {
1875 for key in required {
1876 if !normalized.contains_key(key) && existing.contains_key(key) {
1877 if let Some(value) = existing.get(key) {
1878 normalized.insert(key.to_string(), value.clone());
1879 }
1880 }
1881 }
1882 }
1883 let missing: Vec<&str> = required
1884 .iter()
1885 .copied()
1886 .filter(|key| !normalized.contains_key(*key))
1887 .collect();
1888 if !missing.is_empty() {
1889 return Err(ServiceError::InvalidInput(format!(
1890 "Associative memory mapping for {} is missing required plasticity keys {:?}",
1891 morphology_id, missing
1892 )));
1893 }
1894 }
1895 normalized_mapping_data.push(serde_json::Value::Object(normalized));
1896 } else {
1897 normalized_mapping_data.push(rule.clone());
1898 }
1899 }
1900
1901 debug!(
1902 target: "feagi-services",
1903 "Mapping update request: {} -> {} (existing_rules={}, new_rules={})",
1904 src_area_id,
1905 dst_area_id,
1906 existing_mapping.as_ref().map(|rules| rules.len()).unwrap_or(0),
1907 normalized_mapping_data.len()
1908 );
1909
1910 if existing_mapping
1911 .as_ref()
1912 .is_some_and(|rules| rules == &normalized_mapping_data)
1913 {
1914 info!(
1915 target: "feagi-services",
1916 "Mapping unchanged for {} -> {}; skipping regeneration",
1917 src_area_id,
1918 dst_area_id
1919 );
1920 return Ok(0);
1921 }
1922
1923 if let Some(genome) = self.current_genome.write().as_mut() {
1925 if let Some(src_area) = genome.cortical_areas.get_mut(&src_id) {
1926 update_cortical_mapping_dst_in_properties(
1927 &mut src_area.properties,
1928 &dst_area_id,
1929 &normalized_mapping_data,
1930 )?;
1931 info!(
1932 target: "feagi-services",
1933 "[GENOME-UPDATE] Updated cortical_mapping_dst for {} -> {} (connections={})",
1934 src_area_id,
1935 dst_area_id,
1936 normalized_mapping_data.len()
1937 );
1938 } else {
1939 warn!(target: "feagi-services", "[GENOME-UPDATE] Source area {} not found in RuntimeGenome", src_area_id);
1940 }
1941 } else {
1942 warn!(target: "feagi-services", "[GENOME-UPDATE] No RuntimeGenome loaded - mapping will not persist");
1943 }
1944
1945 let region_io = {
1947 let mut manager = self.connectome.write();
1948 manager
1949 .update_cortical_mapping(&src_id, &dst_id, normalized_mapping_data.clone())
1950 .map_err(|e| ServiceError::Backend(format!("Failed to update mapping: {}", e)))?;
1951
1952 let synapse_count = manager
1954 .regenerate_synapses_for_mapping(&src_id, &dst_id)
1955 .map_err(|e| {
1956 ServiceError::Backend(format!("Failed to regenerate synapses: {}", e))
1957 })?;
1958
1959 if has_associative_memory {
1960 manager
1961 .regenerate_synapses_for_mapping(&dst_id, &src_id)
1962 .map_err(|e| {
1963 ServiceError::Backend(format!(
1964 "Failed to regenerate synapses for associative mirror: {}",
1965 e
1966 ))
1967 })?;
1968 }
1969
1970 let region_io = manager.recompute_brain_region_io_registry().map_err(|e| {
1972 ServiceError::Backend(format!("Failed to recompute region IO registry: {}", e))
1973 })?;
1974
1975 info!(
1976 target: "feagi-services",
1977 "Cortical mapping updated: {} synapses created",
1978 synapse_count
1979 );
1980
1981 (synapse_count, region_io)
1982 };
1983
1984 self.refresh_burst_runner_cache();
1987
1988 if let Some(genome) = self.current_genome.write().as_mut() {
1990 for (region_id, (inputs, outputs)) in region_io.1 {
1991 if let Some(region) = genome.brain_regions.get_mut(®ion_id) {
1992 if inputs.is_empty() {
1993 region.properties.remove("inputs");
1994 } else {
1995 region
1996 .properties
1997 .insert("inputs".to_string(), serde_json::json!(inputs));
1998 }
1999
2000 if outputs.is_empty() {
2001 region.properties.remove("outputs");
2002 } else {
2003 region
2004 .properties
2005 .insert("outputs".to_string(), serde_json::json!(outputs));
2006 }
2007 } else {
2008 warn!(
2009 target: "feagi-services",
2010 "Region '{}' not found in RuntimeGenome while persisting IO registry",
2011 region_id
2012 );
2013 }
2014 }
2015
2016 let manager = self.connectome.read();
2017 if let Some(memory_area) = manager.get_cortical_area(&dst_id) {
2018 if let Some(twin_map) = memory_area
2019 .properties
2020 .get("memory_twin_areas")
2021 .and_then(|v| v.as_object())
2022 {
2023 if let Some(genome_area) = genome.cortical_areas.get_mut(&dst_id) {
2024 genome_area
2025 .properties
2026 .insert("memory_twin_areas".to_string(), serde_json::json!(twin_map));
2027 if let Some(mapping) = memory_area.properties.get("cortical_mapping_dst") {
2028 genome_area
2029 .properties
2030 .insert("cortical_mapping_dst".to_string(), mapping.clone());
2031 }
2032 }
2033 for twin_id_str in twin_map.values().filter_map(|v| v.as_str()) {
2034 let Ok(twin_id) = CorticalID::try_from_base_64(twin_id_str) else {
2035 continue;
2036 };
2037 if genome.cortical_areas.contains_key(&twin_id) {
2038 continue;
2039 }
2040 if let Some(twin_area) = manager.get_cortical_area(&twin_id) {
2041 genome.cortical_areas.insert(twin_id, twin_area.clone());
2042 if let Some(parent_region_id) = twin_area
2043 .properties
2044 .get("parent_region_id")
2045 .and_then(|v| v.as_str())
2046 {
2047 if let Some(region) = genome.brain_regions.get_mut(parent_region_id)
2048 {
2049 region.add_area(twin_id);
2050 }
2051 }
2052 }
2053 }
2054 }
2055 }
2056 }
2057
2058 Ok(region_io.0)
2059 }
2060
2061 #[cfg(feature = "connectome-io")]
2068 async fn export_connectome(
2069 &self,
2070 ) -> ServiceResult<feagi_npu_neural::types::connectome::ConnectomeSnapshot> {
2071 info!(target: "feagi-services", "Exporting connectome via service layer");
2072
2073 let npu_arc = {
2077 let connectome = self.connectome.read();
2078 let npu_opt = connectome.get_npu();
2079 npu_opt
2080 .ok_or_else(|| {
2081 ServiceError::Backend("NPU not connected to ConnectomeManager".to_string())
2082 })?
2083 .clone()
2084 };
2085
2086 use tracing::debug;
2090 let lock_start = std::time::Instant::now();
2091 let thread_id = std::thread::current().id();
2092 debug!(
2093 "[NPU-LOCK] CONNECTOME-SERVICE: Thread {:?} attempting NPU lock for export_connectome at {:?}",
2094 thread_id, lock_start
2095 );
2096 let snapshot = {
2097 let npu_lock = npu_arc.lock().unwrap();
2098 let lock_acquired = std::time::Instant::now();
2099 let lock_wait = lock_acquired.duration_since(lock_start);
2100 debug!(
2101 "[NPU-LOCK] CONNECTOME-SERVICE: Thread {:?} acquired lock after {:.2}ms wait for export_connectome",
2102 thread_id,
2103 lock_wait.as_secs_f64() * 1000.0
2104 );
2105 match &*npu_lock {
2106 feagi_npu_burst_engine::DynamicNPU::F32(npu_f32) => npu_f32.export_connectome(),
2107 feagi_npu_burst_engine::DynamicNPU::INT8(npu_int8) => npu_int8.export_connectome(),
2108 }
2109 };
2110 let lock_released = std::time::Instant::now();
2111 let total_duration = lock_released.duration_since(lock_start);
2112 debug!(
2113 "[NPU-LOCK] CONNECTOME-SERVICE: Thread {:?} RELEASED NPU lock after export_connectome (total: {:.2}ms)",
2114 thread_id,
2115 total_duration.as_secs_f64() * 1000.0
2116 );
2117
2118 info!(target: "feagi-services", "✅ Connectome exported: {} neurons, {} synapses",
2119 snapshot.neurons.count, snapshot.synapses.count);
2120
2121 Ok(snapshot)
2122 }
2123
2124 #[cfg(feature = "connectome-io")]
2125 async fn import_connectome(
2126 &self,
2127 snapshot: feagi_npu_neural::types::connectome::ConnectomeSnapshot,
2128 ) -> ServiceResult<()> {
2129 info!(target: "feagi-services", "Importing connectome via service layer: {} neurons, {} synapses",
2130 snapshot.neurons.count, snapshot.synapses.count);
2131
2132 warn!(target: "feagi-services", "⚠️ Connectome import via service layer not yet fully implemented");
2144 warn!(target: "feagi-services", " NPU.import_connectome_with_config() creates a new NPU instance");
2145 warn!(target: "feagi-services", " This requires stopping burst engine, replacing NPU, and restarting");
2146 warn!(target: "feagi-services", " Recommendation: Use NPU.import_connectome_with_config() during initialization");
2147
2148 Err(ServiceError::NotImplemented(
2149 "Connectome import via service layer requires NPU replacement coordination. Use NPU.import_connectome_with_config() during application initialization, or implement a 'replace NPU' operation that coordinates with BurstLoopRunner.".to_string(),
2150 ))
2151 }
2152}
2153
2154#[cfg(test)]
2155mod tests {
2156 use super::{replace_morphology_id_in_value, update_cortical_mapping_dst_in_properties};
2157 use crate::types::ServiceResult;
2158 use std::collections::HashMap;
2159
2160 #[test]
2161 fn empty_mapping_deletes_destination_key_and_prunes_container() -> ServiceResult<()> {
2162 let mut props: HashMap<String, serde_json::Value> = HashMap::new();
2163 props.insert(
2164 "cortical_mapping_dst".to_string(),
2165 serde_json::json!({
2166 "dstA": [{"morphology_id": "m1"}],
2167 "dstB": []
2168 }),
2169 );
2170
2171 update_cortical_mapping_dst_in_properties(&mut props, "dstA", &[])?;
2172 let dst = props
2173 .get("cortical_mapping_dst")
2174 .and_then(|v| v.as_object())
2175 .expect("cortical_mapping_dst should remain with dstB");
2176 assert!(!dst.contains_key("dstA"));
2177 assert!(dst.contains_key("dstB"));
2178
2179 update_cortical_mapping_dst_in_properties(&mut props, "dstB", &[])?;
2181 assert!(!props.contains_key("cortical_mapping_dst"));
2182 Ok(())
2183 }
2184
2185 #[test]
2186 fn non_empty_mapping_sets_destination_key() -> ServiceResult<()> {
2187 let mut props: HashMap<String, serde_json::Value> = HashMap::new();
2188 update_cortical_mapping_dst_in_properties(
2189 &mut props,
2190 "dstX",
2191 &[serde_json::json!({"morphology_id": "m1"})],
2192 )?;
2193
2194 let dst = props
2195 .get("cortical_mapping_dst")
2196 .and_then(|v| v.as_object())
2197 .expect("cortical_mapping_dst should exist");
2198 let arr = dst
2199 .get("dstX")
2200 .and_then(|v| v.as_array())
2201 .expect("dstX should be an array");
2202 assert_eq!(arr.len(), 1);
2203 Ok(())
2204 }
2205
2206 #[tokio::test]
2207 async fn morphology_create_update_delete_roundtrip() -> ServiceResult<()> {
2208 use super::ConnectomeServiceImpl;
2209 use crate::traits::ConnectomeService;
2210 use parking_lot::RwLock;
2211 use std::sync::Arc;
2212
2213 let connectome = Arc::new(RwLock::new(
2215 feagi_brain_development::ConnectomeManager::new_for_testing(),
2216 ));
2217
2218 let genome = feagi_evolutionary::RuntimeGenome {
2220 metadata: feagi_evolutionary::GenomeMetadata {
2221 genome_id: "test".to_string(),
2222 genome_title: "test".to_string(),
2223 genome_description: "".to_string(),
2224 version: "2.0".to_string(),
2225 timestamp: 0.0,
2226 brain_regions_root: None,
2227 },
2228 cortical_areas: HashMap::new(),
2229 brain_regions: HashMap::new(),
2230 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
2231 physiology: feagi_evolutionary::PhysiologyConfig::default(),
2232 signatures: feagi_evolutionary::GenomeSignatures {
2233 genome: "0".to_string(),
2234 blueprint: "0".to_string(),
2235 physiology: "0".to_string(),
2236 morphologies: None,
2237 },
2238 stats: feagi_evolutionary::GenomeStats::default(),
2239 };
2240 let current_genome = Arc::new(RwLock::new(Some(genome)));
2241
2242 let svc = ConnectomeServiceImpl::new(connectome.clone(), current_genome.clone());
2243
2244 let morph_id = "m_test_vectors".to_string();
2246 let morph = feagi_evolutionary::Morphology {
2247 morphology_type: feagi_evolutionary::MorphologyType::Vectors,
2248 parameters: feagi_evolutionary::MorphologyParameters::Vectors {
2249 vectors: vec![[1, 2, 3]],
2250 },
2251 class: "custom".to_string(),
2252 };
2253 svc.create_morphology(morph_id.clone(), morph).await?;
2254
2255 {
2257 let genome_guard = current_genome.read();
2258 let genome = genome_guard.as_ref().expect("genome must exist");
2259 assert!(genome.morphologies.contains(&morph_id));
2260 }
2261 {
2262 let mgr = connectome.read();
2263 assert!(mgr.get_morphologies().contains(&morph_id));
2264 }
2265
2266 let morph2 = feagi_evolutionary::Morphology {
2268 morphology_type: feagi_evolutionary::MorphologyType::Vectors,
2269 parameters: feagi_evolutionary::MorphologyParameters::Vectors {
2270 vectors: vec![[9, 9, 9]],
2271 },
2272 class: "custom".to_string(),
2273 };
2274 svc.update_morphology(morph_id.clone(), morph2).await?;
2275 {
2276 let mgr = connectome.read();
2277 let stored = mgr
2278 .get_morphologies()
2279 .get(&morph_id)
2280 .expect("morphology must exist");
2281 match &stored.parameters {
2282 feagi_evolutionary::MorphologyParameters::Vectors { vectors } => {
2283 assert_eq!(vectors.as_slice(), &[[9, 9, 9]]);
2284 }
2285 other => panic!("unexpected parameters: {:?}", other),
2286 }
2287 }
2288
2289 svc.delete_morphology(&morph_id).await?;
2291 {
2292 let genome_guard = current_genome.read();
2293 let genome = genome_guard.as_ref().expect("genome must exist");
2294 assert!(!genome.morphologies.contains(&morph_id));
2295 }
2296 {
2297 let mgr = connectome.read();
2298 assert!(!mgr.get_morphologies().contains(&morph_id));
2299 }
2300
2301 Ok(())
2302 }
2303
2304 #[tokio::test]
2305 async fn morphology_rename_updates_registry_and_cortical_mapping_references(
2306 ) -> ServiceResult<()> {
2307 use super::ConnectomeServiceImpl;
2308 use crate::traits::ConnectomeService;
2309 use feagi_structures::genomic::cortical_area::{
2310 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CorticalID,
2311 };
2312 use parking_lot::RwLock;
2313 use std::collections::HashMap;
2314 use std::sync::Arc;
2315
2316 let connectome = Arc::new(RwLock::new(
2317 feagi_brain_development::ConnectomeManager::new_for_testing(),
2318 ));
2319
2320 let src_id = CorticalID::try_from_bytes(b"csrc0001").unwrap();
2321 let dst_id = CorticalID::try_from_bytes(b"csrc0002").unwrap();
2322
2323 let mut src_area = CorticalArea::new(
2324 src_id,
2325 0,
2326 "Source".to_string(),
2327 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
2328 (0, 0, 0).into(),
2329 CorticalAreaType::Custom(
2330 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
2331 ),
2332 )
2333 .unwrap();
2334 src_area.properties.insert(
2335 "cortical_mapping_dst".to_string(),
2336 serde_json::json!({
2337 dst_id.as_base_64(): [
2338 {"morphology_id": "m_old", "morphology_scalar": 1},
2339 ["m_old", 2, 1.0, false]
2340 ]
2341 }),
2342 );
2343
2344 let dst_area = CorticalArea::new(
2345 dst_id,
2346 1,
2347 "Target".to_string(),
2348 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
2349 (0, 0, 0).into(),
2350 CorticalAreaType::Custom(
2351 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
2352 ),
2353 )
2354 .unwrap();
2355
2356 let morph = feagi_evolutionary::Morphology {
2357 morphology_type: feagi_evolutionary::MorphologyType::Vectors,
2358 parameters: feagi_evolutionary::MorphologyParameters::Vectors {
2359 vectors: vec![[1, 0, 0]],
2360 },
2361 class: "custom".to_string(),
2362 };
2363
2364 let mut morphologies = feagi_evolutionary::MorphologyRegistry::new();
2365 morphologies.add_morphology("m_old".to_string(), morph.clone());
2366
2367 let genome = feagi_evolutionary::RuntimeGenome {
2368 metadata: feagi_evolutionary::GenomeMetadata {
2369 genome_id: "test".to_string(),
2370 genome_title: "test".to_string(),
2371 genome_description: "".to_string(),
2372 version: "2.0".to_string(),
2373 timestamp: 0.0,
2374 brain_regions_root: None,
2375 },
2376 cortical_areas: HashMap::from([(src_id, src_area.clone()), (dst_id, dst_area.clone())]),
2377 brain_regions: HashMap::new(),
2378 morphologies,
2379 physiology: feagi_evolutionary::PhysiologyConfig::default(),
2380 signatures: feagi_evolutionary::GenomeSignatures {
2381 genome: "0".to_string(),
2382 blueprint: "0".to_string(),
2383 physiology: "0".to_string(),
2384 morphologies: None,
2385 },
2386 stats: feagi_evolutionary::GenomeStats::default(),
2387 };
2388
2389 {
2390 let mut manager = connectome.write();
2391 manager.upsert_morphology("m_old".to_string(), morph);
2392 }
2393
2394 let current_genome = Arc::new(RwLock::new(Some(genome)));
2395 let svc = ConnectomeServiceImpl::new(connectome.clone(), current_genome.clone());
2396
2397 svc.rename_morphology("m_old", "m_new").await?;
2398
2399 {
2400 let genome_guard = current_genome.read();
2401 let genome = genome_guard.as_ref().expect("genome must exist");
2402 assert!(!genome.morphologies.contains("m_old"));
2403 assert!(genome.morphologies.contains("m_new"));
2404
2405 let area = genome
2406 .cortical_areas
2407 .get(&src_id)
2408 .expect("src area must exist");
2409 let dstmap = area
2410 .properties
2411 .get("cortical_mapping_dst")
2412 .and_then(|v| v.as_object())
2413 .expect("cortical_mapping_dst must exist");
2414 let rules = dstmap
2415 .get(&dst_id.as_base_64())
2416 .and_then(|v| v.as_array())
2417 .expect("rules must exist");
2418 assert_eq!(rules.len(), 2);
2419 assert_eq!(
2420 rules[0].get("morphology_id").and_then(|v| v.as_str()),
2421 Some("m_new")
2422 );
2423 assert_eq!(
2424 rules[1]
2425 .as_array()
2426 .and_then(|a| a.first())
2427 .and_then(|v| v.as_str()),
2428 Some("m_new")
2429 );
2430 }
2431
2432 {
2433 let mgr = connectome.read();
2434 assert!(!mgr.get_morphologies().contains("m_old"));
2435 assert!(mgr.get_morphologies().contains("m_new"));
2436 }
2437
2438 Ok(())
2439 }
2440
2441 #[tokio::test]
2442 async fn morphology_rename_rejects_core_morphologies() -> ServiceResult<()> {
2443 use super::ConnectomeServiceImpl;
2444 use crate::traits::ConnectomeService;
2445 use crate::types::ServiceError;
2446 use parking_lot::RwLock;
2447 use std::collections::HashMap;
2448 use std::sync::Arc;
2449
2450 let connectome = Arc::new(RwLock::new(
2451 feagi_brain_development::ConnectomeManager::new_for_testing(),
2452 ));
2453 {
2454 let mut manager = connectome.write();
2455 manager.setup_core_morphologies_for_testing();
2456 }
2457
2458 let mut morphologies = feagi_evolutionary::MorphologyRegistry::new();
2459 feagi_evolutionary::add_core_morphologies(&mut morphologies);
2460
2461 let genome = feagi_evolutionary::RuntimeGenome {
2462 metadata: feagi_evolutionary::GenomeMetadata {
2463 genome_id: "test".to_string(),
2464 genome_title: "test".to_string(),
2465 genome_description: "".to_string(),
2466 version: "2.0".to_string(),
2467 timestamp: 0.0,
2468 brain_regions_root: None,
2469 },
2470 cortical_areas: HashMap::new(),
2471 brain_regions: HashMap::new(),
2472 morphologies,
2473 physiology: feagi_evolutionary::PhysiologyConfig::default(),
2474 signatures: feagi_evolutionary::GenomeSignatures {
2475 genome: "0".to_string(),
2476 blueprint: "0".to_string(),
2477 physiology: "0".to_string(),
2478 morphologies: None,
2479 },
2480 stats: feagi_evolutionary::GenomeStats::default(),
2481 };
2482
2483 let current_genome = Arc::new(RwLock::new(Some(genome)));
2484 let svc = ConnectomeServiceImpl::new(connectome.clone(), current_genome.clone());
2485
2486 let err = svc
2487 .rename_morphology("projector", "projector_renamed")
2488 .await
2489 .unwrap_err();
2490 assert!(
2491 matches!(err, ServiceError::InvalidInput(_)),
2492 "Expected InvalidInput, got {:?}",
2493 err
2494 );
2495
2496 Ok(())
2497 }
2498
2499 #[test]
2500 fn replace_morphology_id_in_value_handles_object_and_array_formats() {
2501 let mut value = serde_json::json!({
2502 "nested": {
2503 "morphology_id": "old_id",
2504 "other": "x"
2505 },
2506 "arr": ["old_id", 1, 2]
2507 });
2508 let mut count = 0;
2509 replace_morphology_id_in_value(&mut value, "old_id", "new_id", &mut count);
2510 assert_eq!(count, 2);
2511 assert_eq!(value["nested"]["morphology_id"], "new_id");
2512 assert_eq!(value["arr"][0], "new_id");
2513 }
2514
2515 #[tokio::test]
2516 async fn associative_memory_missing_window_is_filled_from_existing_mapping() -> ServiceResult<()>
2517 {
2518 use super::ConnectomeServiceImpl;
2519 use crate::traits::ConnectomeService;
2520 use feagi_structures::genomic::cortical_area::{
2521 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CorticalID,
2522 };
2523 use parking_lot::RwLock;
2524 use std::collections::HashMap;
2525 use std::sync::Arc;
2526
2527 let connectome = Arc::new(RwLock::new(
2528 feagi_brain_development::ConnectomeManager::new_for_testing(),
2529 ));
2530
2531 let src_id = CorticalID::try_from_bytes(b"csrc0003").unwrap();
2532 let dst_id = CorticalID::try_from_bytes(b"csrc0004").unwrap();
2533
2534 let mut src_area = CorticalArea::new(
2535 src_id,
2536 0,
2537 "Source".to_string(),
2538 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
2539 (0, 0, 0).into(),
2540 CorticalAreaType::Custom(
2541 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
2542 ),
2543 )
2544 .unwrap();
2545 let dst_area = CorticalArea::new(
2546 dst_id,
2547 0,
2548 "Target".to_string(),
2549 CorticalAreaDimensions::new(1, 1, 1).unwrap(),
2550 (0, 0, 0).into(),
2551 CorticalAreaType::Custom(
2552 feagi_structures::genomic::cortical_area::CustomCorticalType::LeakyIntegrateFire,
2553 ),
2554 )
2555 .unwrap();
2556
2557 src_area.properties.insert(
2558 "cortical_mapping_dst".to_string(),
2559 serde_json::json!({
2560 dst_id.as_base_64(): [{
2561 "morphology_id": "associative_memory",
2562 "morphology_scalar": 1,
2563 "postSynapticCurrent_multiplier": 1.0,
2564 "plasticity_flag": true,
2565 "plasticity_constant": 1,
2566 "ltp_multiplier": 1,
2567 "ltd_multiplier": 1,
2568 "plasticity_window": 5
2569 }]
2570 }),
2571 );
2572
2573 {
2574 let mut manager = connectome.write();
2575 manager.add_cortical_area(src_area.clone())?;
2576 manager.add_cortical_area(dst_area.clone())?;
2577 }
2578
2579 let genome = feagi_evolutionary::RuntimeGenome {
2580 metadata: feagi_evolutionary::GenomeMetadata {
2581 genome_id: "test".to_string(),
2582 genome_title: "test".to_string(),
2583 genome_description: "".to_string(),
2584 version: "2.0".to_string(),
2585 timestamp: 0.0,
2586 brain_regions_root: None,
2587 },
2588 cortical_areas: HashMap::from([(src_id, src_area), (dst_id, dst_area)]),
2589 brain_regions: HashMap::new(),
2590 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
2591 physiology: feagi_evolutionary::PhysiologyConfig::default(),
2592 signatures: feagi_evolutionary::GenomeSignatures {
2593 genome: "0".to_string(),
2594 blueprint: "0".to_string(),
2595 physiology: "0".to_string(),
2596 morphologies: None,
2597 },
2598 stats: feagi_evolutionary::GenomeStats::default(),
2599 };
2600 let current_genome = Arc::new(RwLock::new(Some(genome)));
2601
2602 let svc = ConnectomeServiceImpl::new(connectome.clone(), current_genome.clone());
2603
2604 let mapping_data = vec![serde_json::json!({
2605 "morphology_id": "associative_memory",
2606 "morphology_scalar": 1,
2607 "postSynapticCurrent_multiplier": 1.0,
2608 "plasticity_flag": true,
2609 "plasticity_constant": 1,
2610 "ltp_multiplier": 1,
2611 "ltd_multiplier": 1
2612 })];
2613
2614 svc.update_cortical_mapping(src_id.as_base_64(), dst_id.as_base_64(), mapping_data)
2615 .await?;
2616
2617 let genome_guard = current_genome.read();
2618 let genome = genome_guard.as_ref().expect("genome must exist");
2619 let updated = genome
2620 .cortical_areas
2621 .get(&src_id)
2622 .and_then(|area| area.properties.get("cortical_mapping_dst"))
2623 .and_then(|v| v.as_object())
2624 .and_then(|map| map.get(&dst_id.as_base_64()))
2625 .and_then(|v| v.as_array())
2626 .and_then(|rules| rules.first())
2627 .and_then(|v| v.as_object())
2628 .and_then(|obj| obj.get("plasticity_window"))
2629 .and_then(|v| v.as_i64());
2630 assert_eq!(updated, Some(5));
2631
2632 Ok(())
2633 }
2634
2635 #[tokio::test]
2636 async fn delete_cortical_area_persists_to_runtime_genome() -> ServiceResult<()> {
2637 use super::ConnectomeServiceImpl;
2638 use crate::traits::ConnectomeService;
2639 use feagi_structures::genomic::brain_regions::{BrainRegion, RegionID, RegionType};
2640 use feagi_structures::genomic::cortical_area::{
2641 CoreCorticalType, CorticalArea, CorticalAreaDimensions,
2642 };
2643 use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
2644 use parking_lot::RwLock;
2645 use std::collections::HashMap;
2646 use std::sync::Arc;
2647
2648 let connectome = Arc::new(RwLock::new(
2650 feagi_brain_development::ConnectomeManager::new_for_testing(),
2651 ));
2652
2653 let cortical_id = CoreCorticalType::Power.to_cortical_id();
2655
2656 let dims = CorticalAreaDimensions::new(1, 1, 1).expect("dimensions must be valid");
2657 let pos = GenomeCoordinate3D::new(0, 0, 0);
2658 let cortical_type = cortical_id
2659 .as_cortical_type()
2660 .expect("cortical type must be derivable from id");
2661
2662 let area = CorticalArea::new(
2663 cortical_id,
2664 0, "test_area".to_string(),
2666 dims,
2667 pos,
2668 cortical_type,
2669 )
2670 .expect("area must be valid");
2671
2672 let region_id = RegionID::new();
2674 let region_key = region_id.to_string();
2675 let region = BrainRegion::new(region_id, "root".to_string(), RegionType::Undefined)
2676 .expect("region must be valid")
2677 .with_areas([cortical_id]);
2678
2679 let genome = feagi_evolutionary::RuntimeGenome {
2681 metadata: feagi_evolutionary::GenomeMetadata {
2682 genome_id: "test".to_string(),
2683 genome_title: "test".to_string(),
2684 genome_description: "".to_string(),
2685 version: "3.0".to_string(),
2686 timestamp: 0.0,
2687 brain_regions_root: Some(region_key.clone()),
2688 },
2689 cortical_areas: HashMap::from([(cortical_id, area.clone())]),
2690 brain_regions: HashMap::from([(region_key.clone(), region.clone())]),
2691 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
2692 physiology: feagi_evolutionary::PhysiologyConfig::default(),
2693 signatures: feagi_evolutionary::GenomeSignatures {
2694 genome: "0".to_string(),
2695 blueprint: "0".to_string(),
2696 physiology: "0".to_string(),
2697 morphologies: None,
2698 },
2699 stats: feagi_evolutionary::GenomeStats::default(),
2700 };
2701 let current_genome = Arc::new(RwLock::new(Some(genome)));
2702
2703 {
2705 let mut mgr = connectome.write();
2706 mgr.add_brain_region(region, None)
2707 .expect("brain region should be addable");
2708 mgr.add_cortical_area(area)
2709 .expect("cortical area should be addable");
2710 }
2711
2712 let svc = ConnectomeServiceImpl::new(connectome.clone(), current_genome.clone());
2713
2714 let cortical_id_base64 = cortical_id.as_base_64();
2716 svc.delete_cortical_area(&cortical_id_base64).await?;
2717
2718 {
2720 let genome_guard = current_genome.read();
2721 let genome = genome_guard.as_ref().expect("genome must exist");
2722 assert!(!genome.cortical_areas.contains_key(&cortical_id));
2723 let region = genome
2724 .brain_regions
2725 .get(®ion_key)
2726 .expect("region must exist in genome");
2727 assert!(!region.contains_area(&cortical_id));
2728 }
2729
2730 Ok(())
2731 }
2732
2733 #[tokio::test]
2735 async fn delete_brain_region_deletes_descendants_and_persists() -> ServiceResult<()> {
2736 use super::ConnectomeServiceImpl;
2737 use crate::traits::ConnectomeService;
2738 use feagi_brain_development::ConnectomeManager;
2739 use feagi_structures::genomic::brain_regions::{BrainRegion, RegionID, RegionType};
2740 use feagi_structures::genomic::cortical_area::{
2741 CoreCorticalType, CorticalArea, CorticalAreaDimensions,
2742 };
2743 use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
2744 use parking_lot::RwLock;
2745 use std::collections::HashMap;
2746 use std::sync::Arc;
2747
2748 let connectome = Arc::new(RwLock::new(ConnectomeManager::new_for_testing()));
2749
2750 let root_id = RegionID::new();
2751 let root_key = root_id.to_string();
2752 let child_id = RegionID::new();
2753 let child_key = child_id.to_string();
2754 let grandchild_id = RegionID::new();
2755 let grandchild_key = grandchild_id.to_string();
2756
2757 let power_id = CoreCorticalType::Power.to_cortical_id();
2758 let death_id = CoreCorticalType::Death.to_cortical_id();
2759
2760 let power_area = CorticalArea::new(
2761 power_id,
2762 0,
2763 "power_area".to_string(),
2764 CorticalAreaDimensions::new(1, 1, 1)?,
2765 GenomeCoordinate3D::new(0, 0, 0),
2766 power_id.as_cortical_type()?,
2767 )?;
2768 let death_area = CorticalArea::new(
2769 death_id,
2770 0,
2771 "death_area".to_string(),
2772 CorticalAreaDimensions::new(1, 1, 1)?,
2773 GenomeCoordinate3D::new(0, 0, 0),
2774 death_id.as_cortical_type()?,
2775 )?;
2776
2777 let root = BrainRegion::new(root_id, "root".to_string(), RegionType::Undefined)?;
2778 let child = BrainRegion::new(child_id, "child".to_string(), RegionType::Undefined)?
2779 .with_areas([power_id]);
2780 let grandchild =
2781 BrainRegion::new(grandchild_id, "grand".to_string(), RegionType::Undefined)?
2782 .with_areas([death_id]);
2783
2784 let genome = feagi_evolutionary::RuntimeGenome {
2785 metadata: feagi_evolutionary::GenomeMetadata {
2786 genome_id: "test".to_string(),
2787 genome_title: "test".to_string(),
2788 genome_description: "".to_string(),
2789 version: "3.0".to_string(),
2790 timestamp: 0.0,
2791 brain_regions_root: Some(root_key.clone()),
2792 },
2793 cortical_areas: HashMap::from([
2794 (power_id, power_area.clone()),
2795 (death_id, death_area.clone()),
2796 ]),
2797 brain_regions: HashMap::from([
2798 (root_key.clone(), root.clone()),
2799 (child_key.clone(), child.clone()),
2800 (grandchild_key.clone(), grandchild.clone()),
2801 ]),
2802 morphologies: feagi_evolutionary::MorphologyRegistry::new(),
2803 physiology: feagi_evolutionary::PhysiologyConfig::default(),
2804 signatures: feagi_evolutionary::GenomeSignatures {
2805 genome: "0".to_string(),
2806 blueprint: "0".to_string(),
2807 physiology: "0".to_string(),
2808 morphologies: None,
2809 },
2810 stats: feagi_evolutionary::GenomeStats::default(),
2811 };
2812 let current_genome = Arc::new(RwLock::new(Some(genome)));
2813
2814 {
2815 let mut manager = connectome.write();
2816 manager.add_brain_region(root, None)?;
2817 manager.add_brain_region(child, Some(root_key.clone()))?;
2818 manager.add_brain_region(grandchild, Some(child_key.clone()))?;
2819 manager.add_cortical_area(power_area)?;
2820 manager.add_cortical_area(death_area)?;
2821 }
2822
2823 let svc = ConnectomeServiceImpl::new(connectome.clone(), current_genome.clone());
2824 svc.delete_brain_region(&child_key).await?;
2825
2826 {
2827 let manager = connectome.read();
2828 assert!(manager.get_brain_region(&child_key).is_none());
2829 assert!(manager.get_brain_region(&grandchild_key).is_none());
2830 assert!(manager.get_brain_region(&root_key).is_some());
2831 assert!(!manager.has_cortical_area(&power_id));
2832 assert!(!manager.has_cortical_area(&death_id));
2833 }
2834
2835 {
2836 let genome_guard = current_genome.read();
2837 let genome = genome_guard.as_ref().expect("genome must exist");
2838 assert!(!genome.brain_regions.contains_key(&child_key));
2839 assert!(!genome.brain_regions.contains_key(&grandchild_key));
2840 assert!(genome.brain_regions.contains_key(&root_key));
2841 assert!(!genome.cortical_areas.contains_key(&power_id));
2842 assert!(!genome.cortical_areas.contains_key(&death_id));
2843 }
2844
2845 Ok(())
2846 }
2847}