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
573impl Default for VectorConfig {
574 fn default() -> Self {
575 Self {
576 backend: VectorBackendKind::default(),
577 hnsw: None,
578 diskann: None,
579 hybrid: None,
580 }
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn default_kernel_config() {
590 let cfg = KernelConfig::default();
591 assert!(cfg.enabled);
592 assert_eq!(cfg.max_processes, 64);
593 assert_eq!(cfg.health_check_interval_secs, 30);
594 }
595
596 #[test]
597 fn deserialize_empty() {
598 let cfg: KernelConfig = serde_json::from_str("{}").unwrap();
599 assert!(cfg.enabled);
600 assert_eq!(cfg.max_processes, 64);
601 }
602
603 #[test]
604 fn deserialize_camel_case() {
605 let json = r#"{"maxProcesses": 128, "healthCheckIntervalSecs": 15}"#;
606 let cfg: KernelConfig = serde_json::from_str(json).unwrap();
607 assert_eq!(cfg.max_processes, 128);
608 assert_eq!(cfg.health_check_interval_secs, 15);
609 }
610
611 #[test]
612 fn serde_roundtrip() {
613 let cfg = KernelConfig {
614 enabled: true,
615 max_processes: 256,
616 health_check_interval_secs: 10,
617 cluster: None,
618 chain: None,
619 resource_tree: None,
620 vector: None,
621 profiles: None,
622 pairing: None,
623 };
624 let json = serde_json::to_string(&cfg).unwrap();
625 let restored: KernelConfig = serde_json::from_str(&json).unwrap();
626 assert_eq!(restored.enabled, cfg.enabled);
627 assert_eq!(restored.max_processes, cfg.max_processes);
628 }
629
630 #[test]
631 fn profiles_config_defaults() {
632 let cfg = ProfilesConfig::default();
633 assert!(cfg.enabled);
634 assert_eq!(cfg.storage_path, ".weftos/profiles");
635 assert_eq!(cfg.default_profile, "default");
636 }
637
638 #[test]
639 fn profiles_config_deserialize() {
640 let json = r#"{"enabled": false, "storage_path": "/tmp/profiles", "default_profile": "admin"}"#;
641 let cfg: ProfilesConfig = serde_json::from_str(json).unwrap();
642 assert!(!cfg.enabled);
643 assert_eq!(cfg.storage_path, "/tmp/profiles");
644 assert_eq!(cfg.default_profile, "admin");
645 }
646
647 #[test]
648 fn pairing_config_defaults() {
649 let cfg = PairingConfig::default();
650 assert_eq!(cfg.persist_path, ".weftos/runtime/paired_hosts.json");
651 assert_eq!(cfg.default_window_secs, 30);
652 }
653
654 #[test]
655 fn pairing_config_deserialize() {
656 let json = r#"{"persist_path": "/opt/pairing.json", "default_window_secs": 60}"#;
657 let cfg: PairingConfig = serde_json::from_str(json).unwrap();
658 assert_eq!(cfg.persist_path, "/opt/pairing.json");
659 assert_eq!(cfg.default_window_secs, 60);
660 }
661
662 #[test]
663 fn kernel_config_with_profiles_and_pairing() {
664 let json = r#"{"profiles": {"enabled": true}, "pairing": {"default_window_secs": 45}}"#;
665 let cfg: KernelConfig = serde_json::from_str(json).unwrap();
666 assert!(cfg.profiles.is_some());
667 assert!(cfg.profiles.unwrap().enabled);
668 assert!(cfg.pairing.is_some());
669 assert_eq!(cfg.pairing.unwrap().default_window_secs, 45);
670 }
671
672 #[test]
673 fn vector_config_defaults() {
674 let cfg = VectorConfig::default();
675 assert_eq!(cfg.backend, VectorBackendKind::Hnsw);
676 assert!(cfg.hnsw.is_none());
677 assert!(cfg.diskann.is_none());
678 assert!(cfg.hybrid.is_none());
679 }
680
681 #[test]
682 fn vector_config_deserialize_hybrid() {
683 let json = r#"{"backend": "hybrid", "hybrid": {"hot_capacity": 1000, "promotion_threshold": 5}}"#;
684 let cfg: VectorConfig = serde_json::from_str(json).unwrap();
685 assert_eq!(cfg.backend, VectorBackendKind::Hybrid);
686 let h = cfg.hybrid.unwrap();
687 assert_eq!(h.hot_capacity, 1000);
688 assert_eq!(h.promotion_threshold, 5);
689 }
690
691 #[test]
692 fn vector_config_deserialize_diskann() {
693 let json = r#"{"backend": "diskann", "diskann": {"max_points": 5000000}}"#;
694 let cfg: VectorConfig = serde_json::from_str(json).unwrap();
695 assert_eq!(cfg.backend, VectorBackendKind::DiskAnn);
696 let d = cfg.diskann.unwrap();
697 assert_eq!(d.max_points, 5_000_000);
698 }
699
700 #[test]
701 fn kernel_config_with_vector() {
702 let json = r#"{"vector": {"backend": "hnsw"}}"#;
703 let cfg: KernelConfig = serde_json::from_str(json).unwrap();
704 assert!(cfg.vector.is_some());
705 assert_eq!(cfg.vector.unwrap().backend, VectorBackendKind::Hnsw);
706 }
707}