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