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