1use crate::traits::GenomeService;
12use crate::types::*;
13use async_trait::async_trait;
14use feagi_brain_development::models::CorticalAreaExt;
15use feagi_brain_development::neuroembryogenesis::Neuroembryogenesis;
16use feagi_brain_development::ConnectomeManager;
17use feagi_evolutionary::{get_default_neural_properties, MemoryAreaProperties};
18use feagi_npu_burst_engine::{BurstLoopRunner, ParameterUpdateQueue};
19use feagi_structures::genomic::cortical_area::descriptors::{
20 CorticalSubUnitIndex, CorticalUnitIndex,
21};
22use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::{
23 FrameChangeHandling, PercentageNeuronPositioning,
24};
25use feagi_structures::genomic::cortical_area::{
26 CorticalArea, CorticalAreaDimensions, CorticalAreaType, CorticalID,
27 IOCorticalAreaConfigurationFlag,
28};
29use feagi_structures::genomic::{MotorCorticalUnit, SensoryCorticalUnit};
30use parking_lot::RwLock;
31use serde_json::Value;
32use std::collections::HashMap;
33use std::sync::Arc;
34use tracing::{info, trace, warn};
35
36use crate::genome::{ChangeType, CorticalChangeClassifier};
37
38fn frame_handling_label(frame: FrameChangeHandling) -> &'static str {
39 match frame {
40 FrameChangeHandling::Absolute => "Absolute",
41 FrameChangeHandling::Incremental => "Incremental",
42 }
43}
44
45fn positioning_label(positioning: PercentageNeuronPositioning) -> &'static str {
46 match positioning {
47 PercentageNeuronPositioning::Linear => "Linear",
48 PercentageNeuronPositioning::Fractional => "Fractional",
49 }
50}
51
52fn signage_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
53 match flag {
54 IOCorticalAreaConfigurationFlag::SignedPercentage(..)
55 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(..)
56 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(..)
57 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(..) => "Percentage Signed",
58 IOCorticalAreaConfigurationFlag::Percentage(..)
59 | IOCorticalAreaConfigurationFlag::Percentage2D(..)
60 | IOCorticalAreaConfigurationFlag::Percentage3D(..)
61 | IOCorticalAreaConfigurationFlag::Percentage4D(..) => "Percentage Unsigned",
62 IOCorticalAreaConfigurationFlag::CartesianPlane(..) => "Cartesian Plane",
63 IOCorticalAreaConfigurationFlag::Misc(..) => "Misc",
64 IOCorticalAreaConfigurationFlag::Boolean => "Boolean",
65 }
66}
67
68fn merge_memory_area_properties(
71 base: HashMap<String, Value>,
72 extra: Option<&HashMap<String, Value>>,
73) -> HashMap<String, Value> {
74 let mut defaults = get_default_neural_properties();
75 let memory_defaults = MemoryAreaProperties::default();
76 defaults
77 .entry("cortical_group".to_string())
78 .or_insert(Value::from("MEMORY"));
79 defaults
80 .entry("is_mem_type".to_string())
81 .or_insert(Value::from(true));
82 defaults
83 .entry("temporal_depth".to_string())
84 .or_insert(Value::from(memory_defaults.temporal_depth));
85 defaults
86 .entry("longterm_mem_threshold".to_string())
87 .or_insert(Value::from(memory_defaults.longterm_threshold));
88 defaults
89 .entry("lifespan_growth_rate".to_string())
90 .or_insert(Value::from(memory_defaults.lifespan_growth_rate));
91 defaults
92 .entry("init_lifespan".to_string())
93 .or_insert(Value::from(memory_defaults.init_lifespan));
94
95 defaults.extend(base);
96 if let Some(extra_props) = extra {
97 defaults.extend(extra_props.clone());
98 }
99 defaults
100}
101
102fn behavior_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
103 match flag {
104 IOCorticalAreaConfigurationFlag::Boolean => "Not Applicable",
105 IOCorticalAreaConfigurationFlag::CartesianPlane(frame)
106 | IOCorticalAreaConfigurationFlag::Misc(frame)
107 | IOCorticalAreaConfigurationFlag::Percentage(frame, _)
108 | IOCorticalAreaConfigurationFlag::Percentage2D(frame, _)
109 | IOCorticalAreaConfigurationFlag::Percentage3D(frame, _)
110 | IOCorticalAreaConfigurationFlag::Percentage4D(frame, _)
111 | IOCorticalAreaConfigurationFlag::SignedPercentage(frame, _)
112 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(frame, _)
113 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(frame, _)
114 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(frame, _) => {
115 frame_handling_label(*frame)
116 }
117 }
118}
119
120fn coding_type_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
121 match flag {
122 IOCorticalAreaConfigurationFlag::Percentage(_, positioning)
123 | IOCorticalAreaConfigurationFlag::Percentage2D(_, positioning)
124 | IOCorticalAreaConfigurationFlag::Percentage3D(_, positioning)
125 | IOCorticalAreaConfigurationFlag::Percentage4D(_, positioning)
126 | IOCorticalAreaConfigurationFlag::SignedPercentage(_, positioning)
127 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(_, positioning)
128 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(_, positioning)
129 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(_, positioning) => {
130 positioning_label(*positioning)
131 }
132 IOCorticalAreaConfigurationFlag::CartesianPlane(..)
133 | IOCorticalAreaConfigurationFlag::Misc(..)
134 | IOCorticalAreaConfigurationFlag::Boolean => "Not Applicable",
135 }
136}
137
138fn io_unit_reference_from_cortical_id(cortical_id: &CorticalID) -> Option<[u8; 3]> {
139 let bytes = cortical_id.as_bytes();
140 if bytes[0] != b'i' && bytes[0] != b'o' {
141 return None;
142 }
143 Some([bytes[1], bytes[2], bytes[3]])
144}
145
146fn io_coding_options_for_unit(cortical_id: &CorticalID) -> Option<IOCodingOptions> {
147 let unit_ref = io_unit_reference_from_cortical_id(cortical_id)?;
148 let is_input = cortical_id.as_bytes()[0] == b'i';
149
150 let (accepted_type, allowed_frames) = if is_input {
151 let unit = SensoryCorticalUnit::list_all()
152 .iter()
153 .find(|u| u.get_cortical_id_unit_reference() == unit_ref)?;
154 (
155 unit.get_accepted_wrapped_io_data_type(),
156 unit.get_allowed_frame_change_handling(),
157 )
158 } else {
159 let unit = MotorCorticalUnit::list_all()
160 .iter()
161 .find(|u| u.get_cortical_id_unit_reference() == unit_ref)?;
162 (
163 unit.get_accepted_wrapped_io_data_type(),
164 unit.get_allowed_frame_change_handling(),
165 )
166 };
167
168 let mut signage_options = Vec::new();
169 let mut behavior_options = Vec::new();
170 let mut coding_type_options = Vec::new();
171
172 let io_flag = match cortical_id.extract_io_data_flag() {
173 Ok(flag) => flag,
174 Err(err) => {
175 warn!(
176 target: "feagi-services",
177 "[IO-CODING] {} failed to extract io_flag: {} (accepted_type={})",
178 cortical_id,
179 err,
180 accepted_type
181 );
182 return None;
183 }
184 };
185 signage_options.push(signage_label_from_flag(&io_flag).to_string());
186
187 let supports_frame_handling = !matches!(io_flag, IOCorticalAreaConfigurationFlag::Boolean);
188 if supports_frame_handling {
189 if let Some(frames) = allowed_frames {
190 for frame in frames {
191 behavior_options.push(frame_handling_label(*frame).to_string());
192 }
193 } else {
194 behavior_options.push("Absolute".to_string());
195 behavior_options.push("Incremental".to_string());
196 }
197 } else {
198 behavior_options.push("Not Applicable".to_string());
199 }
200
201 let supports_positioning = matches!(
202 io_flag,
203 IOCorticalAreaConfigurationFlag::Percentage(..)
204 | IOCorticalAreaConfigurationFlag::Percentage2D(..)
205 | IOCorticalAreaConfigurationFlag::Percentage3D(..)
206 | IOCorticalAreaConfigurationFlag::Percentage4D(..)
207 | IOCorticalAreaConfigurationFlag::SignedPercentage(..)
208 | IOCorticalAreaConfigurationFlag::SignedPercentage2D(..)
209 | IOCorticalAreaConfigurationFlag::SignedPercentage3D(..)
210 | IOCorticalAreaConfigurationFlag::SignedPercentage4D(..)
211 );
212 if supports_positioning {
213 coding_type_options.push("Linear".to_string());
214 coding_type_options.push("Fractional".to_string());
215 } else {
216 coding_type_options.push("Not Applicable".to_string());
217 }
218
219 if signage_options.is_empty() {
220 warn!(
221 target: "feagi-services",
222 "[IO-CODING] {} empty signage_options (accepted_type={}, io_flag={:?})",
223 cortical_id,
224 accepted_type,
225 io_flag
226 );
227 }
228 Some(IOCodingOptions {
229 signage_options,
230 behavior_options,
231 coding_type_options,
232 })
233}
234
235pub struct GenomeServiceImpl {
237 connectome: Arc<RwLock<ConnectomeManager>>,
238 parameter_queue: Option<ParameterUpdateQueue>,
239 current_genome: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
242 genome_load_counter: Arc<RwLock<i32>>,
244 genome_load_timestamp: Arc<RwLock<Option<i64>>>,
246 burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
248}
249
250impl GenomeServiceImpl {
251 pub fn new(connectome: Arc<RwLock<ConnectomeManager>>) -> Self {
252 Self {
253 connectome,
254 parameter_queue: None,
255 current_genome: Arc::new(RwLock::new(None)),
256 genome_load_counter: Arc::new(RwLock::new(0)),
257 genome_load_timestamp: Arc::new(RwLock::new(None)),
258 burst_runner: None,
259 }
260 }
261
262 pub fn new_with_parameter_queue(
263 connectome: Arc<RwLock<ConnectomeManager>>,
264 parameter_queue: ParameterUpdateQueue,
265 ) -> Self {
266 Self {
267 connectome,
268 parameter_queue: Some(parameter_queue),
269 current_genome: Arc::new(RwLock::new(None)),
270 genome_load_counter: Arc::new(RwLock::new(0)),
271 genome_load_timestamp: Arc::new(RwLock::new(None)),
272 burst_runner: None,
273 }
274 }
275
276 pub fn set_burst_runner(&mut self, burst_runner: Arc<RwLock<BurstLoopRunner>>) {
278 self.burst_runner = Some(burst_runner);
279 }
280
281 fn refresh_burst_runner_cache(&self) {
283 if let Some(ref burst_runner) = self.burst_runner {
284 let manager = self.connectome.read();
285 let mappings = manager.get_all_cortical_idx_to_id_mappings();
286 let chunk_sizes = manager.get_all_visualization_granularities();
287 let mapping_count = mappings.len();
288 let burst_runner_write = burst_runner.write();
289 burst_runner_write.refresh_cortical_id_mappings(mappings);
290 burst_runner_write.refresh_visualization_granularities(chunk_sizes);
291 info!(target: "feagi-services", "Refreshed burst runner cache with {} cortical areas", mapping_count);
292 }
293 }
294
295 pub fn get_current_genome_arc(&self) -> Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>> {
298 Arc::clone(&self.current_genome)
299 }
300}
301
302#[async_trait]
303impl GenomeService for GenomeServiceImpl {
304 async fn load_genome(&self, params: LoadGenomeParams) -> ServiceResult<GenomeInfo> {
305 info!(target: "feagi-services", "Loading genome from JSON");
306
307 let mut genome = feagi_evolutionary::load_genome_from_json(¶ms.json_str)
309 .map_err(|e| ServiceError::InvalidInput(format!("Failed to parse genome: {}", e)))?;
310 let (_areas_added, morphs_added) = feagi_evolutionary::ensure_core_components(&mut genome);
311 if morphs_added > 0 {
312 info!(
313 target: "feagi-services",
314 "Added {} missing core morphologies during genome load",
315 morphs_added
316 );
317 }
318
319 let simulation_timestep = genome.physiology.simulation_timestep;
321 info!(target: "feagi-services", "Genome simulation_timestep: {} seconds", simulation_timestep);
322
323 info!(target: "feagi-services", "Storing RuntimeGenome with {} cortical areas, {} morphologies",
325 genome.cortical_areas.len(), genome.morphologies.iter().count());
326 *self.current_genome.write() = Some(genome.clone());
327
328 let genome_num = {
330 let mut counter = self.genome_load_counter.write();
331 *counter += 1;
332 *counter
333 };
334
335 let genome_timestamp = std::time::SystemTime::now()
336 .duration_since(std::time::UNIX_EPOCH)
337 .ok()
338 .map(|d| d.as_secs() as i64);
339
340 *self.genome_load_timestamp.write() = genome_timestamp;
341
342 info!(target: "feagi-services", "Genome load #{}, timestamp: {:?}", genome_num, genome_timestamp);
343
344 let connectome_clone = self.connectome.clone();
351 let blocking_handle = tokio::task::spawn_blocking(
352 move || -> Result<feagi_brain_development::neuroembryogenesis::DevelopmentProgress, ServiceError> {
353 let mut genome_clone = genome;
355 let (prepare_result, resize_result) = {
356 let mut manager = connectome_clone.write();
357 let prepare_result = manager.prepare_for_new_genome();
358 let resize_result = prepare_result
359 .as_ref()
360 .ok()
361 .map(|_| manager.resize_for_genome(&genome_clone));
362 (prepare_result, resize_result)
363 }; prepare_result.map_err(|e| {
366 tracing::error!(target: "feagi-services", "prepare_for_new_genome failed: {}", e);
367 ServiceError::from(e)
368 })?;
369 if let Some(resize_result) = resize_result {
370 resize_result.map_err(|e| {
371 tracing::error!(target: "feagi-services", "resize_for_genome failed: {}", e);
372 ServiceError::from(e)
373 })?;
374 }
375
376 let manager_arc = feagi_brain_development::ConnectomeManager::instance();
379 let mut neuro = Neuroembryogenesis::new(manager_arc.clone());
380
381 let develop_result = neuro.develop_from_genome(&genome_clone);
382 let develop_result = match develop_result {
383 Ok(()) => Ok(()),
384 Err(e) => {
385 let err_str = e.to_string();
386 let is_already_exists = err_str.contains("already exists");
387 if is_already_exists {
388 tracing::warn!(
389 target: "feagi-services",
390 "Neuroembryogenesis failed with 'already exists' - clearing connectome and retrying once: {}",
391 err_str
392 );
393 let (retry_prepare, retry_resize) = {
395 let mut manager = connectome_clone.write();
396 let prep = manager.prepare_for_new_genome();
397 let resize = prep.as_ref().ok().map(|_| manager.resize_for_genome(&genome_clone));
398 (prep, resize)
399 };
400 retry_prepare.map_err(|e| {
401 tracing::error!(target: "feagi-services", "Retry prepare_for_new_genome failed: {}", e);
402 ServiceError::from(e)
403 })?;
404 if let Some(res) = retry_resize {
405 res.map_err(|e| {
406 tracing::error!(target: "feagi-services", "Retry resize_for_genome failed: {}", e);
407 ServiceError::from(e)
408 })?;
409 }
410 let mut neuro_retry = Neuroembryogenesis::new(manager_arc.clone());
411 neuro_retry.develop_from_genome(&genome_clone).map_err(|e| {
412 tracing::error!(target: "feagi-services", "Neuroembryogenesis retry failed: {}", e);
413 ServiceError::Backend(format!(
414 "Neuroembryogenesis failed: {}. Retry after connectome reset also failed.",
415 err_str
416 ))
417 })?;
418 neuro = neuro_retry;
419 Ok(())
420 } else {
421 tracing::error!(target: "feagi-services", "Neuroembryogenesis failed: {}", e);
422 Err(ServiceError::Backend(format!("Neuroembryogenesis failed: {}", e)))
423 }
424 }
425 };
426 develop_result?;
427
428 {
431 let mut manager = manager_arc.write();
432 manager.ensure_core_cortical_areas().map_err(|e| {
433 tracing::error!(target: "feagi-services", "ensure_core_cortical_areas failed: {}", e);
434 ServiceError::Backend(format!("Failed to ensure core cortical areas: {}", e))
435 })?;
436 }
437
438 let root_region_id = manager_arc.read().get_root_region_id();
440 if let Some(root_id) = root_region_id {
441 genome_clone.metadata.brain_regions_root = Some(root_id);
442 info!(target: "feagi-services", "✅ Set genome brain_regions_root: {}", genome_clone.metadata.brain_regions_root.as_ref().unwrap());
443 } else {
444 warn!(target: "feagi-services", "⚠️ No root region found after neuroembryogenesis");
445 }
446
447 Ok(neuro.get_progress())
448 },
449 );
450
451 let progress = match tokio::time::timeout(
453 tokio::time::Duration::from_secs(300), blocking_handle,
455 )
456 .await
457 {
458 Ok(Ok(result)) => result?,
459 Ok(Err(e)) => {
460 return Err(ServiceError::Backend(format!(
461 "Blocking task panicked: {}",
462 e
463 )))
464 }
465 Err(_) => {
466 warn!(target: "feagi-services", "Genome loading timed out after 5 minutes - aborting");
468 return Err(ServiceError::Backend(
469 "Genome loading timed out after 5 minutes".to_string(),
470 ));
471 }
472 };
473
474 info!(
475 target: "feagi-services",
476 "Genome loaded: {} cortical areas, {} neurons, {} synapses created",
477 progress.cortical_areas_created,
478 progress.neurons_created,
479 progress.synapses_created
480 );
481
482 let brain_regions_from_bdu = {
486 let manager = self.connectome.read();
487 let hierarchy = manager.get_brain_region_hierarchy();
488 hierarchy.get_all_regions()
489 };
490
491 if !brain_regions_from_bdu.is_empty() {
492 let mut current_genome_guard = self.current_genome.write();
493 if let Some(ref mut genome) = *current_genome_guard {
494 if brain_regions_from_bdu.len() > genome.brain_regions.len() {
496 info!(
497 target: "feagi-services",
498 "Syncing {} auto-generated brain regions from BDU to RuntimeGenome",
499 brain_regions_from_bdu.len()
500 );
501 genome.brain_regions = brain_regions_from_bdu;
502 }
503 }
504 }
505
506 let (cortical_area_count, brain_region_count) = {
508 let manager = self.connectome.read();
509 let cortical_area_count = manager.get_cortical_area_count();
510 let brain_region_ids = manager.get_brain_region_ids();
511 let brain_region_count = brain_region_ids.len();
512 (cortical_area_count, brain_region_count)
513 };
514
515 self.refresh_burst_runner_cache();
517
518 Ok(GenomeInfo {
519 genome_id: "current".to_string(),
520 genome_title: "Current Genome".to_string(),
521 version: "2.1".to_string(),
522 cortical_area_count,
523 brain_region_count,
524 simulation_timestep, genome_num: Some(genome_num), genome_timestamp, })
528 }
529
530 async fn save_genome(&self, params: SaveGenomeParams) -> ServiceResult<String> {
531 info!(target: "feagi-services", "Saving genome to JSON");
532
533 let genome_opt = self.current_genome.read().clone();
535
536 let mut genome = genome_opt.ok_or_else(|| {
537 ServiceError::Internal(
538 "No RuntimeGenome stored. Genome must be loaded via load_genome() before it can be saved.".to_string()
539 )
540 })?;
541
542 info!(target: "feagi-services", "✅ RuntimeGenome loaded, exporting in flat format v3.0");
543
544 if let Some(id) = params.genome_id {
546 genome.metadata.genome_id = id;
547 }
548 if let Some(title) = params.genome_title {
549 genome.metadata.genome_title = title;
550 }
551
552 let json_str = feagi_evolutionary::save_genome_to_json(&genome)
554 .map_err(|e| ServiceError::Internal(format!("Failed to save genome: {}", e)))?;
555
556 info!(target: "feagi-services", "✅ Genome exported successfully (flat format v3.0)");
557 Ok(json_str)
558 }
559
560 async fn get_genome_info(&self) -> ServiceResult<GenomeInfo> {
561 trace!(target: "feagi-services", "Getting genome info");
562
563 let (cortical_area_count, brain_region_count) = {
565 let manager = self.connectome.read();
566 let cortical_area_count = manager.get_cortical_area_count();
567 let brain_region_ids = manager.get_brain_region_ids();
568 let brain_region_count = brain_region_ids.len();
569 trace!(
570 target: "feagi-services",
571 "Reading genome info: {} cortical areas, {} brain regions",
572 cortical_area_count,
573 brain_region_count
574 );
575 trace!(
576 target: "feagi-services",
577 "Brain region IDs: {:?}",
578 brain_region_ids.iter().take(10).collect::<Vec<_>>()
579 );
580 (cortical_area_count, brain_region_count)
581 }; let simulation_timestep = {
585 let genome_opt = self.current_genome.read();
586 genome_opt
587 .as_ref()
588 .map(|g| g.physiology.simulation_timestep)
589 .unwrap_or(0.025) };
591
592 let genome_num = {
594 let counter = self.genome_load_counter.read();
595 if *counter > 0 {
596 Some(*counter)
597 } else {
598 None }
600 };
601
602 let genome_timestamp = *self.genome_load_timestamp.read();
603
604 Ok(GenomeInfo {
605 genome_id: "current".to_string(),
606 genome_title: "Current Genome".to_string(),
607 version: "2.1".to_string(),
608 cortical_area_count,
609 brain_region_count,
610 simulation_timestep,
611 genome_num,
612 genome_timestamp,
613 })
614 }
615
616 async fn validate_genome(&self, json_str: String) -> ServiceResult<bool> {
617 trace!(target: "feagi-services", "Validating genome JSON");
618
619 let genome = feagi_evolutionary::load_genome_from_json(&json_str)
621 .map_err(|e| ServiceError::InvalidInput(format!("Failed to parse genome: {}", e)))?;
622
623 let validation = feagi_evolutionary::validate_genome(&genome);
625
626 if !validation.errors.is_empty() {
627 return Err(ServiceError::InvalidInput(format!(
628 "Genome validation failed: {} errors, {} warnings. First error: {}",
629 validation.errors.len(),
630 validation.warnings.len(),
631 validation
632 .errors
633 .first()
634 .unwrap_or(&"Unknown error".to_string())
635 )));
636 }
637
638 Ok(true)
639 }
640
641 async fn reset_connectome(&self) -> ServiceResult<()> {
642 info!(target: "feagi-services", "Resetting connectome");
643
644 self.connectome
646 .write()
647 .prepare_for_new_genome()
648 .map_err(ServiceError::from)?;
649
650 info!(target: "feagi-services", "Connectome reset complete");
651 Ok(())
652 }
653
654 async fn create_cortical_areas(
655 &self,
656 params: Vec<CreateCorticalAreaParams>,
657 ) -> ServiceResult<Vec<CorticalAreaInfo>> {
658 info!(target: "feagi-services", "Creating {} new cortical areas via GenomeService", params.len());
659
660 let root_region_id = self.connectome.read().get_root_region_id();
662
663 let mut areas_to_add = Vec::new();
665 for param in ¶ms {
666 let cortical_id_typed = CorticalID::try_from_base_64(¶m.cortical_id)
668 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
669
670 let area_type = cortical_id_typed.as_cortical_type().map_err(|e| {
672 ServiceError::InvalidInput(format!("Failed to determine cortical area type: {}", e))
673 })?;
674
675 let mut area = CorticalArea::new(
677 cortical_id_typed,
678 0, param.name.clone(),
680 CorticalAreaDimensions::new(
681 param.dimensions.0 as u32,
682 param.dimensions.1 as u32,
683 param.dimensions.2 as u32,
684 )?,
685 param.position.into(), area_type,
687 )?;
688
689 if let Some(visible) = param.visible {
691 area.add_property_mut("visible".to_string(), serde_json::json!(visible));
692 }
693 if let Some(sub_group) = ¶m.sub_group {
694 area.add_property_mut("sub_group".to_string(), serde_json::json!(sub_group));
695 }
696 if let Some(neurons_per_voxel) = param.neurons_per_voxel {
697 area.add_property_mut(
698 "neurons_per_voxel".to_string(),
699 serde_json::json!(neurons_per_voxel),
700 );
701 }
702 if let Some(postsynaptic_current) = param.postsynaptic_current {
703 area.add_property_mut(
704 "postsynaptic_current".to_string(),
705 serde_json::json!(postsynaptic_current),
706 );
707 }
708 if let Some(plasticity_constant) = param.plasticity_constant {
709 area.add_property_mut(
710 "plasticity_constant".to_string(),
711 serde_json::json!(plasticity_constant),
712 );
713 }
714 if let Some(degeneration) = param.degeneration {
715 area.add_property_mut("degeneration".to_string(), serde_json::json!(degeneration));
716 }
717 if let Some(psp_uniform_distribution) = param.psp_uniform_distribution {
718 area.add_property_mut(
719 "psp_uniform_distribution".to_string(),
720 serde_json::json!(psp_uniform_distribution),
721 );
722 }
723 if let Some(firing_threshold_increment) = param.firing_threshold_increment {
724 area.add_property_mut(
725 "firing_threshold_increment".to_string(),
726 serde_json::json!(firing_threshold_increment),
727 );
728 }
729 if let Some(firing_threshold_limit) = param.firing_threshold_limit {
730 area.add_property_mut(
731 "firing_threshold_limit".to_string(),
732 serde_json::json!(firing_threshold_limit),
733 );
734 }
735 if let Some(consecutive_fire_count) = param.consecutive_fire_count {
736 area.add_property_mut(
737 "consecutive_fire_limit".to_string(),
738 serde_json::json!(consecutive_fire_count),
739 );
740 }
741 if let Some(snooze_period) = param.snooze_period {
742 area.add_property_mut(
743 "snooze_period".to_string(),
744 serde_json::json!(snooze_period),
745 );
746 }
747 if let Some(refractory_period) = param.refractory_period {
748 area.add_property_mut(
749 "refractory_period".to_string(),
750 serde_json::json!(refractory_period),
751 );
752 }
753 if let Some(leak_coefficient) = param.leak_coefficient {
754 area.add_property_mut(
755 "leak_coefficient".to_string(),
756 serde_json::json!(leak_coefficient),
757 );
758 }
759 if let Some(leak_variability) = param.leak_variability {
760 area.add_property_mut(
761 "leak_variability".to_string(),
762 serde_json::json!(leak_variability),
763 );
764 }
765 if let Some(burst_engine_active) = param.burst_engine_active {
766 area.add_property_mut(
767 "burst_engine_active".to_string(),
768 serde_json::json!(burst_engine_active),
769 );
770 }
771 if matches!(area_type, CorticalAreaType::Memory(_)) {
772 let merged = merge_memory_area_properties(
773 area.properties.clone(),
774 param.properties.as_ref(),
775 );
776 area.properties = merged;
777 } else if let Some(properties) = ¶m.properties {
778 area.properties = properties.clone();
779 }
780
781 if matches!(
783 area_type,
784 CorticalAreaType::BrainInput(_) | CorticalAreaType::BrainOutput(_)
785 ) && !area.properties.contains_key("parent_region_id")
786 {
787 if let Some(ref root_id) = root_region_id {
788 area.add_property_mut(
789 "parent_region_id".to_string(),
790 serde_json::Value::String(root_id.clone()),
791 );
792 }
793 }
794
795 areas_to_add.push(area);
796 }
797
798 {
800 let mut genome_lock = self.current_genome.write();
801 if let Some(ref mut genome) = *genome_lock {
802 for area in &areas_to_add {
803 genome.cortical_areas.insert(area.cortical_id, area.clone());
804 info!(target: "feagi-services", "Added {} to runtime genome", area.cortical_id.as_base_64());
805 if let Some(parent) = area
806 .properties
807 .get("parent_region_id")
808 .and_then(|v| v.as_str())
809 {
810 let region = genome.brain_regions.get_mut(parent).ok_or_else(|| {
811 ServiceError::InvalidInput(format!(
812 "Unknown parent_region_id '{}' for new cortical area {}",
813 parent,
814 area.cortical_id.as_base_64()
815 ))
816 })?;
817 region.cortical_areas.insert(area.cortical_id);
818 }
819 }
820 } else {
821 return Err(ServiceError::Backend("No genome loaded".to_string()));
822 }
823 }
824
825 let genome_clone = {
827 let genome_lock = self.current_genome.read();
828 genome_lock
829 .as_ref()
830 .ok_or_else(|| ServiceError::Backend("No genome loaded".to_string()))?
831 .clone()
832 };
833
834 let (neurons_created, synapses_created) = {
836 let connectome_clone = self.connectome.clone();
837 tokio::task::spawn_blocking(move || {
838 let mut neuro = Neuroembryogenesis::new(connectome_clone);
839 neuro.add_cortical_areas(areas_to_add.clone(), &genome_clone)
840 })
841 .await
842 .map_err(|e| ServiceError::Backend(format!("Neuroembryogenesis task failed: {}", e)))?
843 .map_err(|e| ServiceError::Backend(format!("Neuroembryogenesis failed: {}", e)))?
844 };
845
846 info!(target: "feagi-services",
847 "✅ Created {} cortical areas: {} neurons, {} synapses",
848 params.len(), neurons_created, synapses_created);
849
850 self.refresh_burst_runner_cache();
852
853 let mut created_areas = Vec::new();
855 for param in ¶ms {
856 match self.get_cortical_area_info(¶m.cortical_id).await {
857 Ok(area_info) => created_areas.push(area_info),
858 Err(e) => {
859 warn!(target: "feagi-services", "Created area {} but failed to fetch info: {}", param.cortical_id, e);
860 return Err(ServiceError::Backend(format!(
861 "Created areas but failed to fetch info for {}: {}",
862 param.cortical_id, e
863 )));
864 }
865 }
866 }
867
868 Ok(created_areas)
869 }
870
871 async fn update_cortical_area(
872 &self,
873 cortical_id: &str,
874 changes: HashMap<String, Value>,
875 ) -> ServiceResult<CorticalAreaInfo> {
876 info!(target: "feagi-services", "Updating cortical area: {} with {} changes", cortical_id, changes.len());
877
878 let cortical_id_typed = feagi_evolutionary::string_to_cortical_id(cortical_id)
880 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
881
882 let mut changes = changes;
883 let mut effective_cortical_id = cortical_id_typed;
884 let mut effective_cortical_id_str = cortical_id.to_string();
885
886 {
891 let manager = self.connectome.read();
892 if !manager.has_cortical_area(&effective_cortical_id) {
893 let target_bytes = effective_cortical_id.as_bytes();
894 let mut candidates: Vec<_> = manager
895 .get_cortical_area_ids()
896 .iter()
897 .map(|c| **c)
898 .filter(|candidate| {
899 let c = candidate.as_bytes();
900 c[0] == target_bytes[0] && c[1] == target_bytes[1]
902 && c[2] == target_bytes[2]
903 && c[3] == target_bytes[3] && c[6] == target_bytes[6] && c[7] == target_bytes[7] })
907 .collect();
908 if candidates.len() == 1 {
909 let resolved = candidates.remove(0);
910 info!(
911 target: "feagi-services",
912 "[GENOME-UPDATE] Resolved remapped cortical ID '{}' -> '{}'",
913 cortical_id,
914 resolved.as_base_64()
915 );
916 effective_cortical_id = resolved;
917 effective_cortical_id_str = resolved.as_base_64();
918 } else {
919 return Err(ServiceError::NotFound {
920 resource: "CorticalArea".to_string(),
921 id: cortical_id.to_string(),
922 });
923 }
924 }
925 }
926
927 if changes.contains_key("group_id") {
928 let new_id = self.apply_unit_index_update(
929 &effective_cortical_id,
930 &effective_cortical_id_str,
931 &changes,
932 )?;
933 effective_cortical_id = new_id;
934 effective_cortical_id_str = new_id.as_base_64();
935 self.regenerate_mappings_for_area(&effective_cortical_id)?;
936 changes.remove("group_id");
937 if changes.is_empty() {
938 return self
939 .get_cortical_area_info(&effective_cortical_id_str)
940 .await;
941 }
942 }
943
944 let change_type = CorticalChangeClassifier::classify_changes(&changes);
946 CorticalChangeClassifier::log_classification_result(&changes, change_type);
947
948 match change_type {
950 ChangeType::Parameter => {
951 self.update_parameters_only(&effective_cortical_id_str, changes)
953 .await
954 }
955 ChangeType::Metadata => {
956 self.update_metadata_only(&effective_cortical_id_str, changes)
958 .await
959 }
960 ChangeType::Structural => {
961 self.update_with_localized_rebuild(&effective_cortical_id_str, changes)
963 .await
964 }
965 ChangeType::Hybrid => {
966 let separated = CorticalChangeClassifier::separate_changes_by_type(&changes);
968
969 if let Some(metadata_changes) = separated.get(&ChangeType::Metadata) {
971 if !metadata_changes.is_empty() {
972 self.update_metadata_only(
973 &effective_cortical_id_str,
974 metadata_changes.clone(),
975 )
976 .await?;
977 }
978 }
979
980 if let Some(param_changes) = separated.get(&ChangeType::Parameter) {
981 if !param_changes.is_empty() {
982 self.update_parameters_only(
983 &effective_cortical_id_str,
984 param_changes.clone(),
985 )
986 .await?;
987 }
988 }
989
990 if let Some(struct_changes) = separated.get(&ChangeType::Structural) {
991 if !struct_changes.is_empty() {
992 self.update_with_localized_rebuild(
993 &effective_cortical_id_str,
994 struct_changes.clone(),
995 )
996 .await?;
997 }
998 }
999
1000 self.get_cortical_area_info(&effective_cortical_id_str)
1002 .await
1003 }
1004 }
1005 }
1006}
1007
1008impl GenomeServiceImpl {
1009 async fn update_parameters_only(
1013 &self,
1014 cortical_id: &str,
1015 changes: HashMap<String, Value>,
1016 ) -> ServiceResult<CorticalAreaInfo> {
1017 info!(target: "feagi-services", "[FAST-UPDATE] Parameter-only update for {}", cortical_id);
1018
1019 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
1021 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
1022 let cortical_idx = {
1024 let manager = self.connectome.read();
1025 manager
1026 .get_cortical_idx(&cortical_id_typed)
1027 .ok_or_else(|| ServiceError::NotFound {
1028 resource: "CorticalArea".to_string(),
1029 id: cortical_id.to_string(),
1030 })?
1031 };
1032
1033 if let Some(queue) = &self.parameter_queue {
1035 let base_threshold = {
1037 let manager = self.connectome.read();
1038 manager
1039 .get_cortical_area(&cortical_id_typed)
1040 .map(|area| area.firing_threshold())
1041 };
1042
1043 for (param_name, value) in &changes {
1044 let classifier = CorticalChangeClassifier::parameter_changes();
1046 if classifier.contains(param_name.as_str()) {
1047 let bt = if param_name == "neuron_fire_threshold_increment"
1049 || param_name == "firing_threshold_increment"
1050 {
1051 base_threshold
1052 } else {
1053 None
1054 };
1055
1056 queue.push(feagi_npu_burst_engine::ParameterUpdate {
1057 cortical_idx,
1058 cortical_id: cortical_id.to_string(),
1059 parameter_name: param_name.clone(),
1060 value: value.clone(),
1061 dimensions: None, neurons_per_voxel: None,
1063 base_threshold: bt,
1064 });
1065 trace!(
1066 target: "feagi-services",
1067 "[PARAM-QUEUE] Queued {}={} for area {}",
1068 param_name,
1069 value,
1070 cortical_id
1071 );
1072 }
1073 }
1074 info!(target: "feagi-services", "[FAST-UPDATE] Queued parameter updates (will apply in next burst)");
1075 } else {
1076 warn!(target: "feagi-services", "Parameter queue not available - updates will not affect neurons");
1077 }
1078
1079 {
1085 let mut manager = self.connectome.write();
1086 if let Some(area) = manager.get_cortical_area_mut(&cortical_id_typed) {
1087 for (key, value) in &changes {
1088 match key.as_str() {
1089 "firing_threshold" | "neuron_fire_threshold" => {
1091 if let Some(v) = value.as_f64() {
1092 area.properties
1093 .insert("firing_threshold".to_string(), serde_json::json!(v));
1094 }
1095 }
1096 "firing_threshold_limit" | "neuron_firing_threshold_limit" => {
1097 if let Some(v) = value.as_f64() {
1098 area.properties.insert(
1099 "firing_threshold_limit".to_string(),
1100 serde_json::json!(v),
1101 );
1102 }
1103 }
1104 "firing_threshold_increment_x" => {
1106 if let Some(v) = value.as_f64() {
1107 area.properties.insert(
1108 "firing_threshold_increment_x".to_string(),
1109 serde_json::json!(v),
1110 );
1111 }
1112 }
1113 "firing_threshold_increment_y" => {
1114 if let Some(v) = value.as_f64() {
1115 area.properties.insert(
1116 "firing_threshold_increment_y".to_string(),
1117 serde_json::json!(v),
1118 );
1119 }
1120 }
1121 "firing_threshold_increment_z" => {
1122 if let Some(v) = value.as_f64() {
1123 area.properties.insert(
1124 "firing_threshold_increment_z".to_string(),
1125 serde_json::json!(v),
1126 );
1127 }
1128 }
1129 "firing_threshold_increment" | "neuron_fire_threshold_increment" => {
1130 if let Some(arr) = value.as_array() {
1131 if arr.len() == 3 {
1132 if let (Some(x), Some(y), Some(z)) =
1133 (arr[0].as_f64(), arr[1].as_f64(), arr[2].as_f64())
1134 {
1135 area.properties.insert(
1136 "firing_threshold_increment_x".to_string(),
1137 serde_json::json!(x),
1138 );
1139 area.properties.insert(
1140 "firing_threshold_increment_y".to_string(),
1141 serde_json::json!(y),
1142 );
1143 area.properties.insert(
1144 "firing_threshold_increment_z".to_string(),
1145 serde_json::json!(z),
1146 );
1147 }
1148 }
1149 } else if let Some(obj) = value.as_object() {
1150 if let (Some(x), Some(y), Some(z)) = (
1151 obj.get("x").and_then(|v| v.as_f64()),
1152 obj.get("y").and_then(|v| v.as_f64()),
1153 obj.get("z").and_then(|v| v.as_f64()),
1154 ) {
1155 area.properties.insert(
1156 "firing_threshold_increment_x".to_string(),
1157 serde_json::json!(x),
1158 );
1159 area.properties.insert(
1160 "firing_threshold_increment_y".to_string(),
1161 serde_json::json!(y),
1162 );
1163 area.properties.insert(
1164 "firing_threshold_increment_z".to_string(),
1165 serde_json::json!(z),
1166 );
1167 }
1168 }
1169 }
1170
1171 "refractory_period" | "neuron_refractory_period" | "refrac" => {
1173 if let Some(v) = value.as_u64() {
1174 area.properties.insert(
1175 "refractory_period".to_string(),
1176 serde_json::json!(v as u32),
1177 );
1178 }
1179 }
1180 "leak_coefficient" | "neuron_leak_coefficient" | "leak" => {
1181 if let Some(v) = value.as_f64() {
1182 area.properties
1183 .insert("leak_coefficient".to_string(), serde_json::json!(v));
1184 }
1185 }
1186
1187 "consecutive_fire_cnt_max"
1189 | "neuron_consecutive_fire_count"
1190 | "consecutive_fire_count" => {
1191 if let Some(v) = value.as_u64() {
1192 area.properties.insert(
1194 "consecutive_fire_limit".to_string(),
1195 serde_json::json!(v as u32),
1196 );
1197 }
1198 }
1199 "snooze_length" | "neuron_snooze_period" | "snooze_period" => {
1200 if let Some(v) = value.as_u64() {
1201 area.properties.insert(
1203 "snooze_period".to_string(),
1204 serde_json::json!(v as u32),
1205 );
1206 }
1207 }
1208
1209 "neuron_excitability" | "excitability" => {
1211 if let Some(v) = value.as_f64() {
1212 if (0.0..=1.0).contains(&v) {
1213 area.properties.insert(
1214 "neuron_excitability".to_string(),
1215 serde_json::json!(v),
1216 );
1217 } else {
1218 warn!(
1219 target: "feagi-services",
1220 "[FAST-UPDATE] Ignoring neuron_excitability={} for area {} (expected 0..=1)",
1221 v,
1222 cortical_id
1223 );
1224 }
1225 }
1226 }
1227
1228 "postsynaptic_current" | "neuron_post_synaptic_potential" => {
1230 if let Some(v) = value.as_f64() {
1231 area.properties.insert(
1232 "postsynaptic_current".to_string(),
1233 serde_json::json!(v),
1234 );
1235 }
1236 }
1237 "postsynaptic_current_max" | "neuron_post_synaptic_potential_max" => {
1238 if let Some(v) = value.as_f64() {
1239 area.properties.insert(
1240 "postsynaptic_current_max".to_string(),
1241 serde_json::json!(v),
1242 );
1243 }
1244 }
1245 "degeneration" | "neuron_degeneracy_coefficient" => {
1246 if let Some(v) = value.as_f64() {
1247 area.properties
1248 .insert("degeneration".to_string(), serde_json::json!(v));
1249 }
1250 }
1251 "plasticity_constant" | "neuron_plasticity_constant" => {
1252 if let Some(v) = value.as_f64() {
1253 area.properties.insert(
1254 "plasticity_constant".to_string(),
1255 serde_json::json!(v),
1256 );
1257 }
1258 }
1259 "psp_uniform_distribution" | "neuron_psp_uniform_distribution" => {
1260 if let Some(v) = value.as_bool() {
1261 area.properties.insert(
1262 "psp_uniform_distribution".to_string(),
1263 serde_json::json!(v),
1264 );
1265 }
1266 }
1267
1268 "init_lifespan" | "neuron_init_lifespan" => {
1270 if let Some(v) = value.as_u64() {
1271 area.properties.insert(
1272 "init_lifespan".to_string(),
1273 serde_json::json!(v as u32),
1274 );
1275 }
1276 }
1277 "lifespan_growth_rate" | "neuron_lifespan_growth_rate" => {
1278 if let Some(v) =
1280 value.as_f64().or_else(|| value.as_u64().map(|u| u as f64))
1281 {
1282 area.properties.insert(
1283 "lifespan_growth_rate".to_string(),
1284 serde_json::json!(v as f32),
1285 );
1286 }
1287 }
1288 "longterm_mem_threshold" | "neuron_longterm_mem_threshold" => {
1289 if let Some(v) = value.as_u64() {
1290 area.properties.insert(
1291 "longterm_mem_threshold".to_string(),
1292 serde_json::json!(v as u32),
1293 );
1294 }
1295 }
1296 "temporal_depth" => {
1297 if let Some(v) = value.as_u64() {
1298 if v == 0 {
1299 warn!(
1300 target: "feagi-services",
1301 "[FAST-UPDATE] Ignoring temporal_depth=0 for area {} (temporal_depth must be >= 1)",
1302 cortical_id
1303 );
1304 } else {
1305 area.properties.insert(
1306 "temporal_depth".to_string(),
1307 serde_json::json!(v as u32),
1308 );
1309 }
1310 }
1311 }
1312
1313 "mp_charge_accumulation" | "neuron_mp_charge_accumulation" => {
1315 if let Some(v) = value.as_bool() {
1316 area.properties.insert(
1317 "mp_charge_accumulation".to_string(),
1318 serde_json::json!(v),
1319 );
1320 }
1321 }
1322 "mp_driven_psp" | "neuron_mp_driven_psp" => {
1323 if let Some(v) = value.as_bool() {
1324 area.properties
1325 .insert("mp_driven_psp".to_string(), serde_json::json!(v));
1326 }
1327 }
1328
1329 "burst_engine_active" => {
1331 if let Some(v) = value.as_bool() {
1332 area.properties.insert(
1333 "burst_engine_active".to_string(),
1334 serde_json::json!(v),
1335 );
1336 }
1337 }
1338 "visualization_voxel_granularity" => {
1339 if let Some(arr) = value.as_array() {
1342 if arr.len() == 3 {
1343 let x_opt = arr[0]
1345 .as_u64()
1346 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1347 let y_opt = arr[1]
1348 .as_u64()
1349 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1350 let z_opt = arr[2]
1351 .as_u64()
1352 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1353
1354 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1355 let x_u32 = x as u32;
1356 let y_u32 = y as u32;
1357 let z_u32 = z as u32;
1358
1359 if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
1361 area.properties
1363 .remove("visualization_voxel_granularity");
1364 } else {
1365 area.properties.insert(
1367 "visualization_voxel_granularity".to_string(),
1368 serde_json::json!([x_u32, y_u32, z_u32]),
1369 );
1370 }
1371 }
1372 }
1373 }
1374 }
1375
1376 _ => {}
1377 }
1378 }
1379 }
1380 manager.refresh_cortical_area_hashes(true, false);
1381 }
1382
1383 if let Some(genome) = self.current_genome.write().as_mut() {
1385 if let Some(area) = genome.cortical_areas.get_mut(&cortical_id_typed) {
1386 trace!(
1387 target: "feagi-services",
1388 "[GENOME-UPDATE] Updating RuntimeGenome for area {}",
1389 cortical_id
1390 );
1391 for (key, value) in &changes {
1392 match key.as_str() {
1393 "neuron_fire_threshold" | "firing_threshold" => {
1394 if let Some(v) = value.as_f64() {
1395 area.properties
1396 .insert("firing_threshold".to_string(), serde_json::json!(v));
1397 }
1398 }
1399 "firing_threshold_limit" | "neuron_firing_threshold_limit" => {
1400 if let Some(v) = value.as_f64() {
1401 area.properties.insert(
1402 "firing_threshold_limit".to_string(),
1403 serde_json::json!(v),
1404 );
1405 }
1406 }
1407 "firing_threshold_increment_x" => {
1408 if let Some(v) = value.as_f64() {
1409 area.properties.insert(
1410 "firing_threshold_increment_x".to_string(),
1411 serde_json::json!(v),
1412 );
1413 }
1414 }
1415 "firing_threshold_increment_y" => {
1416 if let Some(v) = value.as_f64() {
1417 area.properties.insert(
1418 "firing_threshold_increment_y".to_string(),
1419 serde_json::json!(v),
1420 );
1421 }
1422 }
1423 "firing_threshold_increment_z" => {
1424 if let Some(v) = value.as_f64() {
1425 area.properties.insert(
1426 "firing_threshold_increment_z".to_string(),
1427 serde_json::json!(v),
1428 );
1429 }
1430 }
1431 "leak_coefficient" | "neuron_leak_coefficient" => {
1432 if let Some(v) = value.as_f64() {
1433 area.properties
1434 .insert("leak_coefficient".to_string(), serde_json::json!(v));
1435 }
1436 }
1437 "leak_variability" | "neuron_leak_variability" => {
1438 if let Some(v) = value.as_f64() {
1439 area.properties
1440 .insert("leak_variability".to_string(), serde_json::json!(v));
1441 }
1442 }
1443 "refractory_period" | "neuron_refractory_period" => {
1444 if let Some(v) = value.as_u64() {
1445 area.properties.insert(
1446 "refractory_period".to_string(),
1447 serde_json::json!(v as u32),
1448 );
1449 }
1450 }
1451 "snooze_period" | "neuron_snooze_period" => {
1452 if let Some(v) = value.as_u64() {
1453 area.properties.insert(
1455 "snooze_length".to_string(),
1456 serde_json::json!(v as u32),
1457 );
1458 }
1459 }
1460 "consecutive_fire_count" | "neuron_consecutive_fire_count" => {
1461 if let Some(v) = value.as_u64() {
1462 area.properties.insert(
1464 "consecutive_fire_cnt_max".to_string(),
1465 serde_json::json!(v as u32),
1466 );
1467 }
1468 }
1469 "postsynaptic_current" | "neuron_post_synaptic_potential" => {
1470 if let Some(v) = value.as_f64() {
1471 area.properties.insert(
1472 "postsynaptic_current".to_string(),
1473 serde_json::json!(v),
1474 );
1475 }
1476 }
1477 "postsynaptic_current_max" | "neuron_post_synaptic_potential_max" => {
1478 if let Some(v) = value.as_f64() {
1479 area.properties.insert(
1480 "postsynaptic_current_max".to_string(),
1481 serde_json::json!(v),
1482 );
1483 }
1484 }
1485 "plasticity_constant" | "neuron_plasticity_constant" => {
1486 if let Some(v) = value.as_f64() {
1487 area.properties.insert(
1488 "plasticity_constant".to_string(),
1489 serde_json::json!(v),
1490 );
1491 }
1492 }
1493 "degeneration" | "neuron_degeneracy_coefficient" => {
1494 if let Some(v) = value.as_f64() {
1495 area.properties
1496 .insert("degeneration".to_string(), serde_json::json!(v));
1497 }
1498 }
1499 "psp_uniform_distribution" | "neuron_psp_uniform_distribution" => {
1500 if let Some(v) = value.as_bool() {
1501 area.properties.insert(
1502 "psp_uniform_distribution".to_string(),
1503 serde_json::json!(v),
1504 );
1505 }
1506 }
1507 "mp_driven_psp" | "neuron_mp_driven_psp" => {
1508 if let Some(v) = value.as_bool() {
1509 area.properties
1510 .insert("mp_driven_psp".to_string(), serde_json::json!(v));
1511 info!(
1512 target: "feagi-services",
1513 "[GENOME-UPDATE] Updated mp_driven_psp={} in RuntimeGenome for area {}",
1514 v, cortical_id
1515 );
1516 } else {
1517 warn!(
1518 target: "feagi-services",
1519 "[GENOME-UPDATE] Failed to update mp_driven_psp: value is not a bool (got {:?})",
1520 value
1521 );
1522 }
1523 }
1524 "mp_charge_accumulation" | "neuron_mp_charge_accumulation" => {
1525 if let Some(v) = value.as_bool() {
1526 area.properties.insert(
1527 "mp_charge_accumulation".to_string(),
1528 serde_json::json!(v),
1529 );
1530 }
1531 }
1532 "neuron_excitability" => {
1533 if let Some(v) = value.as_f64() {
1534 area.properties.insert(
1535 "neuron_excitability".to_string(),
1536 serde_json::json!(v),
1537 );
1538 }
1539 }
1540 "init_lifespan" | "neuron_init_lifespan" => {
1541 if let Some(v) = value.as_u64() {
1542 area.properties.insert(
1543 "init_lifespan".to_string(),
1544 serde_json::json!(v as u32),
1545 );
1546 }
1547 }
1548 "lifespan_growth_rate" | "neuron_lifespan_growth_rate" => {
1549 if let Some(v) = value.as_u64() {
1550 area.properties.insert(
1551 "lifespan_growth_rate".to_string(),
1552 serde_json::json!(v as u32),
1553 );
1554 }
1555 }
1556 "longterm_mem_threshold" | "neuron_longterm_mem_threshold" => {
1557 if let Some(v) = value.as_u64() {
1558 area.properties.insert(
1559 "longterm_mem_threshold".to_string(),
1560 serde_json::json!(v as u32),
1561 );
1562 }
1563 }
1564 "temporal_depth" => {
1565 if let Some(v) = value.as_u64() {
1566 if v == 0 {
1567 warn!(
1568 target: "feagi-services",
1569 "[GENOME-UPDATE] Ignoring temporal_depth=0 for area {} (temporal_depth must be >= 1)",
1570 cortical_id
1571 );
1572 } else {
1573 area.properties.insert(
1574 "temporal_depth".to_string(),
1575 serde_json::json!(v as u32),
1576 );
1577 }
1578 }
1579 }
1580 "firing_threshold_increment" | "neuron_fire_threshold_increment" => {
1581 if let Some(arr) = value.as_array() {
1583 if arr.len() == 3 {
1584 if let (Some(x), Some(y), Some(z)) =
1585 (arr[0].as_f64(), arr[1].as_f64(), arr[2].as_f64())
1586 {
1587 area.properties.insert(
1588 "firing_threshold_increment_x".to_string(),
1589 serde_json::json!(x),
1590 );
1591 area.properties.insert(
1592 "firing_threshold_increment_y".to_string(),
1593 serde_json::json!(y),
1594 );
1595 area.properties.insert(
1596 "firing_threshold_increment_z".to_string(),
1597 serde_json::json!(z),
1598 );
1599 }
1600 }
1601 } else if let Some(obj) = value.as_object() {
1602 if let (Some(x), Some(y), Some(z)) = (
1604 obj.get("x").and_then(|v| v.as_f64()),
1605 obj.get("y").and_then(|v| v.as_f64()),
1606 obj.get("z").and_then(|v| v.as_f64()),
1607 ) {
1608 area.properties.insert(
1609 "firing_threshold_increment_x".to_string(),
1610 serde_json::json!(x),
1611 );
1612 area.properties.insert(
1613 "firing_threshold_increment_y".to_string(),
1614 serde_json::json!(y),
1615 );
1616 area.properties.insert(
1617 "firing_threshold_increment_z".to_string(),
1618 serde_json::json!(z),
1619 );
1620 }
1621 }
1622 }
1623 "burst_engine_active" => {
1624 if let Some(v) = value.as_bool() {
1625 area.properties.insert(
1626 "burst_engine_active".to_string(),
1627 serde_json::json!(v),
1628 );
1629 }
1630 }
1631 "visualization_voxel_granularity" => {
1632 if let Some(arr) = value.as_array() {
1635 if arr.len() == 3 {
1636 let x_opt = arr[0]
1638 .as_u64()
1639 .or_else(|| arr[0].as_f64().map(|f| f as u64));
1640 let y_opt = arr[1]
1641 .as_u64()
1642 .or_else(|| arr[1].as_f64().map(|f| f as u64));
1643 let z_opt = arr[2]
1644 .as_u64()
1645 .or_else(|| arr[2].as_f64().map(|f| f as u64));
1646
1647 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1648 let x_u32 = x as u32;
1649 let y_u32 = y as u32;
1650 let z_u32 = z as u32;
1651
1652 if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
1654 area.properties
1656 .remove("visualization_voxel_granularity");
1657 } else {
1658 area.properties.insert(
1660 "visualization_voxel_granularity".to_string(),
1661 serde_json::json!([x_u32, y_u32, z_u32]),
1662 );
1663 }
1664 }
1665 }
1666 }
1667 }
1668 _ => {}
1669 }
1670 }
1671 } else {
1672 warn!(
1673 target: "feagi-services",
1674 "[GENOME-UPDATE] WARNING: Cortical area {} not found in RuntimeGenome - property updates will not persist to saved genome!",
1675 cortical_id
1676 );
1677 }
1678 } else {
1679 warn!(
1680 target: "feagi-services",
1681 "[GENOME-UPDATE] WARNING: No RuntimeGenome loaded - property updates will not persist to saved genome!"
1682 );
1683 }
1684
1685 {
1687 let mut manager = self.connectome.write();
1688 let area = manager
1689 .get_cortical_area_mut(&cortical_id_typed)
1690 .ok_or_else(|| ServiceError::NotFound {
1691 resource: "CorticalArea".to_string(),
1692 id: cortical_id.to_string(),
1693 })?;
1694
1695 for (key, value) in &changes {
1697 match key.as_str() {
1698 "neuron_fire_threshold" | "firing_threshold" => {
1699 if let Some(v) = value.as_f64() {
1700 area.add_property_mut(
1701 "firing_threshold".to_string(),
1702 serde_json::json!(v),
1703 );
1704 }
1705 }
1706 "firing_threshold_limit" | "neuron_firing_threshold_limit" => {
1707 if let Some(v) = value.as_f64() {
1708 area.add_property_mut(
1709 "firing_threshold_limit".to_string(),
1710 serde_json::json!(v),
1711 );
1712 }
1713 }
1714 "leak_coefficient" | "neuron_leak_coefficient" => {
1715 if let Some(v) = value.as_f64() {
1716 area.add_property_mut(
1717 "leak_coefficient".to_string(),
1718 serde_json::json!(v),
1719 );
1720 }
1721 }
1722 "leak_variability" | "neuron_leak_variability" => {
1723 if let Some(v) = value.as_f64() {
1724 area.add_property_mut(
1725 "leak_variability".to_string(),
1726 serde_json::json!(v),
1727 );
1728 }
1729 }
1730 "refractory_period" | "neuron_refractory_period" => {
1731 if let Some(v) = value.as_u64() {
1732 area.add_property_mut(
1733 "refractory_period".to_string(),
1734 serde_json::json!(v as u32),
1735 );
1736 }
1737 }
1738 "snooze_period" | "neuron_snooze_period" => {
1739 if let Some(v) = value.as_u64() {
1740 area.add_property_mut(
1741 "snooze_period".to_string(),
1742 serde_json::json!(v as u32),
1743 );
1744 }
1745 }
1746 "consecutive_fire_count" | "neuron_consecutive_fire_count" => {
1747 if let Some(v) = value.as_u64() {
1748 area.add_property_mut(
1749 "consecutive_fire_limit".to_string(),
1750 serde_json::json!(v as u32),
1751 );
1752 }
1753 }
1754 "plasticity_constant" | "neuron_plasticity_constant" => {
1755 if let Some(v) = value.as_f64() {
1756 area.add_property_mut(
1757 "plasticity_constant".to_string(),
1758 serde_json::json!(v),
1759 );
1760 }
1761 }
1762 "degeneration" | "neuron_degeneracy_coefficient" => {
1763 if let Some(v) = value.as_f64() {
1764 area.add_property_mut("degeneration".to_string(), serde_json::json!(v));
1765 }
1766 }
1767 "postsynaptic_current" | "neuron_post_synaptic_potential" => {
1768 if let Some(v) = value.as_f64() {
1769 area.add_property_mut(
1770 "postsynaptic_current".to_string(),
1771 serde_json::json!(v),
1772 );
1773 }
1774 }
1775 "postsynaptic_current_max" | "neuron_post_synaptic_potential_max" => {
1776 if let Some(v) = value.as_f64() {
1777 area.add_property_mut(
1778 "postsynaptic_current_max".to_string(),
1779 serde_json::json!(v),
1780 );
1781 }
1782 }
1783 "psp_uniform_distribution" | "neuron_psp_uniform_distribution" => {
1784 if let Some(v) = value.as_bool() {
1785 area.add_property_mut(
1786 "psp_uniform_distribution".to_string(),
1787 serde_json::json!(v),
1788 );
1789 }
1790 }
1791 "mp_driven_psp" | "neuron_mp_driven_psp" => {
1792 if let Some(v) = value.as_bool() {
1793 area.add_property_mut(
1794 "mp_driven_psp".to_string(),
1795 serde_json::json!(v),
1796 );
1797 info!(
1798 target: "feagi-services",
1799 "[CONNECTOME-UPDATE] Updated mp_driven_psp={} in ConnectomeManager for area {}",
1800 v, cortical_id
1801 );
1802 }
1803 }
1804 "mp_charge_accumulation" | "neuron_mp_charge_accumulation" => {
1805 if let Some(v) = value.as_bool() {
1806 area.add_property_mut(
1807 "mp_charge_accumulation".to_string(),
1808 serde_json::json!(v),
1809 );
1810 }
1811 }
1812 "excitability" | "neuron_excitability" => {
1813 if let Some(v) = value.as_f64() {
1814 area.add_property_mut("excitability".to_string(), serde_json::json!(v));
1815 }
1816 }
1817 "init_lifespan" | "neuron_init_lifespan" => {
1818 if let Some(v) = value.as_u64() {
1819 area.add_property_mut(
1820 "init_lifespan".to_string(),
1821 serde_json::json!(v as u32),
1822 );
1823 }
1824 }
1825 "lifespan_growth_rate" | "neuron_lifespan_growth_rate" => {
1826 if let Some(v) = value.as_u64() {
1827 area.add_property_mut(
1828 "lifespan_growth_rate".to_string(),
1829 serde_json::json!(v as u32),
1830 );
1831 }
1832 }
1833 "longterm_mem_threshold" | "neuron_longterm_mem_threshold" => {
1834 if let Some(v) = value.as_u64() {
1835 area.add_property_mut(
1836 "longterm_mem_threshold".to_string(),
1837 serde_json::json!(v as u32),
1838 );
1839 }
1840 }
1841 "firing_threshold_increment" | "neuron_fire_threshold_increment" => {
1842 if let Some(arr) = value.as_array() {
1844 if arr.len() == 3 {
1845 let x = arr[0].as_f64().unwrap_or(0.0);
1846 let y = arr[1].as_f64().unwrap_or(0.0);
1847 let z = arr[2].as_f64().unwrap_or(0.0);
1848
1849 area.add_property_mut(
1851 "firing_threshold_increment".to_string(),
1852 serde_json::json!(arr),
1853 );
1854 area.add_property_mut(
1855 "firing_threshold_increment_x".to_string(),
1856 serde_json::json!(x),
1857 );
1858 area.add_property_mut(
1859 "firing_threshold_increment_y".to_string(),
1860 serde_json::json!(y),
1861 );
1862 area.add_property_mut(
1863 "firing_threshold_increment_z".to_string(),
1864 serde_json::json!(z),
1865 );
1866 }
1867 } else if let Some(obj) = value.as_object() {
1868 let x = obj.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
1870 let y = obj.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
1871 let z = obj.get("z").and_then(|v| v.as_f64()).unwrap_or(0.0);
1872
1873 area.add_property_mut(
1874 "firing_threshold_increment".to_string(),
1875 serde_json::json!([x, y, z]),
1876 );
1877 area.add_property_mut(
1878 "firing_threshold_increment_x".to_string(),
1879 serde_json::json!(x),
1880 );
1881 area.add_property_mut(
1882 "firing_threshold_increment_y".to_string(),
1883 serde_json::json!(y),
1884 );
1885 area.add_property_mut(
1886 "firing_threshold_increment_z".to_string(),
1887 serde_json::json!(z),
1888 );
1889 }
1890 }
1891 "burst_engine_active" => {
1892 if let Some(v) = value.as_bool() {
1893 area.add_property_mut(
1894 "burst_engine_active".to_string(),
1895 serde_json::json!(v),
1896 );
1897 }
1898 }
1899 _ => {}
1900 }
1901 }
1902 manager.refresh_cortical_area_hashes(true, false);
1903 }
1904
1905 #[cfg(feature = "plasticity")]
1914 {
1915 let memory_param_changed = changes.keys().any(|k| {
1916 matches!(
1917 k.as_str(),
1918 "init_lifespan"
1919 | "neuron_init_lifespan"
1920 | "lifespan_growth_rate"
1921 | "neuron_lifespan_growth_rate"
1922 | "longterm_mem_threshold"
1923 | "neuron_longterm_mem_threshold"
1924 | "temporal_depth"
1925 )
1926 });
1927
1928 if memory_param_changed {
1929 use feagi_evolutionary::extract_memory_properties;
1930 use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
1931
1932 let mut manager = self.connectome.write();
1933 if let Some(area) = manager.get_cortical_area(&cortical_id_typed) {
1934 if let Some(mem_props) = extract_memory_properties(&area.properties) {
1935 let _ = manager
1937 .refresh_upstream_cortical_areas_from_mappings(&cortical_id_typed);
1938
1939 if let Some(npu_arc) = manager.get_npu().cloned() {
1943 if let Ok(mut npu) = npu_arc.lock() {
1944 let upstream_areas =
1945 manager.get_upstream_cortical_areas(&cortical_id_typed);
1946 let existing_configs = npu.get_all_fire_ledger_configs();
1947 let desired = mem_props.temporal_depth as usize;
1948
1949 for upstream_idx in upstream_areas.iter().copied() {
1950 let existing = existing_configs
1951 .iter()
1952 .find(|(idx, _)| *idx == upstream_idx)
1953 .map(|(_, w)| *w)
1954 .unwrap_or(0);
1955 let resolved = existing.max(desired);
1956 if resolved != existing {
1957 if let Err(e) =
1958 npu.configure_fire_ledger_window(upstream_idx, resolved)
1959 {
1960 warn!(
1961 target: "feagi-services",
1962 "[GENOME-UPDATE] Failed to configure FireLedger window for upstream idx={} (requested={}): {}",
1963 upstream_idx,
1964 resolved,
1965 e
1966 );
1967 }
1968 }
1969 }
1970 } else {
1971 warn!(target: "feagi-services", "[GENOME-UPDATE] Failed to lock NPU for FireLedger update");
1972 }
1973 }
1974
1975 if let Some(executor) = manager.get_plasticity_executor() {
1977 if let Ok(exec) = executor.lock() {
1978 let upstream_areas =
1979 manager.get_upstream_cortical_areas(&cortical_id_typed);
1980 let upstream_non_memory =
1981 manager.filter_non_memory_upstream_areas(&upstream_areas);
1982 let lifecycle_config = MemoryNeuronLifecycleConfig {
1983 initial_lifespan: mem_props.init_lifespan,
1984 lifespan_growth_rate: mem_props.lifespan_growth_rate,
1985 longterm_threshold: mem_props.longterm_threshold,
1986 max_reactivations: 1000,
1987 };
1988
1989 exec.register_memory_area(
1990 cortical_idx,
1991 cortical_id.to_string(),
1992 mem_props.temporal_depth,
1993 upstream_non_memory,
1994 Some(lifecycle_config),
1995 );
1996 } else {
1997 warn!(target: "feagi-services", "[GENOME-UPDATE] Failed to lock PlasticityExecutor for memory-area update");
1998 }
1999 }
2000 }
2001 }
2002 }
2003 }
2004
2005 info!(target: "feagi-services", "[FAST-UPDATE] Parameter update complete");
2006
2007 self.get_cortical_area_info(cortical_id).await
2009 }
2010
2011 fn apply_io_coding_update(
2015 &self,
2016 cortical_id: &CorticalID,
2017 cortical_id_str: &str,
2018 changes: &HashMap<String, Value>,
2019 ) -> ServiceResult<CorticalID> {
2020 let new_cortical_id_str = changes
2021 .get("new_cortical_id")
2022 .and_then(|v| v.as_str())
2023 .ok_or_else(|| {
2024 ServiceError::InvalidInput(
2025 "new_cortical_id is required when updating IO coding parameters".to_string(),
2026 )
2027 })?;
2028 let _new_cortical_id = CorticalID::try_from_base_64(new_cortical_id_str).map_err(|e| {
2029 ServiceError::InvalidInput(format!(
2030 "Invalid new_cortical_id '{}': {}",
2031 new_cortical_id_str, e
2032 ))
2033 })?;
2034
2035 let bytes = cortical_id.as_bytes();
2036 let is_input = bytes[0] == b'i';
2037 let is_output = bytes[0] == b'o';
2038 if !is_input && !is_output {
2039 return Err(ServiceError::InvalidInput(
2040 "IO coding updates only apply to IPU/OPU cortical areas".to_string(),
2041 ));
2042 }
2043
2044 let current_flag = cortical_id.extract_io_data_flag().map_err(|e| {
2045 ServiceError::InvalidInput(format!(
2046 "Unable to decode IO configuration from cortical ID '{}': {}",
2047 cortical_id_str, e
2048 ))
2049 })?;
2050
2051 let parse_frame = |raw: &str| -> Option<FrameChangeHandling> {
2052 let lower = raw.trim().to_ascii_lowercase();
2053 match lower.as_str() {
2054 "absolute" => Some(FrameChangeHandling::Absolute),
2055 "incremental" => Some(FrameChangeHandling::Incremental),
2056 _ => None,
2057 }
2058 };
2059 let parse_positioning = |raw: &str| -> Option<PercentageNeuronPositioning> {
2060 let lower = raw.trim().to_ascii_lowercase();
2061 match lower.as_str() {
2062 "linear" => Some(PercentageNeuronPositioning::Linear),
2063 "fractional" => Some(PercentageNeuronPositioning::Fractional),
2064 _ => None,
2065 }
2066 };
2067 let parse_signage = |raw: &str| -> Option<bool> {
2068 let lower = raw.trim().to_ascii_lowercase();
2069 if lower.contains("unsigned") {
2070 Some(false)
2071 } else if lower.contains("signed") {
2072 Some(true)
2073 } else {
2074 None
2075 }
2076 };
2077
2078 let requested_signage = changes.get("coding_signage").and_then(|v| v.as_str());
2079 let requested_behavior = changes.get("coding_behavior").and_then(|v| v.as_str());
2080 let requested_type = changes.get("coding_type").and_then(|v| v.as_str());
2081
2082 let new_flag = match current_flag {
2083 IOCorticalAreaConfigurationFlag::Percentage(frame, positioning) => {
2084 let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2085 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2086 let new_positioning = requested_type
2087 .and_then(parse_positioning)
2088 .unwrap_or(positioning);
2089 if signed {
2090 IOCorticalAreaConfigurationFlag::SignedPercentage(new_frame, new_positioning)
2091 } else {
2092 IOCorticalAreaConfigurationFlag::Percentage(new_frame, new_positioning)
2093 }
2094 }
2095 IOCorticalAreaConfigurationFlag::Percentage2D(frame, positioning) => {
2096 let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2097 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2098 let new_positioning = requested_type
2099 .and_then(parse_positioning)
2100 .unwrap_or(positioning);
2101 if signed {
2102 IOCorticalAreaConfigurationFlag::SignedPercentage2D(new_frame, new_positioning)
2103 } else {
2104 IOCorticalAreaConfigurationFlag::Percentage2D(new_frame, new_positioning)
2105 }
2106 }
2107 IOCorticalAreaConfigurationFlag::Percentage3D(frame, positioning) => {
2108 let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2109 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2110 let new_positioning = requested_type
2111 .and_then(parse_positioning)
2112 .unwrap_or(positioning);
2113 if signed {
2114 IOCorticalAreaConfigurationFlag::SignedPercentage3D(new_frame, new_positioning)
2115 } else {
2116 IOCorticalAreaConfigurationFlag::Percentage3D(new_frame, new_positioning)
2117 }
2118 }
2119 IOCorticalAreaConfigurationFlag::Percentage4D(frame, positioning) => {
2120 let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2121 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2122 let new_positioning = requested_type
2123 .and_then(parse_positioning)
2124 .unwrap_or(positioning);
2125 if signed {
2126 IOCorticalAreaConfigurationFlag::SignedPercentage4D(new_frame, new_positioning)
2127 } else {
2128 IOCorticalAreaConfigurationFlag::Percentage4D(new_frame, new_positioning)
2129 }
2130 }
2131 IOCorticalAreaConfigurationFlag::SignedPercentage(frame, positioning) => {
2132 let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2133 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2134 let new_positioning = requested_type
2135 .and_then(parse_positioning)
2136 .unwrap_or(positioning);
2137 if signed {
2138 IOCorticalAreaConfigurationFlag::SignedPercentage(new_frame, new_positioning)
2139 } else {
2140 IOCorticalAreaConfigurationFlag::Percentage(new_frame, new_positioning)
2141 }
2142 }
2143 IOCorticalAreaConfigurationFlag::SignedPercentage2D(frame, positioning) => {
2144 let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2145 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2146 let new_positioning = requested_type
2147 .and_then(parse_positioning)
2148 .unwrap_or(positioning);
2149 if signed {
2150 IOCorticalAreaConfigurationFlag::SignedPercentage2D(new_frame, new_positioning)
2151 } else {
2152 IOCorticalAreaConfigurationFlag::Percentage2D(new_frame, new_positioning)
2153 }
2154 }
2155 IOCorticalAreaConfigurationFlag::SignedPercentage3D(frame, positioning) => {
2156 let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2157 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2158 let new_positioning = requested_type
2159 .and_then(parse_positioning)
2160 .unwrap_or(positioning);
2161 if signed {
2162 IOCorticalAreaConfigurationFlag::SignedPercentage3D(new_frame, new_positioning)
2163 } else {
2164 IOCorticalAreaConfigurationFlag::Percentage3D(new_frame, new_positioning)
2165 }
2166 }
2167 IOCorticalAreaConfigurationFlag::SignedPercentage4D(frame, positioning) => {
2168 let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2169 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2170 let new_positioning = requested_type
2171 .and_then(parse_positioning)
2172 .unwrap_or(positioning);
2173 if signed {
2174 IOCorticalAreaConfigurationFlag::SignedPercentage4D(new_frame, new_positioning)
2175 } else {
2176 IOCorticalAreaConfigurationFlag::Percentage4D(new_frame, new_positioning)
2177 }
2178 }
2179 IOCorticalAreaConfigurationFlag::CartesianPlane(frame) => {
2180 if let Some(signage) = requested_signage {
2181 if !signage.trim().eq_ignore_ascii_case("not applicable") {
2182 return Err(ServiceError::InvalidInput(
2183 "coding_signage not supported for CartesianPlane".to_string(),
2184 ));
2185 }
2186 }
2187 if let Some(coding_type) = requested_type {
2188 if !coding_type.trim().eq_ignore_ascii_case("not applicable") {
2189 return Err(ServiceError::InvalidInput(
2190 "coding_type not supported for CartesianPlane".to_string(),
2191 ));
2192 }
2193 }
2194 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2195 IOCorticalAreaConfigurationFlag::CartesianPlane(new_frame)
2196 }
2197 IOCorticalAreaConfigurationFlag::Misc(frame) => {
2198 if let Some(signage) = requested_signage {
2199 if !signage.trim().eq_ignore_ascii_case("not applicable") {
2200 return Err(ServiceError::InvalidInput(
2201 "coding_signage not supported for Misc".to_string(),
2202 ));
2203 }
2204 }
2205 if let Some(coding_type) = requested_type {
2206 if !coding_type.trim().eq_ignore_ascii_case("not applicable") {
2207 return Err(ServiceError::InvalidInput(
2208 "coding_type not supported for Misc".to_string(),
2209 ));
2210 }
2211 }
2212 let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2213 IOCorticalAreaConfigurationFlag::Misc(new_frame)
2214 }
2215 IOCorticalAreaConfigurationFlag::Boolean => {
2216 if let Some(signage) = requested_signage {
2217 if !signage.trim().eq_ignore_ascii_case("boolean")
2218 && !signage.trim().eq_ignore_ascii_case("not applicable")
2219 {
2220 return Err(ServiceError::InvalidInput(
2221 "coding_signage not supported for Boolean".to_string(),
2222 ));
2223 }
2224 }
2225 if let Some(coding_type) = requested_type {
2226 if !coding_type.trim().eq_ignore_ascii_case("not applicable") {
2227 return Err(ServiceError::InvalidInput(
2228 "coding_type not supported for Boolean".to_string(),
2229 ));
2230 }
2231 }
2232 if let Some(behavior) = requested_behavior {
2233 if !behavior.trim().eq_ignore_ascii_case("not applicable") {
2234 return Err(ServiceError::InvalidInput(
2235 "coding_behavior not supported for Boolean".to_string(),
2236 ));
2237 }
2238 }
2239 IOCorticalAreaConfigurationFlag::Boolean
2240 }
2241 };
2242
2243 let unit_identifier = [bytes[1], bytes[2], bytes[3]];
2244 let cortical_subunit_index = CorticalSubUnitIndex::from(bytes[6]);
2245 let cortical_unit_index = CorticalUnitIndex::from(bytes[7]);
2246 let computed_id = new_flag.as_io_cortical_id(
2247 is_input,
2248 unit_identifier,
2249 cortical_unit_index,
2250 cortical_subunit_index,
2251 );
2252
2253 if computed_id.as_base_64() != new_cortical_id_str {
2254 return Err(ServiceError::InvalidInput(format!(
2255 "new_cortical_id '{}' does not match computed ID '{}'",
2256 new_cortical_id_str,
2257 computed_id.as_base_64()
2258 )));
2259 }
2260 info!(
2261 target: "feagi-services",
2262 "[IO-CODING] Remapping cortical ID {} -> {}",
2263 cortical_id.as_base_64(),
2264 computed_id.as_base_64()
2265 );
2266
2267 let new_cortical_type = if is_input {
2268 CorticalAreaType::BrainInput(new_flag)
2269 } else {
2270 CorticalAreaType::BrainOutput(new_flag)
2271 };
2272
2273 if let Some(genome) = self.current_genome.write().as_mut() {
2275 let mut area = genome.cortical_areas.remove(cortical_id).ok_or_else(|| {
2276 ServiceError::NotFound {
2277 resource: "CorticalArea".to_string(),
2278 id: cortical_id.as_base_64(),
2279 }
2280 })?;
2281 area.cortical_id = computed_id;
2282 area.cortical_type = new_cortical_type;
2283 genome.cortical_areas.insert(computed_id, area);
2284
2285 for region in genome.brain_regions.values_mut() {
2286 if region.cortical_areas.remove(cortical_id) {
2287 region.cortical_areas.insert(computed_id);
2288 }
2289 }
2290
2291 let old_id_str = cortical_id.as_base_64();
2292 let new_id_str = computed_id.as_base_64();
2293 for area in genome.cortical_areas.values_mut() {
2294 if let Some(mapping) = area
2295 .properties
2296 .get_mut("cortical_mapping_dst")
2297 .and_then(|v| v.as_object_mut())
2298 {
2299 if let Some(value) = mapping.remove(&old_id_str) {
2300 mapping.insert(new_id_str.clone(), value);
2301 }
2302 }
2303 }
2304 }
2305
2306 {
2308 let mut manager = self.connectome.write();
2309 manager.rename_cortical_area_id_with_options(
2310 cortical_id,
2311 computed_id,
2312 new_cortical_type,
2313 false,
2314 )?;
2315 }
2316
2317 self.refresh_burst_runner_cache();
2319
2320 Ok(computed_id)
2321 }
2322
2323 fn apply_unit_index_update(
2327 &self,
2328 cortical_id: &CorticalID,
2329 cortical_id_str: &str,
2330 changes: &HashMap<String, Value>,
2331 ) -> ServiceResult<CorticalID> {
2332 let new_group_id_value = changes.get("group_id").ok_or_else(|| {
2333 ServiceError::InvalidInput("group_id is required for unit index update".to_string())
2334 })?;
2335 let new_group_id = if let Some(value) = new_group_id_value.as_u64() {
2336 value
2337 .try_into()
2338 .map_err(|_| ServiceError::InvalidInput("group_id out of range".to_string()))?
2339 } else if let Some(value) = new_group_id_value.as_f64() {
2340 if value.fract() != 0.0 {
2341 return Err(ServiceError::InvalidInput(
2342 "group_id must be an integer".to_string(),
2343 ));
2344 }
2345 let as_u64 = value as u64;
2346 as_u64
2347 .try_into()
2348 .map_err(|_| ServiceError::InvalidInput("group_id out of range".to_string()))?
2349 } else if let Some(raw) = new_group_id_value.as_str() {
2350 raw.parse::<u8>().map_err(|_| {
2351 ServiceError::InvalidInput("group_id must be an integer".to_string())
2352 })?
2353 } else {
2354 return Err(ServiceError::InvalidInput(
2355 "group_id must be an integer".to_string(),
2356 ));
2357 };
2358
2359 let bytes = cortical_id.as_bytes();
2360 let is_input = bytes[0] == b'i';
2361 let is_output = bytes[0] == b'o';
2362 if !is_input && !is_output {
2363 return Err(ServiceError::InvalidInput(
2364 "Unit index updates only apply to IPU/OPU cortical areas".to_string(),
2365 ));
2366 }
2367
2368 let current_flag = cortical_id.extract_io_data_flag().map_err(|e| {
2369 ServiceError::InvalidInput(format!(
2370 "Unable to decode IO configuration from cortical ID '{}': {}",
2371 cortical_id_str, e
2372 ))
2373 })?;
2374 let unit_identifier = [bytes[1], bytes[2], bytes[3]];
2375 let cortical_subunit_index = CorticalSubUnitIndex::from(bytes[6]);
2376 let cortical_unit_index = CorticalUnitIndex::from(new_group_id);
2377 let computed_id = current_flag.as_io_cortical_id(
2378 is_input,
2379 unit_identifier,
2380 cortical_unit_index,
2381 cortical_subunit_index,
2382 );
2383
2384 let new_cortical_type = if is_input {
2385 CorticalAreaType::BrainInput(current_flag)
2386 } else {
2387 CorticalAreaType::BrainOutput(current_flag)
2388 };
2389
2390 if &computed_id == cortical_id {
2391 if let Some(genome) = self.current_genome.write().as_mut() {
2392 if let Some(area) = genome.cortical_areas.get_mut(cortical_id) {
2393 area.properties
2394 .insert("group_id".to_string(), serde_json::json!(new_group_id));
2395 }
2396 }
2397 let mut manager = self.connectome.write();
2398 if let Some(area) = manager.get_cortical_area_mut(cortical_id) {
2399 area.properties
2400 .insert("group_id".to_string(), serde_json::json!(new_group_id));
2401 }
2402 return Ok(*cortical_id);
2403 }
2404
2405 info!(
2406 target: "feagi-services",
2407 "[UNIT-INDEX] Remapping cortical ID {} -> {}",
2408 cortical_id.as_base_64(),
2409 computed_id.as_base_64()
2410 );
2411
2412 if let Some(genome) = self.current_genome.write().as_mut() {
2414 let mut area = genome.cortical_areas.remove(cortical_id).ok_or_else(|| {
2415 ServiceError::NotFound {
2416 resource: "CorticalArea".to_string(),
2417 id: cortical_id.as_base_64(),
2418 }
2419 })?;
2420 area.cortical_id = computed_id;
2421 area.cortical_type = new_cortical_type;
2422 area.properties
2423 .insert("group_id".to_string(), serde_json::json!(new_group_id));
2424 genome.cortical_areas.insert(computed_id, area);
2425
2426 for region in genome.brain_regions.values_mut() {
2427 if region.cortical_areas.remove(cortical_id) {
2428 region.cortical_areas.insert(computed_id);
2429 }
2430 }
2431
2432 let old_id_str = cortical_id.as_base_64();
2433 let new_id_str = computed_id.as_base_64();
2434 for area in genome.cortical_areas.values_mut() {
2435 if let Some(mapping) = area
2436 .properties
2437 .get_mut("cortical_mapping_dst")
2438 .and_then(|v| v.as_object_mut())
2439 {
2440 if let Some(value) = mapping.remove(&old_id_str) {
2441 mapping.insert(new_id_str.clone(), value);
2442 }
2443 }
2444 }
2445 }
2446
2447 {
2449 let mut manager = self.connectome.write();
2450 manager.rename_cortical_area_id_with_options(
2451 cortical_id,
2452 computed_id,
2453 new_cortical_type,
2454 false,
2455 )?;
2456 if let Some(area) = manager.get_cortical_area_mut(&computed_id) {
2457 area.properties
2458 .insert("group_id".to_string(), serde_json::json!(new_group_id));
2459 }
2460 }
2461
2462 self.refresh_burst_runner_cache();
2464
2465 Ok(computed_id)
2466 }
2467
2468 fn regenerate_mappings_for_area(&self, cortical_id: &CorticalID) -> ServiceResult<()> {
2470 let mut manager = self.connectome.write();
2471 let cortical_id_str = cortical_id.as_base_64();
2472
2473 let outgoing_targets = {
2474 let Some(area) = manager.get_cortical_area(cortical_id) else {
2475 return Err(ServiceError::NotFound {
2476 resource: "CorticalArea".to_string(),
2477 id: cortical_id_str.clone(),
2478 });
2479 };
2480 let mut targets = Vec::new();
2481 if let Some(mapping) = area
2482 .properties
2483 .get("cortical_mapping_dst")
2484 .and_then(|v| v.as_object())
2485 {
2486 for key in mapping.keys() {
2487 match CorticalID::try_from_base_64(key) {
2488 Ok(dst_id) => targets.push(dst_id),
2489 Err(err) => warn!(
2490 target: "feagi-services",
2491 "Invalid cortical_mapping_dst ID '{}': {}",
2492 key,
2493 err
2494 ),
2495 }
2496 }
2497 }
2498 targets
2499 };
2500
2501 let incoming_sources = {
2502 let mut sources = Vec::new();
2503 for src_id in manager.get_all_cortical_ids() {
2504 if src_id == *cortical_id {
2505 continue;
2506 }
2507 let Some(src_area) = manager.get_cortical_area(&src_id) else {
2508 continue;
2509 };
2510 if let Some(mapping) = src_area
2511 .properties
2512 .get("cortical_mapping_dst")
2513 .and_then(|v| v.as_object())
2514 {
2515 if mapping.contains_key(&cortical_id_str) {
2516 sources.push(src_id);
2517 }
2518 }
2519 }
2520 sources
2521 };
2522
2523 for dst_id in outgoing_targets {
2524 if dst_id == *cortical_id {
2525 continue;
2526 }
2527 manager
2528 .regenerate_synapses_for_mapping(cortical_id, &dst_id)
2529 .map_err(|e| {
2530 ServiceError::Backend(format!(
2531 "Failed to regenerate outgoing synapses {} -> {}: {}",
2532 cortical_id.as_base_64(),
2533 dst_id.as_base_64(),
2534 e
2535 ))
2536 })?;
2537 }
2538
2539 for src_id in incoming_sources {
2540 manager
2541 .regenerate_synapses_for_mapping(&src_id, cortical_id)
2542 .map_err(|e| {
2543 ServiceError::Backend(format!(
2544 "Failed to regenerate incoming synapses {} -> {}: {}",
2545 src_id.as_base_64(),
2546 cortical_id.as_base_64(),
2547 e
2548 ))
2549 })?;
2550 }
2551
2552 Ok(())
2553 }
2554
2555 async fn update_metadata_only(
2559 &self,
2560 cortical_id: &str,
2561 changes: HashMap<String, Value>,
2562 ) -> ServiceResult<CorticalAreaInfo> {
2563 info!(target: "feagi-services", "[METADATA-UPDATE] Metadata-only update for {}", cortical_id);
2564 let needs_burst_cache_refresh = changes.contains_key("visualization_voxel_granularity");
2565 let mut properties_changed = false;
2566 let mut geometry_changed = false;
2567 for key in changes.keys() {
2568 match key.as_str() {
2569 "coordinates_3d" | "coordinate_3d" | "coordinates" | "position"
2570 | "coordinate_2d" | "coordinates_2d" => {
2571 geometry_changed = true;
2572 }
2573 "cortical_name" | "name" | "visible" | "visualization_voxel_granularity" => {
2574 properties_changed = true;
2575 }
2576 _ => {
2577 properties_changed = true;
2578 }
2579 }
2580 }
2581
2582 let cortical_id_typed =
2584 feagi_evolutionary::string_to_cortical_id(cortical_id).map_err(|e| {
2585 ServiceError::InvalidInput(format!("Invalid cortical ID '{}': {}", cortical_id, e))
2586 })?;
2587 let mut effective_cortical_id = cortical_id_typed;
2588 let mut effective_cortical_id_str = cortical_id.to_string();
2589
2590 let has_coding_update = changes.contains_key("coding_signage")
2591 || changes.contains_key("coding_behavior")
2592 || changes.contains_key("coding_type")
2593 || changes.contains_key("new_cortical_id");
2594 if has_coding_update {
2595 let new_id = self.apply_io_coding_update(&cortical_id_typed, cortical_id, &changes)?;
2596 effective_cortical_id = new_id;
2597 effective_cortical_id_str = new_id.as_base_64();
2598 }
2599
2600 if let Some(genome) = self.current_genome.write().as_mut() {
2602 if let Some(area) = genome.cortical_areas.get_mut(&effective_cortical_id) {
2603 for (key, value) in &changes {
2604 match key.as_str() {
2605 "cortical_name" | "name" => {
2606 if let Some(name) = value.as_str() {
2607 area.name = name.to_string();
2608 }
2609 }
2610 "coordinates_3d" | "coordinate_3d" | "coordinates" | "position" => {
2611 if let Some(arr) = value.as_array() {
2613 if arr.len() >= 3 {
2615 let x = arr[0].as_i64().unwrap_or(0) as i32;
2616 let y = arr[1].as_i64().unwrap_or(0) as i32;
2617 let z = arr[2].as_i64().unwrap_or(0) as i32;
2618 area.position = (x, y, z).into();
2619 info!(target: "feagi-services", "[GENOME-UPDATE] Updated position (array format): ({}, {}, {})", x, y, z);
2620 }
2621 } else if let Some(obj) = value.as_object() {
2622 let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2624 let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2625 let z = obj.get("z").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2626 area.position = (x, y, z).into();
2627 info!(target: "feagi-services", "[GENOME-UPDATE] Updated position (object format): ({}, {}, {})", x, y, z);
2628 }
2629 }
2630 "coordinate_2d" | "coordinates_2d" => {
2631 if let Some(arr) = value.as_array() {
2632 if arr.len() >= 2 {
2633 let x = arr[0].as_i64().unwrap_or(0) as i32;
2634 let y = arr[1].as_i64().unwrap_or(0) as i32;
2635 area.properties.insert(
2636 "coordinate_2d".to_string(),
2637 serde_json::json!([x, y]),
2638 );
2639 info!(target: "feagi-services", "[GENOME-UPDATE] Updated coordinate_2d: ({}, {})", x, y);
2640 } else {
2641 warn!(target: "feagi-services", "[GENOME-UPDATE] coordinate_2d array must have 2 elements, got {}", arr.len());
2642 }
2643 } else if let Some(obj) = value.as_object() {
2644 let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2645 let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2646 area.properties
2647 .insert("coordinate_2d".to_string(), serde_json::json!([x, y]));
2648 info!(target: "feagi-services", "[GENOME-UPDATE] Updated coordinate_2d (object format): ({}, {})", x, y);
2649 } else {
2650 warn!(target: "feagi-services", "[GENOME-UPDATE] coordinate_2d must be array or object, got: {:?}", value);
2651 }
2652 }
2653 "visualization_voxel_granularity" => {
2654 info!(target: "feagi-services", "[GENOME-UPDATE] Received visualization_voxel_granularity update: {:?}", value);
2656 if let Some(arr) = value.as_array() {
2657 if arr.len() == 3 {
2658 let x_opt = arr[0]
2660 .as_u64()
2661 .or_else(|| arr[0].as_f64().map(|f| f as u64));
2662 let y_opt = arr[1]
2663 .as_u64()
2664 .or_else(|| arr[1].as_f64().map(|f| f as u64));
2665 let z_opt = arr[2]
2666 .as_u64()
2667 .or_else(|| arr[2].as_f64().map(|f| f as u64));
2668
2669 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
2670 let x_u32 = x as u32;
2671 let y_u32 = y as u32;
2672 let z_u32 = z as u32;
2673
2674 if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
2676 area.properties
2678 .remove("visualization_voxel_granularity");
2679 info!(target: "feagi-services", "[GENOME-UPDATE] Removed visualization_voxel_granularity override (returned to default 1x1x1)");
2680 } else {
2681 area.properties.insert(
2683 "visualization_voxel_granularity".to_string(),
2684 serde_json::json!([x_u32, y_u32, z_u32]),
2685 );
2686 info!(target: "feagi-services", "[GENOME-UPDATE] Updated visualization_voxel_granularity: ({}, {}, {})", x_u32, y_u32, z_u32);
2687 }
2688 } else {
2689 warn!(target: "feagi-services", "[GENOME-UPDATE] Failed to parse visualization_voxel_granularity array values as integers");
2690 }
2691 } else {
2692 warn!(target: "feagi-services", "[GENOME-UPDATE] visualization_voxel_granularity array must have 3 elements, got {}", arr.len());
2693 }
2694 } else {
2695 warn!(target: "feagi-services", "[GENOME-UPDATE] visualization_voxel_granularity must be an array, got: {:?}", value);
2696 }
2697 }
2698 _ => {}
2699 }
2700 }
2701 }
2702 }
2703
2704 {
2706 let mut manager = self.connectome.write();
2707 let area = manager
2708 .get_cortical_area_mut(&effective_cortical_id)
2709 .ok_or_else(|| ServiceError::NotFound {
2710 resource: "CorticalArea".to_string(),
2711 id: effective_cortical_id_str.clone(),
2712 })?;
2713
2714 for (key, value) in &changes {
2716 match key.as_str() {
2717 "cortical_name" | "name" => {
2718 if let Some(name) = value.as_str() {
2719 area.name = name.to_string();
2720 }
2721 }
2722 "visible" => {
2723 if let Some(v) = value.as_bool() {
2724 area.add_property_mut("visible".to_string(), serde_json::json!(v));
2725 }
2726 }
2727 "coordinates_3d" | "coordinate_3d" | "coordinates" | "position" => {
2728 if let Some(arr) = value.as_array() {
2730 if arr.len() >= 3 {
2732 let x = arr[0].as_i64().unwrap_or(0) as i32;
2733 let y = arr[1].as_i64().unwrap_or(0) as i32;
2734 let z = arr[2].as_i64().unwrap_or(0) as i32;
2735 area.position = (x, y, z).into();
2736 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated position (array format): ({}, {}, {})", x, y, z);
2737 }
2738 } else if let Some(obj) = value.as_object() {
2739 let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2741 let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2742 let z = obj.get("z").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2743 area.position = (x, y, z).into();
2744 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated position (object format): ({}, {}, {})", x, y, z);
2745 }
2746 }
2747 "coordinate_2d" | "coordinates_2d" => {
2748 if let Some(arr) = value.as_array() {
2749 if arr.len() >= 2 {
2750 let x = arr[0].as_i64().unwrap_or(0) as i32;
2751 let y = arr[1].as_i64().unwrap_or(0) as i32;
2752 area.properties
2753 .insert("coordinate_2d".to_string(), serde_json::json!([x, y]));
2754 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated coordinate_2d: ({}, {})", x, y);
2755 } else {
2756 warn!(target: "feagi-services", "[CONNECTOME-UPDATE] coordinate_2d array must have 2 elements, got {}", arr.len());
2757 }
2758 } else if let Some(obj) = value.as_object() {
2759 let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2760 let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2761 area.properties
2762 .insert("coordinate_2d".to_string(), serde_json::json!([x, y]));
2763 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated coordinate_2d (object format): ({}, {})", x, y);
2764 } else {
2765 warn!(target: "feagi-services", "[CONNECTOME-UPDATE] coordinate_2d must be array or object, got: {:?}", value);
2766 }
2767 }
2768 "visualization_voxel_granularity" => {
2769 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Received visualization_voxel_granularity update: {:?}", value);
2771 if let Some(arr) = value.as_array() {
2772 if arr.len() == 3 {
2773 let x_opt = arr[0]
2775 .as_u64()
2776 .or_else(|| arr[0].as_f64().map(|f| f as u64));
2777 let y_opt = arr[1]
2778 .as_u64()
2779 .or_else(|| arr[1].as_f64().map(|f| f as u64));
2780 let z_opt = arr[2]
2781 .as_u64()
2782 .or_else(|| arr[2].as_f64().map(|f| f as u64));
2783
2784 if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
2785 let x_u32 = x as u32;
2786 let y_u32 = y as u32;
2787 let z_u32 = z as u32;
2788
2789 if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
2791 area.properties.remove("visualization_voxel_granularity");
2793 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Removed visualization_voxel_granularity override (returned to default 1x1x1)");
2794 } else {
2795 area.properties.insert(
2797 "visualization_voxel_granularity".to_string(),
2798 serde_json::json!([x_u32, y_u32, z_u32]),
2799 );
2800 info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated visualization_voxel_granularity: ({}, {}, {})", x_u32, y_u32, z_u32);
2801 }
2802 } else {
2803 warn!(target: "feagi-services", "[CONNECTOME-UPDATE] Failed to parse visualization_voxel_granularity array values as integers");
2804 }
2805 } else {
2806 warn!(target: "feagi-services", "[CONNECTOME-UPDATE] visualization_voxel_granularity array must have 3 elements, got {}", arr.len());
2807 }
2808 } else {
2809 warn!(target: "feagi-services", "[CONNECTOME-UPDATE] visualization_voxel_granularity must be an array, got: {:?}", value);
2810 }
2811 }
2812 _ => {}
2813 }
2814 }
2815 manager.refresh_cortical_area_hashes(properties_changed, geometry_changed);
2816 }
2817
2818 if needs_burst_cache_refresh {
2820 self.refresh_burst_runner_cache();
2821 }
2822
2823 info!(target: "feagi-services", "[METADATA-UPDATE] Metadata update complete");
2824
2825 self.get_cortical_area_info(&effective_cortical_id_str)
2827 .await
2828 }
2829
2830 async fn update_with_localized_rebuild(
2840 &self,
2841 cortical_id: &str,
2842 changes: HashMap<String, Value>,
2843 ) -> ServiceResult<CorticalAreaInfo> {
2844 info!(target: "feagi-services", "[STRUCTURAL-REBUILD] Localized rebuild for {}", cortical_id);
2845
2846 let _cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
2848 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
2849
2850 if changes.contains_key("group_id") && changes.len() == 1 {
2851 let new_id =
2852 self.apply_unit_index_update(&_cortical_id_typed, cortical_id, &changes)?;
2853 self.regenerate_mappings_for_area(&new_id)?;
2854 return self.get_cortical_area_info(&new_id.as_base_64()).await;
2855 }
2856
2857 let connectome = Arc::clone(&self.connectome);
2859 let genome_store = Arc::clone(&self.current_genome);
2860 let cortical_id_owned = cortical_id.to_string();
2861 let burst_runner_clone = self.burst_runner.clone();
2862
2863 tokio::task::spawn_blocking(move || {
2864 Self::do_localized_rebuild(
2865 &cortical_id_owned,
2866 changes,
2867 connectome,
2868 genome_store,
2869 burst_runner_clone,
2870 )
2871 })
2872 .await
2873 .map_err(|e| ServiceError::Backend(format!("Rebuild task panicked: {}", e)))?
2874 }
2875
2876 fn do_localized_rebuild(
2878 cortical_id: &str,
2879 changes: HashMap<String, Value>,
2880 connectome: Arc<RwLock<ConnectomeManager>>,
2881 genome_store: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
2882 burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
2883 ) -> ServiceResult<CorticalAreaInfo> {
2884 info!(
2885 "[STRUCTURAL-REBUILD] Starting localized rebuild for {}",
2886 cortical_id
2887 );
2888
2889 let cortical_id_typed =
2891 feagi_evolutionary::string_to_cortical_id(cortical_id).map_err(|e| {
2892 ServiceError::InvalidInput(format!("Invalid cortical ID '{}': {}", cortical_id, e))
2893 })?;
2894
2895 let (old_dimensions, old_density, new_dimensions, new_density) = {
2897 let mut genome_guard = genome_store.write();
2898 let genome = genome_guard
2899 .as_mut()
2900 .ok_or_else(|| ServiceError::Backend("No genome loaded".to_string()))?;
2901
2902 let area = genome
2903 .cortical_areas
2904 .get_mut(&cortical_id_typed)
2905 .ok_or_else(|| ServiceError::NotFound {
2906 resource: "CorticalArea".to_string(),
2907 id: cortical_id.to_string(),
2908 })?;
2909
2910 let old_dims = area.dimensions;
2911 let old_dens = area.neurons_per_voxel();
2912
2913 let is_per_device = changes.contains_key("cortical_dimensions_per_device");
2916
2917 if let Some(dims) = changes
2918 .get("dimensions")
2919 .or_else(|| changes.get("cortical_dimensions"))
2920 .or_else(|| changes.get("cortical_dimensions_per_device"))
2921 {
2922 let (width, height, depth) = if let Some(arr) = dims.as_array() {
2923 if arr.len() >= 3 {
2925 (
2926 arr[0].as_u64().unwrap_or(1) as usize,
2927 arr[1].as_u64().unwrap_or(1) as usize,
2928 arr[2].as_u64().unwrap_or(1) as usize,
2929 )
2930 } else {
2931 (1, 1, 1)
2932 }
2933 } else if let Some(obj) = dims.as_object() {
2934 (
2936 obj.get("x").and_then(|v| v.as_u64()).unwrap_or(1) as usize,
2937 obj.get("y").and_then(|v| v.as_u64()).unwrap_or(1) as usize,
2938 obj.get("z").and_then(|v| v.as_u64()).unwrap_or(1) as usize,
2939 )
2940 } else {
2941 (1, 1, 1)
2942 };
2943
2944 let final_depth = if is_per_device {
2946 let dev_count = changes
2948 .get("dev_count")
2949 .and_then(|v| v.as_u64())
2950 .or_else(|| area.properties.get("dev_count").and_then(|v| v.as_u64()))
2951 .unwrap_or(1) as usize;
2952
2953 info!("[STRUCTURAL-REBUILD] Per-device dimensions: [{}, {}, {}] × dev_count={} → total depth={}",
2954 width, height, depth, dev_count, depth * dev_count);
2955
2956 depth * dev_count
2957 } else {
2958 depth
2959 };
2960
2961 area.dimensions =
2962 CorticalAreaDimensions::new(width as u32, height as u32, final_depth as u32)?;
2963 }
2964
2965 for density_param in [
2968 "neurons_per_voxel",
2969 "per_voxel_neuron_cnt",
2970 "neuron_density",
2971 ] {
2972 if let Some(density) = changes.get(density_param).and_then(|v| v.as_u64()) {
2973 area.add_property_mut(
2974 "neurons_per_voxel".to_string(),
2975 serde_json::json!(density as u32),
2976 );
2977 break;
2978 }
2979 }
2980
2981 if let Some(dev_count) = changes.get("dev_count") {
2983 area.properties
2984 .insert("dev_count".to_string(), dev_count.clone());
2985 }
2986 if let Some(per_device_dims) = changes.get("cortical_dimensions_per_device") {
2987 area.properties.insert(
2988 "cortical_dimensions_per_device".to_string(),
2989 per_device_dims.clone(),
2990 );
2991 }
2992
2993 if let Some(value) = changes.get("neuron_fire_threshold_increment") {
2997 if let Some(arr) = value.as_array() {
2998 if arr.len() >= 3 {
3000 let x = arr[0].as_f64().unwrap_or(0.0) as f32;
3001 let y = arr[1].as_f64().unwrap_or(0.0) as f32;
3002 let z = arr[2].as_f64().unwrap_or(0.0) as f32;
3003
3004 area.properties.insert(
3005 "firing_threshold_increment_x".to_string(),
3006 serde_json::json!(x),
3007 );
3008 area.properties.insert(
3009 "firing_threshold_increment_y".to_string(),
3010 serde_json::json!(y),
3011 );
3012 area.properties.insert(
3013 "firing_threshold_increment_z".to_string(),
3014 serde_json::json!(z),
3015 );
3016
3017 info!(
3018 "[STRUCTURAL-REBUILD] Updated firing_threshold_increment to [{}, {}, {}] for area {}",
3019 x, y, z, cortical_id
3020 );
3021 }
3022 }
3023 }
3024
3025 for increment_param in [
3027 "firing_threshold_increment_x",
3028 "firing_threshold_increment_y",
3029 "firing_threshold_increment_z",
3030 ] {
3031 if let Some(value) = changes.get(increment_param) {
3032 area.properties
3033 .insert(increment_param.to_string(), value.clone());
3034 info!(
3035 "[STRUCTURAL-REBUILD] Updated {} to {} for area {}",
3036 increment_param, value, cortical_id
3037 );
3038 }
3039 }
3040
3041 for param in ["leak_variability", "neuron_leak_variability"] {
3043 if let Some(value) = changes.get(param) {
3044 area.properties
3045 .insert("leak_variability".to_string(), value.clone());
3046 info!(
3047 "[STRUCTURAL-REBUILD] Updated leak_variability to {} for area {}",
3048 value, cortical_id
3049 );
3050 break;
3051 }
3052 }
3053
3054 (
3055 old_dims,
3056 old_dens,
3057 area.dimensions,
3058 area.neurons_per_voxel(),
3059 )
3060 };
3061
3062 let total_voxels = new_dimensions.width as usize
3063 * new_dimensions.height as usize
3064 * new_dimensions.depth as usize;
3065 let estimated_neurons = total_voxels * new_density as usize;
3066
3067 info!(
3068 "[STRUCTURAL-REBUILD] Dimension: {:?} -> {:?}",
3069 old_dimensions, new_dimensions
3070 );
3071 info!(
3072 "[STRUCTURAL-REBUILD] Density: {} -> {} neurons/voxel",
3073 old_density, new_density
3074 );
3075
3076 if estimated_neurons > 1_000_000 {
3077 warn!(
3078 "[STRUCTURAL-REBUILD] ⚠️ Large area resize: {} neurons estimated. This may take significant time and memory.",
3079 estimated_neurons
3080 );
3081 }
3082
3083 let neurons_to_delete = {
3085 let manager = connectome.read();
3086 manager.get_neurons_in_area(&cortical_id_typed)
3087 };
3088
3089 let deleted_count = if !neurons_to_delete.is_empty() {
3090 info!(
3091 "[STRUCTURAL-REBUILD] Deleting {} existing neurons",
3092 neurons_to_delete.len()
3093 );
3094 let mut manager = connectome.write();
3095 manager
3096 .delete_neurons_batch(neurons_to_delete)
3097 .map_err(|e| ServiceError::Backend(format!("Failed to delete neurons: {}", e)))?
3098 } else {
3099 0
3100 };
3101
3102 info!("[STRUCTURAL-REBUILD] Deleted {} neurons", deleted_count);
3103
3104 {
3106 let mut manager = connectome.write();
3107 manager
3108 .resize_cortical_area(&cortical_id_typed, new_dimensions)
3109 .map_err(|e| ServiceError::Backend(format!("Failed to resize area: {}", e)))?;
3110
3111 if let Some(area) = manager.get_cortical_area_mut(&cortical_id_typed) {
3113 area.add_property_mut(
3114 "neurons_per_voxel".to_string(),
3115 serde_json::json!(new_density),
3116 );
3117
3118 if let Some(dev_count) = changes.get("dev_count") {
3120 area.properties
3121 .insert("dev_count".to_string(), dev_count.clone());
3122 }
3123 if let Some(per_device_dims) = changes.get("cortical_dimensions_per_device") {
3124 area.properties.insert(
3125 "cortical_dimensions_per_device".to_string(),
3126 per_device_dims.clone(),
3127 );
3128 }
3129
3130 if let Some(value) = changes.get("neuron_fire_threshold_increment") {
3134 if let Some(arr) = value.as_array() {
3135 if arr.len() >= 3 {
3136 let x = arr[0].as_f64().unwrap_or(0.0) as f32;
3137 let y = arr[1].as_f64().unwrap_or(0.0) as f32;
3138 let z = arr[2].as_f64().unwrap_or(0.0) as f32;
3139
3140 area.properties.insert(
3141 "firing_threshold_increment_x".to_string(),
3142 serde_json::json!(x),
3143 );
3144 area.properties.insert(
3145 "firing_threshold_increment_y".to_string(),
3146 serde_json::json!(y),
3147 );
3148 area.properties.insert(
3149 "firing_threshold_increment_z".to_string(),
3150 serde_json::json!(z),
3151 );
3152 }
3153 }
3154 }
3155
3156 for increment_param in [
3158 "firing_threshold_increment_x",
3159 "firing_threshold_increment_y",
3160 "firing_threshold_increment_z",
3161 ] {
3162 if let Some(value) = changes.get(increment_param) {
3163 area.properties
3164 .insert(increment_param.to_string(), value.clone());
3165 }
3166 }
3167
3168 for param in ["leak_variability", "neuron_leak_variability"] {
3170 if let Some(value) = changes.get(param) {
3171 area.properties
3172 .insert("leak_variability".to_string(), value.clone());
3173 break;
3174 }
3175 }
3176 }
3177 manager.refresh_cortical_area_hashes(true, true);
3178 }
3179
3180 let (cortical_idx, area_data) = {
3184 let manager = connectome.read();
3185 let area = manager
3186 .get_cortical_area(&cortical_id_typed)
3187 .ok_or_else(|| ServiceError::NotFound {
3188 resource: "CorticalArea".to_string(),
3189 id: cortical_id.to_string(),
3190 })?;
3191 let cortical_idx = manager
3192 .get_cortical_idx(&cortical_id_typed)
3193 .ok_or_else(|| ServiceError::Backend("Cortical index not found".to_string()))?;
3194
3195 use feagi_brain_development::models::CorticalAreaExt;
3197 (
3198 cortical_idx,
3199 (
3200 area.dimensions,
3201 area.neurons_per_voxel(),
3202 area.firing_threshold(),
3203 area.firing_threshold_increment_x(),
3204 area.firing_threshold_increment_y(),
3205 area.firing_threshold_increment_z(),
3206 area.firing_threshold_limit(),
3207 area.leak_coefficient(),
3208 area.neuron_excitability(),
3209 area.refractory_period(),
3210 area.consecutive_fire_count() as u16,
3211 area.snooze_period(),
3212 area.mp_charge_accumulation(),
3213 ),
3214 )
3215 };
3216
3217 let npu_arc_for_creation = {
3219 let manager = connectome.read();
3220 manager
3221 .get_npu()
3222 .ok_or_else(|| ServiceError::Backend("NPU not connected".to_string()))?
3223 .clone()
3224 };
3225
3226 let total_neurons =
3231 area_data.0.width * area_data.0.height * area_data.0.depth * area_data.1;
3232
3233 if total_neurons > 1_000_000 {
3234 info!(
3235 "[STRUCTURAL-REBUILD] Creating large area ({} neurons) - NPU lock held until creation completes",
3236 total_neurons
3237 );
3238 }
3239
3240 let creation_start = std::time::Instant::now();
3241
3242 let neurons_created = if total_neurons > 1_000_000 {
3245 let mut total_created = 0u32;
3248 let depth = area_data.0.depth;
3249 let width = area_data.0.width;
3250 let height = area_data.0.height;
3251 let neurons_per_voxel = area_data.1;
3252 let neurons_per_layer = (width * height * neurons_per_voxel) as usize;
3253 const BATCH_SIZE: usize = 10_000; let needs_inner_batching = neurons_per_layer > 50_000; info!(
3259 "[STRUCTURAL-REBUILD] Batching neuron creation: {} layers × {} neurons/layer = {} total{}",
3260 depth, neurons_per_layer, total_neurons,
3261 if needs_inner_batching { " (with inner-layer batching)" } else { "" }
3262 );
3263
3264 for z_layer in 0..depth {
3265 if needs_inner_batching {
3266 let mut layer_created = 0u32;
3268 let neurons_per_row = (width * neurons_per_voxel) as usize;
3269 let rows_per_batch = (BATCH_SIZE / neurons_per_row).max(1);
3270 let total_row_batches = (height as usize).div_ceil(rows_per_batch);
3271
3272 if z_layer == 0 {
3273 info!(
3274 "[STRUCTURAL-REBUILD] Inner-layer batching: {} rows/layer, {} rows/batch, ~{} batches/layer",
3275 height, rows_per_batch, total_row_batches
3276 );
3277 }
3278
3279 for (batch_idx, row_start) in (0..height).step_by(rows_per_batch).enumerate() {
3280 let row_end = (row_start + rows_per_batch as u32).min(height);
3281 let rows_in_batch = row_end - row_start;
3282 let neurons_in_batch = (rows_in_batch * width * neurons_per_voxel) as usize;
3283
3284 let batch_start = std::time::Instant::now();
3285
3286 let batch_created = {
3288 let mut npu_lock = npu_arc_for_creation.lock().map_err(|e| {
3289 ServiceError::Backend(format!("Failed to lock NPU: {}", e))
3290 })?;
3291
3292 npu_lock
3294 .create_cortical_area_neurons_with_offsets(
3295 cortical_idx,
3296 width,
3297 rows_in_batch,
3298 1, neurons_per_voxel,
3300 area_data.2, area_data.3, area_data.4, area_data.5, area_data.6, area_data.7, 0.0, 0, area_data.9, area_data.8, area_data.10, area_data.11, area_data.12, row_start, z_layer, )
3316 .map_err(|e| {
3317 ServiceError::Backend(format!(
3318 "NPU neuron creation failed for layer {} rows {}-{}: {}",
3319 z_layer, row_start, row_end, e
3320 ))
3321 })?
3322 };
3323
3324 let batch_duration = batch_start.elapsed();
3325 layer_created += batch_created;
3326
3327 if batch_duration.as_millis() > 100
3329 && (batch_idx == 0 || batch_idx == total_row_batches - 1)
3330 {
3331 tracing::debug!(
3332 "[STRUCTURAL-REBUILD] Layer {} batch {}/{}: {} neurons in {:.1}ms (rows {}-{})",
3333 z_layer, batch_idx + 1, total_row_batches, neurons_in_batch,
3334 batch_duration.as_millis(), row_start, row_end
3335 );
3336 }
3337 }
3338
3339 total_created += layer_created;
3340 } else {
3341 let layer_created = {
3343 let mut npu_lock = npu_arc_for_creation.lock().map_err(|e| {
3344 ServiceError::Backend(format!("Failed to lock NPU: {}", e))
3345 })?;
3346
3347 npu_lock
3349 .create_cortical_area_neurons_with_z_offset(
3350 cortical_idx,
3351 width,
3352 height,
3353 1, neurons_per_voxel,
3355 area_data.2, area_data.3, area_data.4, area_data.5, area_data.6, area_data.7, 0.0, 0, area_data.9, area_data.8, area_data.10, area_data.11, area_data.12, z_layer, )
3370 .map_err(|e| {
3371 ServiceError::Backend(format!(
3372 "NPU neuron creation failed for layer {}: {}",
3373 z_layer, e
3374 ))
3375 })?
3376 };
3377
3378 total_created += layer_created;
3379 }
3380
3381 if (z_layer + 1) % 5 == 0 || z_layer == depth - 1 {
3383 let progress = ((z_layer + 1) as f32 / depth as f32) * 100.0;
3384 info!(
3385 "[STRUCTURAL-REBUILD] Progress: {}/{} layers ({}%) - {} neurons created",
3386 z_layer + 1,
3387 depth,
3388 progress as u32,
3389 total_created
3390 );
3391 }
3392 }
3393
3394 total_created
3395 } else {
3396 let mut npu_lock = npu_arc_for_creation
3398 .lock()
3399 .map_err(|e| ServiceError::Backend(format!("Failed to lock NPU: {}", e)))?;
3400
3401 npu_lock
3402 .create_cortical_area_neurons(
3403 cortical_idx,
3404 area_data.0.width,
3405 area_data.0.height,
3406 area_data.0.depth,
3407 area_data.1,
3408 area_data.2,
3409 area_data.3,
3410 area_data.4,
3411 area_data.5,
3412 area_data.6,
3413 area_data.7,
3414 0.0,
3415 0,
3416 area_data.9,
3417 area_data.8,
3418 area_data.10,
3419 area_data.11,
3420 area_data.12,
3421 )
3422 .map_err(|e| ServiceError::Backend(format!("NPU neuron creation failed: {}", e)))?
3423 };
3424
3425 let creation_duration = creation_start.elapsed();
3426 info!(
3427 "[STRUCTURAL-REBUILD] Created {} neurons in {:.2}s (NPU lock held until completion)",
3428 neurons_created,
3429 creation_duration.as_secs_f64()
3430 );
3431
3432 if creation_duration.as_secs() > 1 {
3433 warn!(
3434 "[STRUCTURAL-REBUILD] ⚠️ Long creation time: {:.2}s - burst loop was blocked during this period",
3435 creation_duration.as_secs_f64()
3436 );
3437 }
3438
3439 let outgoing_targets = {
3441 let genome_guard = genome_store.read();
3442 let genome = genome_guard
3443 .as_ref()
3444 .ok_or_else(|| ServiceError::Backend("No genome loaded".to_string()))?;
3445 let area = genome
3446 .cortical_areas
3447 .get(&cortical_id_typed)
3448 .ok_or_else(|| ServiceError::NotFound {
3449 resource: "CorticalArea".to_string(),
3450 id: cortical_id.to_string(),
3451 })?;
3452 let mut targets = Vec::new();
3453 if let Some(mapping) = area
3454 .properties
3455 .get("cortical_mapping_dst")
3456 .and_then(|v| v.as_object())
3457 {
3458 for key in mapping.keys() {
3459 match CorticalID::try_from_base_64(key) {
3460 Ok(dst_id) => targets.push(dst_id),
3461 Err(err) => warn!(
3462 target: "feagi-services",
3463 "Invalid cortical_mapping_dst ID '{}': {}",
3464 key,
3465 err
3466 ),
3467 }
3468 }
3469 }
3470 targets
3471 };
3472 let outgoing_synapses = {
3473 let mut manager = connectome.write();
3474 let mut total = 0u32;
3475 for dst_id in outgoing_targets {
3476 if dst_id == cortical_id_typed {
3477 continue;
3478 }
3479 let count = manager
3480 .regenerate_synapses_for_mapping(&cortical_id_typed, &dst_id)
3481 .map_err(|e| {
3482 ServiceError::Backend(format!(
3483 "Failed to rebuild outgoing synapses {} -> {}: {}",
3484 cortical_id,
3485 dst_id.as_base_64(),
3486 e
3487 ))
3488 })?;
3489 total = total.saturating_add(count as u32);
3490 }
3491 total
3492 };
3493
3494 info!(
3495 "[STRUCTURAL-REBUILD] Rebuilt {} outgoing synapses",
3496 outgoing_synapses
3497 );
3498
3499 let incoming_synapses = {
3501 let genome_guard = genome_store.read();
3502 let genome = genome_guard.as_ref().unwrap();
3503
3504 let mut total = 0u32;
3505 for (src_id, src_area) in &genome.cortical_areas {
3506 if src_id == &cortical_id_typed {
3507 continue; }
3509
3510 if let Some(dstmap) = src_area.properties.get("cortical_mapping_dst") {
3512 if let Some(obj) = dstmap.as_object() {
3513 if obj.contains_key(cortical_id) {
3514 let mut manager = connectome.write();
3516 let count = manager
3517 .regenerate_synapses_for_mapping(src_id, &cortical_id_typed)
3518 .map_err(|e| {
3519 ServiceError::Backend(format!(
3520 "Failed to rebuild incoming synapses from {}: {}",
3521 src_id, e
3522 ))
3523 })?;
3524 total = total.saturating_add(count as u32);
3525 info!(
3526 "[STRUCTURAL-REBUILD] Rebuilt {} incoming synapses from {}",
3527 count, src_id
3528 );
3529 }
3530 }
3531 }
3532 }
3533
3534 total
3535 };
3536
3537 info!(
3538 "[STRUCTURAL-REBUILD] Rebuilt {} total incoming synapses",
3539 incoming_synapses
3540 );
3541
3542 let npu_arc = {
3546 let manager = connectome.read();
3547 manager
3548 .get_npu()
3549 .ok_or_else(|| ServiceError::Backend("NPU not connected".to_string()))?
3550 .clone()
3551 };
3552
3553 info!(
3560 "[STRUCTURAL-REBUILD] Rebuilding synapse index for {} neurons...",
3561 neurons_created
3562 );
3563 let index_rebuild_start = std::time::Instant::now();
3564 {
3565 let mut npu_lock = npu_arc
3566 .lock()
3567 .map_err(|e| ServiceError::Backend(format!("Failed to lock NPU: {}", e)))?;
3568
3569 npu_lock.rebuild_synapse_index();
3570 }
3572 let index_rebuild_duration = index_rebuild_start.elapsed();
3573 info!(
3574 "[STRUCTURAL-REBUILD] Synapse index rebuild complete in {:.2}s (NPU lock released)",
3575 index_rebuild_duration.as_secs_f64()
3576 );
3577 if index_rebuild_duration.as_millis() > 100 {
3578 warn!(
3579 "[STRUCTURAL-REBUILD] ⚠️ Slow synapse index rebuild: {:.2}s",
3580 index_rebuild_duration.as_secs_f64()
3581 );
3582 }
3583
3584 info!(
3588 "[STRUCTURAL-REBUILD] ✅ Complete: {} neurons, {} outgoing, {} incoming synapses",
3589 neurons_created, outgoing_synapses, incoming_synapses
3590 );
3591
3592 if let Some(ref burst_runner) = burst_runner {
3594 let manager = connectome.read();
3595 let mappings = manager.get_all_cortical_idx_to_id_mappings();
3596 let mapping_count = mappings.len();
3597 burst_runner.write().refresh_cortical_id_mappings(mappings);
3598 info!(target: "feagi-services", "Refreshed burst runner cache with {} cortical areas", mapping_count);
3599 }
3600
3601 if neurons_created > 100_000 {
3605 info!(
3606 "[STRUCTURAL-REBUILD] Using known counts for large area ({} neurons) to avoid expensive NPU lock",
3607 neurons_created
3608 );
3609 let manager = connectome.read();
3611 let area = manager
3612 .get_cortical_area(&cortical_id_typed)
3613 .ok_or_else(|| ServiceError::NotFound {
3614 resource: "CorticalArea".to_string(),
3615 id: cortical_id.to_string(),
3616 })?;
3617 let cortical_idx = manager
3618 .get_cortical_idx(&cortical_id_typed)
3619 .ok_or_else(|| ServiceError::NotFound {
3620 resource: "CorticalArea".to_string(),
3621 id: cortical_id.to_string(),
3622 })?;
3623
3624 let neuron_count = neurons_created as usize;
3626 let synapse_count = (outgoing_synapses + incoming_synapses) as usize;
3627 let incoming_synapse_count = incoming_synapses as usize;
3628 let outgoing_synapse_count = outgoing_synapses as usize;
3629 let cortical_group = area.get_cortical_group();
3630 let cortical_bytes = cortical_id_typed.as_bytes();
3631 let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
3632 let io_flag = if is_io_area {
3633 cortical_id_typed.extract_io_data_flag().ok()
3634 } else {
3635 None
3636 };
3637 let cortical_subtype = if is_io_area {
3638 String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
3639 } else {
3640 None
3641 };
3642 let unit_id = if is_io_area {
3643 Some(cortical_bytes[6])
3644 } else {
3645 None
3646 };
3647 let group_id = if is_io_area {
3648 Some(cortical_bytes[7])
3649 } else {
3650 None
3651 };
3652 let coding_signage = io_flag
3653 .as_ref()
3654 .map(|flag| signage_label_from_flag(flag).to_string());
3655 let coding_behavior = io_flag
3656 .as_ref()
3657 .map(|flag| behavior_label_from_flag(flag).to_string());
3658 let coding_type = io_flag
3659 .as_ref()
3660 .map(|flag| coding_type_label_from_flag(flag).to_string());
3661 let coding_options = if is_io_area {
3662 io_coding_options_for_unit(&cortical_id_typed)
3663 } else {
3664 None
3665 };
3666 let encoding_type = coding_behavior.clone();
3667 let encoding_format = coding_type.clone();
3668
3669 Ok(CorticalAreaInfo {
3671 cortical_id: area.cortical_id.as_base_64(),
3672 cortical_id_s: area.cortical_id.to_string(),
3673 cortical_idx,
3674 name: area.name.clone(),
3675 dimensions: (
3676 area.dimensions.width as usize,
3677 area.dimensions.height as usize,
3678 area.dimensions.depth as usize,
3679 ),
3680 position: area.position.into(),
3681 area_type: cortical_group
3682 .clone()
3683 .unwrap_or_else(|| "CUSTOM".to_string()),
3684 cortical_group: cortical_group
3685 .clone()
3686 .unwrap_or_else(|| "CUSTOM".to_string()),
3687 cortical_type: {
3688 use feagi_evolutionary::extract_memory_properties;
3689 if extract_memory_properties(&area.properties).is_some() {
3690 "memory".to_string()
3691 } else if let Some(group) = &cortical_group {
3692 match group.as_str() {
3693 "IPU" => "sensory".to_string(),
3694 "OPU" => "motor".to_string(),
3695 "CORE" => "core".to_string(),
3696 _ => "custom".to_string(),
3697 }
3698 } else {
3699 "custom".to_string()
3700 }
3701 },
3702 neuron_count,
3703 synapse_count,
3704 incoming_synapse_count,
3705 outgoing_synapse_count,
3706 visible: area.visible(),
3707 sub_group: area.sub_group(),
3708 neurons_per_voxel: area.neurons_per_voxel(),
3709 postsynaptic_current: area.postsynaptic_current() as f64,
3710 postsynaptic_current_max: area.postsynaptic_current_max() as f64,
3711 plasticity_constant: area.plasticity_constant() as f64,
3712 degeneration: area.degeneration() as f64,
3713 psp_uniform_distribution: area.psp_uniform_distribution(),
3714 mp_driven_psp: area.mp_driven_psp(),
3715 firing_threshold: area.firing_threshold() as f64,
3716 firing_threshold_increment: [
3717 area.firing_threshold_increment_x() as f64,
3718 area.firing_threshold_increment_y() as f64,
3719 area.firing_threshold_increment_z() as f64,
3720 ],
3721 firing_threshold_limit: area.firing_threshold_limit() as f64,
3722 consecutive_fire_count: area.consecutive_fire_count(),
3723 snooze_period: area.snooze_period() as u32,
3724 refractory_period: area.refractory_period() as u32,
3725 leak_coefficient: area.leak_coefficient() as f64,
3726 leak_variability: area.leak_variability() as f64,
3727 mp_charge_accumulation: area.mp_charge_accumulation(),
3728 neuron_excitability: area.neuron_excitability() as f64,
3729 burst_engine_active: area.burst_engine_active(),
3730 init_lifespan: area.init_lifespan(),
3731 lifespan_growth_rate: area.lifespan_growth_rate() as f64,
3732 longterm_mem_threshold: area.longterm_mem_threshold(),
3733 temporal_depth: {
3734 use feagi_evolutionary::extract_memory_properties;
3735 extract_memory_properties(&area.properties).map(|p| p.temporal_depth.max(1))
3736 },
3737 properties: HashMap::new(),
3738 cortical_subtype,
3739 encoding_type,
3740 encoding_format,
3741 unit_id,
3742 group_id,
3743 coding_signage,
3744 coding_behavior,
3745 coding_type,
3746 coding_options,
3747 parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
3748 dev_count: area
3749 .properties
3750 .get("dev_count")
3751 .and_then(|v| v.as_u64().map(|n| n as usize)),
3752 cortical_dimensions_per_device: area
3753 .properties
3754 .get("cortical_dimensions_per_device")
3755 .and_then(|v| v.as_array())
3756 .and_then(|arr| {
3757 if arr.len() == 3 {
3758 Some((
3759 arr[0].as_u64()? as usize,
3760 arr[1].as_u64()? as usize,
3761 arr[2].as_u64()? as usize,
3762 ))
3763 } else {
3764 None
3765 }
3766 }),
3767 visualization_voxel_granularity: area
3768 .properties
3769 .get("visualization_voxel_granularity")
3770 .and_then(|v| v.as_array())
3771 .and_then(|arr| {
3772 if arr.len() == 3 {
3773 Some((
3774 arr[0].as_u64()? as u32,
3775 arr[1].as_u64()? as u32,
3776 arr[2].as_u64()? as u32,
3777 ))
3778 } else {
3779 None
3780 }
3781 }),
3782 })
3783 } else {
3784 Self::get_cortical_area_info_blocking(cortical_id, &connectome)
3786 }
3787 }
3788
3789 fn get_cortical_area_info_blocking(
3791 cortical_id: &str,
3792 connectome: &Arc<RwLock<ConnectomeManager>>,
3793 ) -> ServiceResult<CorticalAreaInfo> {
3794 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
3796 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
3797
3798 let manager = connectome.read();
3799
3800 let area = manager
3801 .get_cortical_area(&cortical_id_typed)
3802 .ok_or_else(|| ServiceError::NotFound {
3803 resource: "CorticalArea".to_string(),
3804 id: cortical_id.to_string(),
3805 })?;
3806
3807 let cortical_idx = manager
3808 .get_cortical_idx(&cortical_id_typed)
3809 .ok_or_else(|| ServiceError::NotFound {
3810 resource: "CorticalArea".to_string(),
3811 id: cortical_id.to_string(),
3812 })?;
3813
3814 let neuron_count = manager.get_neuron_count_in_area(&cortical_id_typed);
3815 let outgoing_synapse_count = manager.get_outgoing_synapse_count_in_area(&cortical_id_typed);
3816 let incoming_synapse_count = manager.get_incoming_synapse_count_in_area(&cortical_id_typed);
3817 let synapse_count = outgoing_synapse_count;
3818
3819 let cortical_group = area.get_cortical_group();
3820 let cortical_bytes = cortical_id_typed.as_bytes();
3821 let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
3822 let io_flag = if is_io_area {
3823 cortical_id_typed.extract_io_data_flag().ok()
3824 } else {
3825 None
3826 };
3827 let cortical_subtype = if is_io_area {
3828 String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
3829 } else {
3830 None
3831 };
3832 let unit_id = if is_io_area {
3833 Some(cortical_bytes[6])
3834 } else {
3835 None
3836 };
3837 let group_id = if is_io_area {
3838 Some(cortical_bytes[7])
3839 } else {
3840 None
3841 };
3842 let coding_signage = io_flag
3843 .as_ref()
3844 .map(|flag| signage_label_from_flag(flag).to_string());
3845 let coding_behavior = io_flag
3846 .as_ref()
3847 .map(|flag| behavior_label_from_flag(flag).to_string());
3848 let coding_type = io_flag
3849 .as_ref()
3850 .map(|flag| coding_type_label_from_flag(flag).to_string());
3851 let coding_options = if is_io_area {
3852 io_coding_options_for_unit(&cortical_id_typed)
3853 } else {
3854 None
3855 };
3856 let encoding_type = coding_behavior.clone();
3857 let encoding_format = coding_type.clone();
3858
3859 Ok(CorticalAreaInfo {
3860 cortical_id: area.cortical_id.as_base_64(),
3861 cortical_id_s: area.cortical_id.to_string(), cortical_idx,
3863 name: area.name.clone(),
3864 dimensions: (
3865 area.dimensions.width as usize,
3866 area.dimensions.height as usize,
3867 area.dimensions.depth as usize,
3868 ),
3869 position: area.position.into(),
3870 area_type: cortical_group
3871 .clone()
3872 .unwrap_or_else(|| "CUSTOM".to_string()),
3873 cortical_group: cortical_group
3874 .clone()
3875 .unwrap_or_else(|| "CUSTOM".to_string()),
3876 cortical_type: {
3878 use feagi_evolutionary::extract_memory_properties;
3879 if extract_memory_properties(&area.properties).is_some() {
3880 "memory".to_string()
3881 } else if let Some(group) = &cortical_group {
3882 match group.as_str() {
3883 "IPU" => "sensory".to_string(),
3884 "OPU" => "motor".to_string(),
3885 "CORE" => "core".to_string(),
3886 _ => "custom".to_string(),
3887 }
3888 } else {
3889 "custom".to_string()
3890 }
3891 },
3892 neuron_count,
3893 synapse_count,
3894 incoming_synapse_count,
3895 outgoing_synapse_count,
3896 visible: area.visible(),
3897 sub_group: area.sub_group(),
3898 neurons_per_voxel: area.neurons_per_voxel(),
3899 postsynaptic_current: area.postsynaptic_current() as f64,
3900 postsynaptic_current_max: area.postsynaptic_current_max() as f64,
3901 plasticity_constant: area.plasticity_constant() as f64,
3902 degeneration: area.degeneration() as f64,
3903 psp_uniform_distribution: area.psp_uniform_distribution(),
3904 mp_driven_psp: area.mp_driven_psp(),
3905 firing_threshold: area.firing_threshold() as f64,
3906 firing_threshold_increment: [
3907 area.firing_threshold_increment_x() as f64,
3908 area.firing_threshold_increment_y() as f64,
3909 area.firing_threshold_increment_z() as f64,
3910 ],
3911 firing_threshold_limit: area.firing_threshold_limit() as f64,
3912 consecutive_fire_count: area.consecutive_fire_count(),
3913 snooze_period: area.snooze_period() as u32,
3914 refractory_period: area.refractory_period() as u32,
3915 leak_coefficient: area.leak_coefficient() as f64,
3916 leak_variability: area.leak_variability() as f64,
3917 mp_charge_accumulation: area.mp_charge_accumulation(),
3918 neuron_excitability: area.neuron_excitability() as f64,
3919 burst_engine_active: area.burst_engine_active(),
3920 init_lifespan: area.init_lifespan(),
3921 lifespan_growth_rate: area.lifespan_growth_rate() as f64,
3922 longterm_mem_threshold: area.longterm_mem_threshold(),
3923 temporal_depth: {
3924 use feagi_evolutionary::extract_memory_properties;
3925 extract_memory_properties(&area.properties).map(|p| p.temporal_depth.max(1))
3926 },
3927 properties: HashMap::new(),
3928 cortical_subtype,
3929 encoding_type,
3930 encoding_format,
3931 unit_id,
3932 group_id,
3933 coding_signage,
3934 coding_behavior,
3935 coding_type,
3936 coding_options,
3937 parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
3938 dev_count: area
3940 .properties
3941 .get("dev_count")
3942 .and_then(|v| v.as_u64().map(|n| n as usize)),
3943 cortical_dimensions_per_device: area
3944 .properties
3945 .get("cortical_dimensions_per_device")
3946 .and_then(|v| v.as_array())
3947 .and_then(|arr| {
3948 if arr.len() == 3 {
3949 Some((
3950 arr[0].as_u64()? as usize,
3951 arr[1].as_u64()? as usize,
3952 arr[2].as_u64()? as usize,
3953 ))
3954 } else {
3955 None
3956 }
3957 }),
3958 visualization_voxel_granularity: area
3959 .properties
3960 .get("visualization_voxel_granularity")
3961 .and_then(|v| v.as_array())
3962 .and_then(|arr| {
3963 if arr.len() == 3 {
3964 Some((
3965 arr[0].as_u64()? as u32,
3966 arr[1].as_u64()? as u32,
3967 arr[2].as_u64()? as u32,
3968 ))
3969 } else {
3970 None
3971 }
3972 }),
3973 })
3974 }
3975
3976 async fn get_cortical_area_info(&self, cortical_id: &str) -> ServiceResult<CorticalAreaInfo> {
3978 let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
3980 .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
3981
3982 let manager = self.connectome.read();
3983
3984 let area = manager
3985 .get_cortical_area(&cortical_id_typed)
3986 .ok_or_else(|| ServiceError::NotFound {
3987 resource: "CorticalArea".to_string(),
3988 id: cortical_id.to_string(),
3989 })?;
3990
3991 tracing::info!(target: "feagi-services",
3993 "get_cortical_area_info: querying {} - position {:?}, dimensions {:?}, visible: {}",
3994 cortical_id, area.position, area.dimensions, area.visible()
3995 );
3996
3997 let cortical_idx = manager
3998 .get_cortical_idx(&cortical_id_typed)
3999 .ok_or_else(|| ServiceError::NotFound {
4000 resource: "CorticalArea".to_string(),
4001 id: cortical_id.to_string(),
4002 })?;
4003
4004 let neuron_count = manager.get_neuron_count_in_area(&cortical_id_typed);
4005 let outgoing_synapse_count = manager.get_outgoing_synapse_count_in_area(&cortical_id_typed);
4006 let incoming_synapse_count = manager.get_incoming_synapse_count_in_area(&cortical_id_typed);
4007 let synapse_count = outgoing_synapse_count;
4008
4009 let cortical_group = area.get_cortical_group();
4011 let cortical_bytes = cortical_id_typed.as_bytes();
4012 let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
4013 let io_flag = if is_io_area {
4014 cortical_id_typed.extract_io_data_flag().ok()
4015 } else {
4016 None
4017 };
4018 let cortical_subtype = if is_io_area {
4019 String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
4020 } else {
4021 None
4022 };
4023 let unit_id = if is_io_area {
4024 Some(cortical_bytes[6])
4025 } else {
4026 None
4027 };
4028 let group_id = if is_io_area {
4029 Some(cortical_bytes[7])
4030 } else {
4031 None
4032 };
4033 let coding_signage = io_flag
4034 .as_ref()
4035 .map(|flag| signage_label_from_flag(flag).to_string());
4036 let coding_behavior = io_flag
4037 .as_ref()
4038 .map(|flag| behavior_label_from_flag(flag).to_string());
4039 let coding_type = io_flag
4040 .as_ref()
4041 .map(|flag| coding_type_label_from_flag(flag).to_string());
4042 let coding_options = if is_io_area {
4043 io_coding_options_for_unit(&cortical_id_typed)
4044 } else {
4045 None
4046 };
4047 let encoding_type = coding_behavior.clone();
4048 let encoding_format = coding_type.clone();
4049
4050 Ok(CorticalAreaInfo {
4051 cortical_id: area.cortical_id.as_base_64(),
4052 cortical_id_s: area.cortical_id.to_string(), cortical_idx,
4054 name: area.name.clone(),
4055 dimensions: (
4056 area.dimensions.width as usize,
4057 area.dimensions.height as usize,
4058 area.dimensions.depth as usize,
4059 ),
4060 position: area.position.into(),
4061 area_type: cortical_group
4062 .clone()
4063 .unwrap_or_else(|| "CUSTOM".to_string()),
4064 cortical_group: cortical_group
4065 .clone()
4066 .unwrap_or_else(|| "CUSTOM".to_string()),
4067 cortical_type: {
4069 use feagi_evolutionary::extract_memory_properties;
4070 if extract_memory_properties(&area.properties).is_some() {
4071 "memory".to_string()
4072 } else if let Some(group) = &cortical_group {
4073 match group.as_str() {
4074 "IPU" => "sensory".to_string(),
4075 "OPU" => "motor".to_string(),
4076 "CORE" => "core".to_string(),
4077 _ => "custom".to_string(),
4078 }
4079 } else {
4080 "custom".to_string()
4081 }
4082 },
4083 neuron_count,
4084 synapse_count,
4085 incoming_synapse_count,
4086 outgoing_synapse_count,
4087 visible: area.visible(),
4088 sub_group: area.sub_group(),
4089 neurons_per_voxel: area.neurons_per_voxel(),
4090 postsynaptic_current: area.postsynaptic_current() as f64,
4091 postsynaptic_current_max: area.postsynaptic_current_max() as f64,
4092 plasticity_constant: area.plasticity_constant() as f64,
4093 degeneration: area.degeneration() as f64,
4094 psp_uniform_distribution: area.psp_uniform_distribution(),
4095 mp_driven_psp: area.mp_driven_psp(),
4096 firing_threshold: area.firing_threshold() as f64,
4097 firing_threshold_increment: [
4098 area.firing_threshold_increment_x() as f64,
4099 area.firing_threshold_increment_y() as f64,
4100 area.firing_threshold_increment_z() as f64,
4101 ],
4102 firing_threshold_limit: area.firing_threshold_limit() as f64,
4103 consecutive_fire_count: area.consecutive_fire_count(),
4104 snooze_period: area.snooze_period() as u32,
4105 refractory_period: area.refractory_period() as u32,
4106 leak_coefficient: area.leak_coefficient() as f64,
4107 leak_variability: area.leak_variability() as f64,
4108 mp_charge_accumulation: area.mp_charge_accumulation(),
4109 neuron_excitability: area.neuron_excitability() as f64,
4110 burst_engine_active: area.burst_engine_active(),
4111 init_lifespan: area.init_lifespan(),
4112 lifespan_growth_rate: area.lifespan_growth_rate() as f64,
4113 longterm_mem_threshold: area.longterm_mem_threshold(),
4114 temporal_depth: {
4115 use feagi_evolutionary::extract_memory_properties;
4116 extract_memory_properties(&area.properties).map(|p| p.temporal_depth.max(1))
4117 },
4118 properties: HashMap::new(),
4119 cortical_subtype,
4120 encoding_type,
4121 encoding_format,
4122 unit_id,
4123 group_id,
4124 coding_signage,
4125 coding_behavior,
4126 coding_type,
4127 coding_options,
4128 parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
4129 dev_count: area
4131 .properties
4132 .get("dev_count")
4133 .and_then(|v| v.as_u64().map(|n| n as usize)),
4134 cortical_dimensions_per_device: area
4135 .properties
4136 .get("cortical_dimensions_per_device")
4137 .and_then(|v| v.as_array())
4138 .and_then(|arr| {
4139 if arr.len() == 3 {
4140 Some((
4141 arr[0].as_u64()? as usize,
4142 arr[1].as_u64()? as usize,
4143 arr[2].as_u64()? as usize,
4144 ))
4145 } else {
4146 None
4147 }
4148 }),
4149 visualization_voxel_granularity: area
4150 .properties
4151 .get("visualization_voxel_granularity")
4152 .and_then(|v| v.as_array())
4153 .and_then(|arr| {
4154 if arr.len() == 3 {
4155 Some((
4156 arr[0].as_u64()? as u32,
4157 arr[1].as_u64()? as u32,
4158 arr[2].as_u64()? as u32,
4159 ))
4160 } else {
4161 None
4162 }
4163 }),
4164 })
4165 }
4166}