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