1use serde::{Deserialize, Serialize};
8
9fn default_max_processes() -> u32 {
11 64
12}
13
14fn default_health_check_interval_secs() -> u64 {
16 30
17}
18
19fn default_enabled() -> bool {
21 true
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ClusterNetworkConfig {
31 #[serde(default = "default_replication_factor", alias = "replicationFactor")]
33 pub replication_factor: usize,
34
35 #[serde(default = "default_shard_count", alias = "shardCount")]
37 pub shard_count: u32,
38
39 #[serde(
41 default = "default_cluster_heartbeat",
42 alias = "heartbeatIntervalSecs"
43 )]
44 pub heartbeat_interval_secs: u64,
45
46 #[serde(default = "default_node_timeout", alias = "nodeTimeoutSecs")]
48 pub node_timeout_secs: u64,
49
50 #[serde(default = "default_enable_consensus", alias = "enableConsensus")]
52 pub enable_consensus: bool,
53
54 #[serde(default = "default_min_quorum", alias = "minQuorumSize")]
56 pub min_quorum_size: usize,
57
58 #[serde(default, alias = "seedNodes")]
60 pub seed_nodes: Vec<String>,
61
62 #[serde(default, alias = "nodeName")]
64 pub node_name: Option<String>,
65}
66
67fn default_replication_factor() -> usize {
68 3
69}
70fn default_shard_count() -> u32 {
71 64
72}
73fn default_cluster_heartbeat() -> u64 {
74 5
75}
76fn default_node_timeout() -> u64 {
77 30
78}
79fn default_enable_consensus() -> bool {
80 true
81}
82fn default_min_quorum() -> usize {
83 2
84}
85
86impl Default for ClusterNetworkConfig {
87 fn default() -> Self {
88 Self {
89 replication_factor: default_replication_factor(),
90 shard_count: default_shard_count(),
91 heartbeat_interval_secs: default_cluster_heartbeat(),
92 node_timeout_secs: default_node_timeout(),
93 enable_consensus: default_enable_consensus(),
94 min_quorum_size: default_min_quorum(),
95 seed_nodes: Vec::new(),
96 node_name: None,
97 }
98 }
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct KernelConfig {
120 #[serde(default = "default_enabled")]
125 pub enabled: bool,
126
127 #[serde(default = "default_max_processes", alias = "maxProcesses")]
129 pub max_processes: u32,
130
131 #[serde(
133 default = "default_health_check_interval_secs",
134 alias = "healthCheckIntervalSecs"
135 )]
136 pub health_check_interval_secs: u64,
137
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub cluster: Option<ClusterNetworkConfig>,
141
142 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub chain: Option<ChainConfig>,
145
146 #[serde(default, skip_serializing_if = "Option::is_none", alias = "resourceTree")]
148 pub resource_tree: Option<ResourceTreeConfig>,
149
150 #[serde(default, skip_serializing_if = "Option::is_none")]
152 pub vector: Option<VectorConfig>,
153
154 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub profiles: Option<ProfilesConfig>,
157
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub pairing: Option<PairingConfig>,
161}
162
163impl Default for KernelConfig {
164 fn default() -> Self {
165 Self {
166 enabled: true,
167 max_processes: default_max_processes(),
168 health_check_interval_secs: default_health_check_interval_secs(),
169 cluster: None,
170 chain: None,
171 resource_tree: None,
172 vector: None,
173 profiles: None,
174 pairing: None,
175 }
176 }
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct ProfilesConfig {
196 #[serde(default = "default_profiles_enabled")]
198 pub enabled: bool,
199
200 #[serde(default = "default_profiles_storage_path")]
202 pub storage_path: String,
203
204 #[serde(default = "default_profile_name")]
206 pub default_profile: String,
207}
208
209fn default_profiles_enabled() -> bool {
210 true
211}
212
213fn default_profiles_storage_path() -> String {
214 ".weftos/profiles".to_owned()
215}
216
217fn default_profile_name() -> String {
218 "default".to_owned()
219}
220
221impl Default for ProfilesConfig {
222 fn default() -> Self {
223 Self {
224 enabled: default_profiles_enabled(),
225 storage_path: default_profiles_storage_path(),
226 default_profile: default_profile_name(),
227 }
228 }
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct PairingConfig {
247 #[serde(default = "default_pairing_persist_path")]
249 pub persist_path: String,
250
251 #[serde(default = "default_pairing_window_secs")]
253 pub default_window_secs: u64,
254}
255
256fn default_pairing_persist_path() -> String {
257 ".weftos/runtime/paired_hosts.json".to_owned()
258}
259
260fn default_pairing_window_secs() -> u64 {
261 30
262}
263
264impl Default for PairingConfig {
265 fn default() -> Self {
266 Self {
267 persist_path: default_pairing_persist_path(),
268 default_window_secs: default_pairing_window_secs(),
269 }
270 }
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
275pub struct ChainConfig {
276 #[serde(default = "default_true")]
278 pub enabled: bool,
279
280 #[serde(default = "default_checkpoint_interval", alias = "checkpointInterval")]
282 pub checkpoint_interval: u64,
283
284 #[serde(default)]
286 pub chain_id: u32,
287
288 #[serde(
291 default,
292 skip_serializing_if = "Option::is_none",
293 alias = "checkpointPath"
294 )]
295 pub checkpoint_path: Option<String>,
296}
297
298fn default_true() -> bool {
299 true
300}
301fn default_checkpoint_interval() -> u64 {
302 1000
303}
304
305impl Default for ChainConfig {
306 fn default() -> Self {
307 Self {
308 enabled: true,
309 checkpoint_interval: default_checkpoint_interval(),
310 chain_id: 0,
311 checkpoint_path: None,
312 }
313 }
314}
315
316impl ChainConfig {
317 pub fn effective_checkpoint_path(&self) -> Option<String> {
322 if self.checkpoint_path.is_some() {
323 return self.checkpoint_path.clone();
324 }
325 #[cfg(feature = "native")]
326 {
327 dirs::home_dir().map(|h| {
328 h.join(".clawft")
329 .join("chain.json")
330 .to_string_lossy()
331 .into_owned()
332 })
333 }
334 #[cfg(not(feature = "native"))]
335 {
336 None
337 }
338 }
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ResourceTreeConfig {
344 #[serde(default = "default_true_rt")]
346 pub enabled: bool,
347
348 #[serde(
350 default,
351 skip_serializing_if = "Option::is_none",
352 alias = "checkpointPath"
353 )]
354 pub checkpoint_path: Option<String>,
355}
356
357fn default_true_rt() -> bool {
358 true
359}
360
361impl Default for ResourceTreeConfig {
362 fn default() -> Self {
363 Self {
364 enabled: true,
365 checkpoint_path: None,
366 }
367 }
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
374#[serde(rename_all = "lowercase")]
375pub enum VectorBackendKind {
376 #[default]
378 Hnsw,
379 DiskAnn,
381 Hybrid,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct VectorHnswConfig {
388 #[serde(default = "default_ef_construction")]
390 pub ef_construction: usize,
391
392 #[serde(default = "default_m")]
394 pub m: usize,
395
396 #[serde(default = "default_max_elements")]
398 pub max_elements: usize,
399}
400
401fn default_ef_construction() -> usize {
402 200
403}
404fn default_m() -> usize {
405 16
406}
407fn default_max_elements() -> usize {
408 100_000
409}
410
411impl Default for VectorHnswConfig {
412 fn default() -> Self {
413 Self {
414 ef_construction: default_ef_construction(),
415 m: default_m(),
416 max_elements: default_max_elements(),
417 }
418 }
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct VectorDiskAnnConfig {
424 #[serde(default = "default_diskann_max_points")]
426 pub max_points: usize,
427
428 #[serde(default = "default_diskann_dimensions")]
430 pub dimensions: usize,
431
432 #[serde(default = "default_diskann_num_neighbors")]
434 pub num_neighbors: usize,
435
436 #[serde(default = "default_diskann_search_list_size")]
438 pub search_list_size: usize,
439
440 #[serde(default = "default_diskann_data_path")]
442 pub data_path: String,
443
444 #[serde(default = "default_diskann_use_pq")]
446 pub use_pq: bool,
447
448 #[serde(default = "default_diskann_pq_num_chunks")]
450 pub pq_num_chunks: usize,
451}
452
453fn default_diskann_max_points() -> usize {
454 10_000_000
455}
456fn default_diskann_dimensions() -> usize {
457 384
458}
459fn default_diskann_num_neighbors() -> usize {
460 64
461}
462fn default_diskann_search_list_size() -> usize {
463 100
464}
465fn default_diskann_data_path() -> String {
466 ".weftos/diskann".to_owned()
467}
468fn default_diskann_use_pq() -> bool {
469 true
470}
471fn default_diskann_pq_num_chunks() -> usize {
472 48
473}
474
475impl Default for VectorDiskAnnConfig {
476 fn default() -> Self {
477 Self {
478 max_points: default_diskann_max_points(),
479 dimensions: default_diskann_dimensions(),
480 num_neighbors: default_diskann_num_neighbors(),
481 search_list_size: default_diskann_search_list_size(),
482 data_path: default_diskann_data_path(),
483 use_pq: default_diskann_use_pq(),
484 pq_num_chunks: default_diskann_pq_num_chunks(),
485 }
486 }
487}
488
489#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
491#[serde(rename_all = "lowercase")]
492pub enum VectorEvictionPolicy {
493 #[default]
495 Lru,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize)]
500pub struct VectorHybridConfig {
501 #[serde(default = "default_hybrid_hot_capacity")]
503 pub hot_capacity: usize,
504
505 #[serde(default = "default_hybrid_promotion_threshold")]
507 pub promotion_threshold: u32,
508
509 #[serde(default)]
511 pub eviction_policy: VectorEvictionPolicy,
512}
513
514fn default_hybrid_hot_capacity() -> usize {
515 50_000
516}
517fn default_hybrid_promotion_threshold() -> u32 {
518 3
519}
520
521impl Default for VectorHybridConfig {
522 fn default() -> Self {
523 Self {
524 hot_capacity: default_hybrid_hot_capacity(),
525 promotion_threshold: default_hybrid_promotion_threshold(),
526 eviction_policy: VectorEvictionPolicy::default(),
527 }
528 }
529}
530
531#[derive(Debug, Clone, Serialize, Deserialize)]
555pub struct VectorConfig {
556 #[serde(default)]
558 pub backend: VectorBackendKind,
559
560 #[serde(default, skip_serializing_if = "Option::is_none")]
562 pub hnsw: Option<VectorHnswConfig>,
563
564 #[serde(default, skip_serializing_if = "Option::is_none")]
566 pub diskann: Option<VectorDiskAnnConfig>,
567
568 #[serde(default, skip_serializing_if = "Option::is_none")]
570 pub hybrid: Option<VectorHybridConfig>,
571
572 #[serde(default, skip_serializing_if = "Option::is_none")]
576 pub log_quantized: Option<LogQuantizedStubConfig>,
577
578 #[serde(default, skip_serializing_if = "Option::is_none")]
582 pub simd_distance: Option<SimdDistanceStubConfig>,
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct LogQuantizedStubConfig {
594 #[serde(default)]
596 pub enabled: bool,
597 #[serde(default = "default_log_quantized_compression_ratio")]
599 pub compression_ratio: usize,
600}
601
602fn default_log_quantized_compression_ratio() -> usize {
603 4
604}
605
606impl Default for LogQuantizedStubConfig {
607 fn default() -> Self {
608 Self {
609 enabled: false,
610 compression_ratio: default_log_quantized_compression_ratio(),
611 }
612 }
613}
614
615#[derive(Debug, Clone, Serialize, Deserialize)]
621pub struct SimdDistanceStubConfig {
622 #[serde(default)]
624 pub enabled: bool,
625 #[serde(default)]
628 pub pad_to_power_of_two: bool,
629}
630
631impl Default for SimdDistanceStubConfig {
632 fn default() -> Self {
633 Self {
634 enabled: false,
635 pad_to_power_of_two: false,
636 }
637 }
638}
639
640impl Default for VectorConfig {
641 fn default() -> Self {
642 Self {
643 backend: VectorBackendKind::default(),
644 hnsw: None,
645 diskann: None,
646 hybrid: None,
647 log_quantized: None,
648 simd_distance: None,
649 }
650 }
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 #[test]
658 fn default_kernel_config() {
659 let cfg = KernelConfig::default();
660 assert!(cfg.enabled);
661 assert_eq!(cfg.max_processes, 64);
662 assert_eq!(cfg.health_check_interval_secs, 30);
663 }
664
665 #[test]
666 fn deserialize_empty() {
667 let cfg: KernelConfig = serde_json::from_str("{}").unwrap();
668 assert!(cfg.enabled);
669 assert_eq!(cfg.max_processes, 64);
670 }
671
672 #[test]
673 fn deserialize_camel_case() {
674 let json = r#"{"maxProcesses": 128, "healthCheckIntervalSecs": 15}"#;
675 let cfg: KernelConfig = serde_json::from_str(json).unwrap();
676 assert_eq!(cfg.max_processes, 128);
677 assert_eq!(cfg.health_check_interval_secs, 15);
678 }
679
680 #[test]
681 fn serde_roundtrip() {
682 let cfg = KernelConfig {
683 enabled: true,
684 max_processes: 256,
685 health_check_interval_secs: 10,
686 cluster: None,
687 chain: None,
688 resource_tree: None,
689 vector: None,
690 profiles: None,
691 pairing: None,
692 };
693 let json = serde_json::to_string(&cfg).unwrap();
694 let restored: KernelConfig = serde_json::from_str(&json).unwrap();
695 assert_eq!(restored.enabled, cfg.enabled);
696 assert_eq!(restored.max_processes, cfg.max_processes);
697 }
698
699 #[test]
700 fn profiles_config_defaults() {
701 let cfg = ProfilesConfig::default();
702 assert!(cfg.enabled);
703 assert_eq!(cfg.storage_path, ".weftos/profiles");
704 assert_eq!(cfg.default_profile, "default");
705 }
706
707 #[test]
708 fn profiles_config_deserialize() {
709 let json = r#"{"enabled": false, "storage_path": "/tmp/profiles", "default_profile": "admin"}"#;
710 let cfg: ProfilesConfig = serde_json::from_str(json).unwrap();
711 assert!(!cfg.enabled);
712 assert_eq!(cfg.storage_path, "/tmp/profiles");
713 assert_eq!(cfg.default_profile, "admin");
714 }
715
716 #[test]
717 fn pairing_config_defaults() {
718 let cfg = PairingConfig::default();
719 assert_eq!(cfg.persist_path, ".weftos/runtime/paired_hosts.json");
720 assert_eq!(cfg.default_window_secs, 30);
721 }
722
723 #[test]
724 fn pairing_config_deserialize() {
725 let json = r#"{"persist_path": "/opt/pairing.json", "default_window_secs": 60}"#;
726 let cfg: PairingConfig = serde_json::from_str(json).unwrap();
727 assert_eq!(cfg.persist_path, "/opt/pairing.json");
728 assert_eq!(cfg.default_window_secs, 60);
729 }
730
731 #[test]
732 fn kernel_config_with_profiles_and_pairing() {
733 let json = r#"{"profiles": {"enabled": true}, "pairing": {"default_window_secs": 45}}"#;
734 let cfg: KernelConfig = serde_json::from_str(json).unwrap();
735 assert!(cfg.profiles.is_some());
736 assert!(cfg.profiles.unwrap().enabled);
737 assert!(cfg.pairing.is_some());
738 assert_eq!(cfg.pairing.unwrap().default_window_secs, 45);
739 }
740
741 #[test]
742 fn vector_config_defaults() {
743 let cfg = VectorConfig::default();
744 assert_eq!(cfg.backend, VectorBackendKind::Hnsw);
745 assert!(cfg.hnsw.is_none());
746 assert!(cfg.diskann.is_none());
747 assert!(cfg.hybrid.is_none());
748 assert!(cfg.log_quantized.is_none());
749 assert!(cfg.simd_distance.is_none());
750 }
751
752 #[test]
753 fn vector_config_deserialize_hybrid() {
754 let json = r#"{"backend": "hybrid", "hybrid": {"hot_capacity": 1000, "promotion_threshold": 5}}"#;
755 let cfg: VectorConfig = serde_json::from_str(json).unwrap();
756 assert_eq!(cfg.backend, VectorBackendKind::Hybrid);
757 let h = cfg.hybrid.unwrap();
758 assert_eq!(h.hot_capacity, 1000);
759 assert_eq!(h.promotion_threshold, 5);
760 }
761
762 #[test]
763 fn vector_config_deserialize_diskann() {
764 let json = r#"{"backend": "diskann", "diskann": {"max_points": 5000000}}"#;
765 let cfg: VectorConfig = serde_json::from_str(json).unwrap();
766 assert_eq!(cfg.backend, VectorBackendKind::DiskAnn);
767 let d = cfg.diskann.unwrap();
768 assert_eq!(d.max_points, 5_000_000);
769 }
770
771 #[test]
772 fn kernel_config_with_vector() {
773 let json = r#"{"vector": {"backend": "hnsw"}}"#;
774 let cfg: KernelConfig = serde_json::from_str(json).unwrap();
775 assert!(cfg.vector.is_some());
776 assert_eq!(cfg.vector.unwrap().backend, VectorBackendKind::Hnsw);
777 }
778
779 #[test]
780 fn log_quantized_stub_config_defaults() {
781 let cfg = LogQuantizedStubConfig::default();
782 assert!(!cfg.enabled);
783 assert_eq!(cfg.compression_ratio, 4);
784 }
785
786 #[test]
787 fn log_quantized_stub_config_deserialize() {
788 let json = r#"{"enabled": true, "compression_ratio": 8}"#;
789 let cfg: LogQuantizedStubConfig = serde_json::from_str(json).unwrap();
790 assert!(cfg.enabled);
791 assert_eq!(cfg.compression_ratio, 8);
792 }
793
794 #[test]
795 fn simd_distance_stub_config_defaults() {
796 let cfg = SimdDistanceStubConfig::default();
797 assert!(!cfg.enabled);
798 assert!(!cfg.pad_to_power_of_two);
799 }
800
801 #[test]
802 fn simd_distance_stub_config_deserialize() {
803 let json = r#"{"enabled": true, "pad_to_power_of_two": true}"#;
804 let cfg: SimdDistanceStubConfig = serde_json::from_str(json).unwrap();
805 assert!(cfg.enabled);
806 assert!(cfg.pad_to_power_of_two);
807 }
808
809 #[test]
810 fn vector_config_with_shaal_stubs() {
811 let json = r#"{
812 "backend": "hnsw",
813 "log_quantized": {"enabled": true, "compression_ratio": 16},
814 "simd_distance": {"enabled": true, "pad_to_power_of_two": true}
815 }"#;
816 let cfg: VectorConfig = serde_json::from_str(json).unwrap();
817 let lq = cfg.log_quantized.unwrap();
818 assert!(lq.enabled);
819 assert_eq!(lq.compression_ratio, 16);
820 let sd = cfg.simd_distance.unwrap();
821 assert!(sd.enabled);
822 assert!(sd.pad_to_power_of_two);
823 }
824}