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