Skip to main content

clawft_types/config/
kernel.rs

1//! Kernel configuration types.
2//!
3//! These types are defined in `clawft-types` so they can be embedded
4//! in the root [`Config`](super::Config) without creating a circular
5//! dependency with `clawft-kernel`.
6
7use serde::{Deserialize, Serialize};
8
9/// Default maximum number of concurrent processes.
10fn default_max_processes() -> u32 {
11    64
12}
13
14/// Default health check interval in seconds.
15fn default_health_check_interval_secs() -> u64 {
16    30
17}
18
19/// Cluster networking configuration for distributed WeftOS nodes.
20///
21/// Controls the ruvector-powered clustering layer that coordinates
22/// native nodes. Browser/edge nodes join via WebSocket to a
23/// coordinator and do not need this configuration.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ClusterNetworkConfig {
26    /// Number of replica copies for each shard (default: 3).
27    #[serde(default = "default_replication_factor", alias = "replicationFactor")]
28    pub replication_factor: usize,
29
30    /// Total number of shards in the cluster (default: 64).
31    #[serde(default = "default_shard_count", alias = "shardCount")]
32    pub shard_count: u32,
33
34    /// Interval between heartbeat checks in seconds (default: 5).
35    #[serde(
36        default = "default_cluster_heartbeat",
37        alias = "heartbeatIntervalSecs"
38    )]
39    pub heartbeat_interval_secs: u64,
40
41    /// Timeout before marking a node offline in seconds (default: 30).
42    #[serde(default = "default_node_timeout", alias = "nodeTimeoutSecs")]
43    pub node_timeout_secs: u64,
44
45    /// Whether to enable DAG-based consensus (default: true).
46    #[serde(default = "default_enable_consensus", alias = "enableConsensus")]
47    pub enable_consensus: bool,
48
49    /// Minimum nodes required for quorum (default: 2).
50    #[serde(default = "default_min_quorum", alias = "minQuorumSize")]
51    pub min_quorum_size: usize,
52
53    /// Seed node addresses for discovery (coordinator addresses).
54    #[serde(default, alias = "seedNodes")]
55    pub seed_nodes: Vec<String>,
56
57    /// Human-readable display name for this node.
58    #[serde(default, alias = "nodeName")]
59    pub node_name: Option<String>,
60}
61
62fn default_replication_factor() -> usize {
63    3
64}
65fn default_shard_count() -> u32 {
66    64
67}
68fn default_cluster_heartbeat() -> u64 {
69    5
70}
71fn default_node_timeout() -> u64 {
72    30
73}
74fn default_enable_consensus() -> bool {
75    true
76}
77fn default_min_quorum() -> usize {
78    2
79}
80
81impl Default for ClusterNetworkConfig {
82    fn default() -> Self {
83        Self {
84            replication_factor: default_replication_factor(),
85            shard_count: default_shard_count(),
86            heartbeat_interval_secs: default_cluster_heartbeat(),
87            node_timeout_secs: default_node_timeout(),
88            enable_consensus: default_enable_consensus(),
89            min_quorum_size: default_min_quorum(),
90            seed_nodes: Vec::new(),
91            node_name: None,
92        }
93    }
94}
95
96/// Kernel subsystem configuration.
97///
98/// Embedded in the root `Config` under the `kernel` key. All fields
99/// have sensible defaults so that existing configuration files parse
100/// without errors.
101///
102/// # Example JSON
103///
104/// ```json
105/// {
106///   "kernel": {
107///     "enabled": false,
108///     "max_processes": 128,
109///     "health_check_interval_secs": 15
110///   }
111/// }
112/// ```
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct KernelConfig {
115    /// Whether the kernel subsystem is enabled.
116    ///
117    /// When `false` (the default), kernel subsystems do not activate
118    /// unless explicitly invoked via `weave kernel` CLI commands.
119    #[serde(default)]
120    pub enabled: bool,
121
122    /// Maximum number of concurrent processes in the process table.
123    #[serde(default = "default_max_processes", alias = "maxProcesses")]
124    pub max_processes: u32,
125
126    /// Interval (in seconds) between periodic health checks.
127    #[serde(
128        default = "default_health_check_interval_secs",
129        alias = "healthCheckIntervalSecs"
130    )]
131    pub health_check_interval_secs: u64,
132
133    /// Cluster networking configuration (native coordinator nodes).
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub cluster: Option<ClusterNetworkConfig>,
136
137    /// Local chain configuration (exochain feature).
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub chain: Option<ChainConfig>,
140
141    /// Resource tree configuration (exochain feature).
142    #[serde(default, skip_serializing_if = "Option::is_none", alias = "resourceTree")]
143    pub resource_tree: Option<ResourceTreeConfig>,
144}
145
146impl Default for KernelConfig {
147    fn default() -> Self {
148        Self {
149            enabled: false,
150            max_processes: default_max_processes(),
151            health_check_interval_secs: default_health_check_interval_secs(),
152            cluster: None,
153            chain: None,
154            resource_tree: None,
155        }
156    }
157}
158
159/// Local chain configuration.
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ChainConfig {
162    /// Whether the local chain is enabled.
163    #[serde(default = "default_true")]
164    pub enabled: bool,
165
166    /// Maximum events before auto-checkpoint.
167    #[serde(default = "default_checkpoint_interval", alias = "checkpointInterval")]
168    pub checkpoint_interval: u64,
169
170    /// Chain ID (0 = local node chain).
171    #[serde(default)]
172    pub chain_id: u32,
173
174    /// Path to the chain checkpoint file for persistence across restarts.
175    /// If `None`, defaults to `~/.clawft/chain/local.json`.
176    #[serde(
177        default,
178        skip_serializing_if = "Option::is_none",
179        alias = "checkpointPath"
180    )]
181    pub checkpoint_path: Option<String>,
182}
183
184fn default_true() -> bool {
185    true
186}
187fn default_checkpoint_interval() -> u64 {
188    1000
189}
190
191impl Default for ChainConfig {
192    fn default() -> Self {
193        Self {
194            enabled: true,
195            checkpoint_interval: default_checkpoint_interval(),
196            chain_id: 0,
197            checkpoint_path: None,
198        }
199    }
200}
201
202impl ChainConfig {
203    /// Returns the effective checkpoint path.
204    ///
205    /// If `checkpoint_path` is set, returns it. Otherwise falls back to
206    /// `~/.clawft/chain.json` (requires the `native` feature for `dirs`).
207    pub fn effective_checkpoint_path(&self) -> Option<String> {
208        if self.checkpoint_path.is_some() {
209            return self.checkpoint_path.clone();
210        }
211        #[cfg(feature = "native")]
212        {
213            dirs::home_dir().map(|h| {
214                h.join(".clawft")
215                    .join("chain.json")
216                    .to_string_lossy()
217                    .into_owned()
218            })
219        }
220        #[cfg(not(feature = "native"))]
221        {
222            None
223        }
224    }
225}
226
227/// Resource tree configuration.
228#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct ResourceTreeConfig {
230    /// Whether the resource tree is enabled.
231    #[serde(default = "default_true_rt")]
232    pub enabled: bool,
233
234    /// Path to checkpoint file (None = in-memory only).
235    #[serde(
236        default,
237        skip_serializing_if = "Option::is_none",
238        alias = "checkpointPath"
239    )]
240    pub checkpoint_path: Option<String>,
241}
242
243fn default_true_rt() -> bool {
244    true
245}
246
247impl Default for ResourceTreeConfig {
248    fn default() -> Self {
249        Self {
250            enabled: true,
251            checkpoint_path: None,
252        }
253    }
254}
255
256#[cfg(test)]
257mod tests {
258    use super::*;
259
260    #[test]
261    fn default_kernel_config() {
262        let cfg = KernelConfig::default();
263        assert!(!cfg.enabled);
264        assert_eq!(cfg.max_processes, 64);
265        assert_eq!(cfg.health_check_interval_secs, 30);
266    }
267
268    #[test]
269    fn deserialize_empty() {
270        let cfg: KernelConfig = serde_json::from_str("{}").unwrap();
271        assert!(!cfg.enabled);
272        assert_eq!(cfg.max_processes, 64);
273    }
274
275    #[test]
276    fn deserialize_camel_case() {
277        let json = r#"{"maxProcesses": 128, "healthCheckIntervalSecs": 15}"#;
278        let cfg: KernelConfig = serde_json::from_str(json).unwrap();
279        assert_eq!(cfg.max_processes, 128);
280        assert_eq!(cfg.health_check_interval_secs, 15);
281    }
282
283    #[test]
284    fn serde_roundtrip() {
285        let cfg = KernelConfig {
286            enabled: true,
287            max_processes: 256,
288            health_check_interval_secs: 10,
289            cluster: None,
290            chain: None,
291            resource_tree: None,
292        };
293        let json = serde_json::to_string(&cfg).unwrap();
294        let restored: KernelConfig = serde_json::from_str(&json).unwrap();
295        assert_eq!(restored.enabled, cfg.enabled);
296        assert_eq!(restored.max_processes, cfg.max_processes);
297    }
298}