Skip to main content

feagi_services/impls/
connectome_service_impl.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Connectome service implementation.
6
7Copyright 2025 Neuraville Inc.
8Licensed under the Apache License, Version 2.0
9*/
10
11use 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};
29// Note: decode_cortical_id removed - use feagi_structures::CorticalID directly
30use 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
92/// Merge default template and memory properties into provided values.
93/// Existing values always override defaults.
94fn 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
289/// Update a cortical area's `cortical_mapping_dst` property in-place.
290///
291/// - When `mapping_data` is empty: remove the destination entry, and if the
292///   container becomes empty remove `cortical_mapping_dst` entirely.
293/// - When `mapping_data` is non-empty: insert/overwrite the destination entry.
294fn 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
433/// Recursively replace morphology_id in JSON values.
434///
435/// Handles object format `{"morphology_id": "x", ...}` and array format
436/// `["morphology_id", scalar, ...]` where morphology_id is the first element.
437fn 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
470/// Default implementation of ConnectomeService
471pub struct ConnectomeServiceImpl {
472    connectome: Arc<RwLock<ConnectomeManager>>,
473    /// Currently loaded genome (source of truth for genome persistence)
474    /// Shared with GenomeServiceImpl to ensure cortical mappings are saved
475    current_genome: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
476    /// Optional reference to RuntimeService for accessing NPU (for connectome I/O)
477    #[cfg(feature = "connectome-io")]
478    runtime_service: Arc<RwLock<Option<Arc<dyn crate::traits::RuntimeService + Send + Sync>>>>,
479    /// Optional burst runner for refreshing cortical_id cache
480    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    /// Set the burst runner for cache refresh
498    pub fn set_burst_runner(&mut self, burst_runner: Arc<RwLock<BurstLoopRunner>>) {
499        self.burst_runner = Some(burst_runner);
500    }
501
502    /// Refresh cortical_id cache in burst runner
503    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    /// Set the runtime service (required for connectome export/import)
517    ///
518    /// This must be called after creating ConnectomeServiceImpl to enable
519    /// connectome I/O operations.
520    #[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    /// Convert RegionType enum to string
530    fn region_type_to_string(region_type: &RegionType) -> String {
531        match region_type {
532            RegionType::Undefined => "Undefined".to_string(),
533        }
534    }
535
536    /// Convert string to RegionType enum
537    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    // ========================================================================
551    // CORTICAL AREA OPERATIONS
552    // ========================================================================
553
554    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        // Convert String to CorticalID
561        let cortical_id_typed = CorticalID::try_from_base_64(&params.cortical_id)
562            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
563
564        // Get cortical area type from the cortical ID
565        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        // Create CorticalArea
570        let mut area = CorticalArea::new(
571            cortical_id_typed,
572            0, // Auto-assigned by ConnectomeManager
573            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(), // Convert (i32, i32, i32) to GenomeCoordinate3D
580            area_type,
581        )?;
582
583        // Set the cortical type
584        // Note: cortical_type_new field removed - type is encoded in CorticalID
585
586        // Apply all neural parameters from params
587        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        // Extract parent_region_id before moving properties
670        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        // Add to connectome
687        self.connectome
688            .write()
689            .add_cortical_area(area)
690            .map_err(ServiceError::from)?;
691
692        // Refresh burst runner cache after creating area
693        self.refresh_burst_runner_cache();
694
695        // CRITICAL: If parent_region_id is specified, add this cortical area
696        // to the parent brain region's cortical_areas set so it persists in genome
697        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(&region_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        // Return info
714        self.get_cortical_area(&params.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        // Convert String to CorticalID
721        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        // Remove from the live connectome, and also scrub from brain-region membership
732        // so UI + region-based operations don't keep referencing a deleted area.
733        //
734        // Note: ConnectomeManager::remove_cortical_area currently does NOT remove the
735        // ID from brain regions, so we do it explicitly here.
736        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(&region_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            // Scrub any remaining mappings that target the deleted area to avoid stale UI state.
754            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        // CRITICAL: Persist deletion into RuntimeGenome (source of truth for save/export).
801        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(&region_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        // Refresh burst runner cache after deleting area
884        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        // TODO: This should be routed through GenomeService for proper genome update
897        // and change classification (PARAMETER vs STRUCTURAL vs METADATA)
898        // Currently this is a stub - needs architecture alignment with Python implementation
899
900        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        // Convert String to CorticalID
910        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        // Get cortical_group from the area (uses cortical_type_new if available)
938        let cortical_group = area.get_cortical_group();
939
940        // Note: decode_cortical_id removed - IPU/OPU metadata now in CorticalID
941        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(), // Human-readable ASCII string
1051            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(), // Convert GenomeCoordinate3D to (i32, i32, i32)
1059            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            // Determine cortical_type based on properties
1066            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            // All neural parameters come from the actual CorticalArea struct
1086            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            // IPU/OPU-specific decoded fields (only populated for IPU/OPU areas)
1116            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            // Extract dev_count and cortical_dimensions_per_device from properties for IPU/OPU
1127            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                // Try to get from properties first
1133                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 not in properties, compute from dimensions and dev_count for IPU/OPU areas
1150                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                // Default is 1x1x1 if not in properties (user-driven, not stored)
1173                // Handle both integer and float JSON values
1174                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))) // Default is 1x1x1
1198            },
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        // CRITICAL: Use try_read() instead of read() to avoid blocking forever
1228        // If write lock is held (e.g., during genome loading), return error instead of hanging
1229        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        }; // Lock dropped here
1244        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        // Convert String to CorticalID
1252        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        // Convert String to CorticalID
1265        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    // ========================================================================
1300    // BRAIN REGION OPERATIONS
1301    // ========================================================================
1302
1303    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        // Convert string to RegionType
1310        let region_type = Self::string_to_region_type(&params.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        // Create BrainRegion
1363        let mut region = BrainRegion::new(
1364            RegionID::from_string(&params.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        // Apply initial properties (persisted into ConnectomeManager and RuntimeGenome).
1372        if !properties.is_empty() {
1373            region = region.with_properties(properties);
1374        }
1375
1376        // Add to connectome
1377        self.connectome
1378            .write()
1379            .add_brain_region(region, params.parent_id.clone())
1380            .map_err(ServiceError::from)?;
1381
1382        // Persist into RuntimeGenome (source of truth for genome save/export).
1383        //
1384        // NOTE: GenomeServiceImpl::create_cortical_areas requires that parent_region_id exists
1385        // in the RuntimeGenome brain_regions map. Without this, any subsequent cortical-area
1386        // creation that targets this region will fail.
1387        if let Some(genome) = self.current_genome.write().as_mut() {
1388            // Fetch the canonical region instance from ConnectomeManager to ensure any internal
1389            // normalization is reflected in the persisted copy.
1390            if let Some(created) = self
1391                .connectome
1392                .read()
1393                .get_brain_region(&params.region_id)
1394                .cloned()
1395            {
1396                genome
1397                    .brain_regions
1398                    .insert(params.region_id.clone(), created);
1399            }
1400        }
1401
1402        // Reassign areas and child regions (if provided).
1403        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(&params.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 == &params.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, &params.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(&params.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        // Return info
1505        self.get_brain_region(&params.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 &region_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 &region_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 &region_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        // Return updated info
1594        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        // Ensure root region always includes power/death if they exist (fixes BV disappearing)
1625        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(&region.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(&region_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        // Require a loaded RuntimeGenome for persistence (source of truth).
1738        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        // Keep ConnectomeManager registry in sync (used by mapping/synapse generation).
1757        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        // Mirror deletion into the ConnectomeManager registry.
1934        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        // Convert String to CorticalID
2035        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        // Update RuntimeGenome if available (CRITICAL for save/load persistence!)
2158        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        // Update the cortical_mapping_dst property in ConnectomeManager
2180        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            // Regenerate synapses for this mapping
2187            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            // Recompute region IO registries after mapping change (critical for BV region boundary behavior)
2205            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        // Refresh burst-runner caches so newly created twin areas are visualized immediately.
2219        // IMPORTANT: Call this after releasing the connectome write lock to avoid deadlocks.
2220        self.refresh_burst_runner_cache();
2221
2222        // Persist updated region IO into RuntimeGenome so genome save/export stays consistent.
2223        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(&region_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    // Note: unit tests for mapping persistence behavior are below in this module.
2296
2297    // ========================================================================
2298    // CONNECTOME I/O OPERATIONS
2299    // ========================================================================
2300
2301    #[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        // Get NPU from ConnectomeManager (which has reference to NPU)
2308        // Note: get_npu() returns Option<&Arc<...>>, so we need to clone the Arc
2309        // to use it outside the lock scope
2310        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        // Export connectome from NPU
2321        // Note: export_connectome() is on RustNPU, but we have DynamicNPU
2322        // We need to handle both F32 and INT8 variants
2323        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        // NOTE: NPU.import_connectome_with_config() is a constructor that creates a NEW NPU.
2367        // This means importing requires replacing the entire NPU instance, which involves:
2368        // 1. Stopping the burst engine
2369        // 2. Creating a new NPU from the snapshot
2370        // 3. Replacing the NPU in ConnectomeManager and BurstLoopRunner
2371        // 4. Restarting the burst engine
2372        //
2373        // This is a complex operation that requires coordination across multiple components.
2374        // For now, we return NotImplemented and recommend using the NPU constructor directly
2375        // during application initialization, or implementing a higher-level "replace NPU" operation.
2376
2377        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        // Now remove last remaining destination, container should be removed entirely
2417        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        // Isolated connectome manager instance for this test.
2553        let connectome = Arc::new(RwLock::new(
2554            feagi_brain_development::ConnectomeManager::new_for_testing(),
2555        ));
2556
2557        // Minimal RuntimeGenome (source of truth) for persistence.
2558        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        // Create
2584        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        // Verify both source-of-truth and connectome registry were updated
2595        {
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        // Update (overwrite vectors)
2606        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        // Delete
2629        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        // Isolated connectome manager instance for this test.
2988        let connectome = Arc::new(RwLock::new(
2989            feagi_brain_development::ConnectomeManager::new_for_testing(),
2990        ));
2991
2992        // Use a known-valid cortical ID/type pair to avoid ID encoding intricacies in this unit test.
2993        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, // Let ConnectomeManager assign a proper idx
3004            "test_area".to_string(),
3005            dims,
3006            pos,
3007            cortical_type,
3008        )
3009        .expect("area must be valid");
3010
3011        // Create a region that contains the test area.
3012        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        // Seed RuntimeGenome with the area + region membership (this is what genome save/export uses).
3019        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        // Seed ConnectomeManager with the same region + area (this is what BV and runtime uses).
3043        {
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        // Act: delete by base64 string.
3054        let cortical_id_base64 = cortical_id.as_base_64();
3055        svc.delete_cortical_area(&cortical_id_base64).await?;
3056
3057        // Assert: RuntimeGenome no longer contains the area nor region membership.
3058        {
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(&region_key)
3065                .expect("region must exist in genome");
3066            assert!(!region.contains_area(&cortical_id));
3067        }
3068
3069        Ok(())
3070    }
3071
3072    /// Deleting a region should remove descendants, areas, and RuntimeGenome entries.
3073    #[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}