Skip to main content

clawft_kernel/
console.rs

1//! Kernel console: boot event types and output formatting.
2//!
3//! Provides [`BootEvent`], [`BootPhase`], and [`LogLevel`] types for
4//! recording and displaying kernel boot output. The interactive REPL
5//! loop is stubbed (requires complex stdin handling); only the event
6//! types and output formatting are implemented.
7
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10
11/// Phase of the boot sequence.
12#[non_exhaustive]
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum BootPhase {
15    /// Pre-boot initialization.
16    Init,
17    /// Loading configuration.
18    Config,
19    /// Registering system services.
20    Services,
21    /// Loading resource tree from DAG.
22    ResourceTree,
23    /// Spawning service agents.
24    Agents,
25    /// Network service discovery.
26    Network,
27    /// ECC cognitive substrate initialization.
28    Ecc,
29    /// Boot complete, ready for commands.
30    Ready,
31}
32
33impl BootPhase {
34    /// Short tag string for console output (e.g. `[INIT]`).
35    pub fn tag(&self) -> &'static str {
36        match self {
37            BootPhase::Init => "INIT",
38            BootPhase::Config => "CONFIG",
39            BootPhase::Services => "SERVICES",
40            BootPhase::ResourceTree => "TREE",
41            BootPhase::Agents => "AGENTS",
42            BootPhase::Network => "NETWORK",
43            BootPhase::Ecc => "ECC",
44            BootPhase::Ready => "READY",
45        }
46    }
47}
48
49impl std::fmt::Display for BootPhase {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        write!(f, "{}", self.tag())
52    }
53}
54
55/// Log level for boot events.
56#[non_exhaustive]
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
58pub enum LogLevel {
59    /// Debug-level messages (not shown in normal boot output).
60    Debug,
61    /// Informational messages (standard boot output).
62    Info,
63    /// Warning messages.
64    Warn,
65    /// Error messages.
66    Error,
67}
68
69/// A single boot event recorded during kernel startup.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct BootEvent {
72    /// When the event occurred.
73    pub timestamp: DateTime<Utc>,
74    /// Which boot phase generated the event.
75    pub phase: BootPhase,
76    /// Human-readable event message.
77    pub message: String,
78    /// Severity level.
79    pub level: LogLevel,
80}
81
82impl BootEvent {
83    /// Create a new info-level boot event.
84    pub fn info(phase: BootPhase, message: impl Into<String>) -> Self {
85        Self {
86            timestamp: Utc::now(),
87            phase,
88            message: message.into(),
89            level: LogLevel::Info,
90        }
91    }
92
93    /// Create a new warning-level boot event.
94    pub fn warn(phase: BootPhase, message: impl Into<String>) -> Self {
95        Self {
96            timestamp: Utc::now(),
97            phase,
98            message: message.into(),
99            level: LogLevel::Warn,
100        }
101    }
102
103    /// Create a new error-level boot event.
104    pub fn error(phase: BootPhase, message: impl Into<String>) -> Self {
105        Self {
106            timestamp: Utc::now(),
107            phase,
108            message: message.into(),
109            level: LogLevel::Error,
110        }
111    }
112
113    /// Format this event for console display.
114    ///
115    /// Example: `  [INIT]      WeftOS v0.1.0 booting...`
116    pub fn format_line(&self) -> String {
117        let tag = self.phase.tag();
118        format!("  [{tag:<10}] {}", self.message)
119    }
120}
121
122/// Boot log: a recorded sequence of boot events.
123///
124/// Used to replay boot output when attaching to a running kernel
125/// or for diagnostics.
126#[derive(Debug, Clone, Default)]
127pub struct BootLog {
128    events: Vec<BootEvent>,
129}
130
131impl BootLog {
132    /// Create an empty boot log.
133    pub fn new() -> Self {
134        Self { events: Vec::new() }
135    }
136
137    /// Record a boot event.
138    pub fn push(&mut self, event: BootEvent) {
139        self.events.push(event);
140    }
141
142    /// Get all recorded events.
143    pub fn events(&self) -> &[BootEvent] {
144        &self.events
145    }
146
147    /// Format all events for console display.
148    pub fn format_all(&self) -> String {
149        let mut output = String::new();
150        for event in &self.events {
151            if event.level == LogLevel::Debug {
152                continue;
153            }
154            output.push_str(&event.format_line());
155            output.push('\n');
156        }
157        output
158    }
159
160    /// Get the number of recorded events.
161    pub fn len(&self) -> usize {
162        self.events.len()
163    }
164
165    /// Check whether the log is empty.
166    pub fn is_empty(&self) -> bool {
167        self.events.is_empty()
168    }
169}
170
171/// Default capacity of the kernel event ring buffer.
172const DEFAULT_EVENT_LOG_CAPACITY: usize = 1024;
173
174/// Thread-safe ring buffer for kernel runtime events.
175///
176/// Captures boot events and any post-boot events (service starts/stops,
177/// agent spawns, health checks, errors). Holds at most `capacity` events
178/// — when full, the oldest event is evicted.
179///
180/// Used by the daemon to serve `kernel.logs` RPC requests.
181pub struct KernelEventLog {
182    events: std::sync::Mutex<std::collections::VecDeque<BootEvent>>,
183    capacity: usize,
184}
185
186impl KernelEventLog {
187    /// Create a new event log with the default capacity (1024).
188    pub fn new() -> Self {
189        Self::with_capacity(DEFAULT_EVENT_LOG_CAPACITY)
190    }
191
192    /// Create a new event log with a specific capacity.
193    pub fn with_capacity(capacity: usize) -> Self {
194        Self {
195            events: std::sync::Mutex::new(std::collections::VecDeque::with_capacity(capacity)),
196            capacity,
197        }
198    }
199
200    /// Push an event into the ring buffer.
201    pub fn push(&self, event: BootEvent) {
202        let mut events = self.events.lock().unwrap();
203        if events.len() >= self.capacity {
204            events.pop_front();
205        }
206        events.push_back(event);
207    }
208
209    /// Push a simple info event with a source tag and message.
210    pub fn info(&self, source: &str, message: impl Into<String>) {
211        self.push(BootEvent {
212            timestamp: Utc::now(),
213            phase: BootPhase::Ready, // post-boot events use Ready phase
214            message: format!("[{source}] {}", message.into()),
215            level: LogLevel::Info,
216        });
217    }
218
219    /// Push a warning event.
220    pub fn warn(&self, source: &str, message: impl Into<String>) {
221        self.push(BootEvent {
222            timestamp: Utc::now(),
223            phase: BootPhase::Ready,
224            message: format!("[{source}] {}", message.into()),
225            level: LogLevel::Warn,
226        });
227    }
228
229    /// Push an error event.
230    pub fn error(&self, source: &str, message: impl Into<String>) {
231        self.push(BootEvent {
232            timestamp: Utc::now(),
233            phase: BootPhase::Ready,
234            message: format!("[{source}] {}", message.into()),
235            level: LogLevel::Error,
236        });
237    }
238
239    /// Push an info event and optionally append to the local chain.
240    #[cfg(feature = "exochain")]
241    pub fn info_with_chain(
242        &self,
243        source: &str,
244        message: impl Into<String>,
245        chain: Option<&crate::chain::ChainManager>,
246    ) {
247        let msg = message.into();
248        self.info(source, &msg);
249        if let Some(cm) = chain {
250            cm.append(source, "log.info", Some(serde_json::json!({ "message": msg })));
251        }
252    }
253
254    /// Ingest all events from a BootLog (used to seed boot events).
255    pub fn ingest_boot_log(&self, boot_log: &BootLog) {
256        for event in boot_log.events() {
257            self.push(event.clone());
258        }
259    }
260
261    /// Get the last `n` events (or all if `n` is 0 or exceeds count).
262    pub fn tail(&self, n: usize) -> Vec<BootEvent> {
263        let events = self.events.lock().unwrap();
264        if n == 0 || n >= events.len() {
265            events.iter().cloned().collect()
266        } else {
267            events.iter().skip(events.len() - n).cloned().collect()
268        }
269    }
270
271    /// Get all events matching a minimum log level.
272    pub fn filter_level(&self, min_level: &LogLevel, n: usize) -> Vec<BootEvent> {
273        let events = self.events.lock().unwrap();
274        let level_rank = |l: &LogLevel| -> u8 {
275            match l {
276                LogLevel::Debug => 0,
277                LogLevel::Info => 1,
278                LogLevel::Warn => 2,
279                LogLevel::Error => 3,
280            }
281        };
282        let min_rank = level_rank(min_level);
283        let filtered: Vec<BootEvent> = events
284            .iter()
285            .filter(|e| level_rank(&e.level) >= min_rank)
286            .cloned()
287            .collect();
288        if n == 0 || n >= filtered.len() {
289            filtered
290        } else {
291            filtered[filtered.len() - n..].to_vec()
292        }
293    }
294
295    /// Current number of events in the buffer.
296    pub fn len(&self) -> usize {
297        self.events.lock().unwrap().len()
298    }
299
300    /// Check if the buffer is empty.
301    pub fn is_empty(&self) -> bool {
302        self.events.lock().unwrap().is_empty()
303    }
304}
305
306impl Default for KernelEventLog {
307    fn default() -> Self {
308        Self::new()
309    }
310}
311
312impl std::fmt::Debug for KernelEventLog {
313    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
314        let len = self.len();
315        f.debug_struct("KernelEventLog")
316            .field("count", &len)
317            .field("capacity", &self.capacity)
318            .finish()
319    }
320}
321
322/// Format the boot banner header.
323pub fn boot_banner() -> String {
324    let mut output = String::new();
325    output.push_str("\n  WeftOS v0.1.0\n");
326    output.push_str("  ");
327    output.push_str(&"-".repeat(45));
328    output.push('\n');
329    output
330}
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    #[test]
337    fn boot_phase_tags() {
338        assert_eq!(BootPhase::Init.tag(), "INIT");
339        assert_eq!(BootPhase::Config.tag(), "CONFIG");
340        assert_eq!(BootPhase::Services.tag(), "SERVICES");
341        assert_eq!(BootPhase::ResourceTree.tag(), "TREE");
342        assert_eq!(BootPhase::Agents.tag(), "AGENTS");
343        assert_eq!(BootPhase::Network.tag(), "NETWORK");
344        assert_eq!(BootPhase::Ready.tag(), "READY");
345    }
346
347    #[test]
348    fn boot_event_info() {
349        let event = BootEvent::info(BootPhase::Init, "WeftOS v0.1.0 booting...");
350        assert_eq!(event.phase, BootPhase::Init);
351        assert_eq!(event.level, LogLevel::Info);
352        assert_eq!(event.message, "WeftOS v0.1.0 booting...");
353    }
354
355    #[test]
356    fn boot_event_format_line() {
357        let event = BootEvent::info(BootPhase::Init, "PID 0 (kernel)");
358        let line = event.format_line();
359        assert!(line.contains("[INIT"));
360        assert!(line.contains("PID 0 (kernel)"));
361    }
362
363    #[test]
364    fn boot_log_push_and_format() {
365        let mut log = BootLog::new();
366        log.push(BootEvent::info(BootPhase::Init, "booting..."));
367        log.push(BootEvent::info(BootPhase::Config, "config loaded"));
368        log.push(BootEvent::info(BootPhase::Ready, "ready"));
369
370        assert_eq!(log.len(), 3);
371        let formatted = log.format_all();
372        assert!(formatted.contains("booting..."));
373        assert!(formatted.contains("config loaded"));
374        assert!(formatted.contains("ready"));
375    }
376
377    #[test]
378    fn boot_log_skips_debug() {
379        let mut log = BootLog::new();
380        log.push(BootEvent {
381            timestamp: Utc::now(),
382            phase: BootPhase::Init,
383            message: "debug msg".into(),
384            level: LogLevel::Debug,
385        });
386        log.push(BootEvent::info(BootPhase::Init, "info msg"));
387
388        let formatted = log.format_all();
389        assert!(!formatted.contains("debug msg"));
390        assert!(formatted.contains("info msg"));
391    }
392
393    #[test]
394    fn boot_log_empty() {
395        let log = BootLog::new();
396        assert!(log.is_empty());
397        assert_eq!(log.len(), 0);
398        assert!(log.format_all().is_empty());
399    }
400
401    #[test]
402    fn boot_banner_format() {
403        let banner = boot_banner();
404        assert!(banner.contains("WeftOS v0.1.0"));
405        assert!(banner.contains("---"));
406    }
407
408    #[test]
409    fn boot_event_serde() {
410        let event = BootEvent::info(BootPhase::Services, "[OK] message-bus");
411        let json = serde_json::to_string(&event).unwrap();
412        let restored: BootEvent = serde_json::from_str(&json).unwrap();
413        assert_eq!(restored.phase, BootPhase::Services);
414        assert_eq!(restored.message, "[OK] message-bus");
415    }
416
417    #[test]
418    fn boot_event_warn_and_error() {
419        let warn_event = BootEvent::warn(BootPhase::Services, "slow start");
420        assert_eq!(warn_event.level, LogLevel::Warn);
421
422        let err_event = BootEvent::error(BootPhase::Services, "failed");
423        assert_eq!(err_event.level, LogLevel::Error);
424    }
425
426    // ── KernelEventLog tests ──────────────────────────────────
427
428    #[test]
429    fn event_log_push_and_tail() {
430        let log = KernelEventLog::new();
431        log.info("test", "first");
432        log.info("test", "second");
433        log.warn("test", "third");
434
435        assert_eq!(log.len(), 3);
436
437        let all = log.tail(0);
438        assert_eq!(all.len(), 3);
439        assert!(all[0].message.contains("first"));
440
441        let last_two = log.tail(2);
442        assert_eq!(last_two.len(), 2);
443        assert!(last_two[0].message.contains("second"));
444    }
445
446    #[test]
447    fn event_log_ring_buffer_evicts() {
448        let log = KernelEventLog::with_capacity(3);
449        log.info("a", "1");
450        log.info("b", "2");
451        log.info("c", "3");
452        log.info("d", "4"); // evicts "1"
453
454        assert_eq!(log.len(), 3);
455        let all = log.tail(0);
456        assert!(all[0].message.contains("[b] 2"));
457        assert!(all[2].message.contains("[d] 4"));
458    }
459
460    #[test]
461    fn event_log_filter_level() {
462        let log = KernelEventLog::new();
463        log.info("test", "info msg");
464        log.warn("test", "warn msg");
465        log.error("test", "error msg");
466
467        let warns_and_above = log.filter_level(&LogLevel::Warn, 0);
468        assert_eq!(warns_and_above.len(), 2);
469
470        let errors_only = log.filter_level(&LogLevel::Error, 0);
471        assert_eq!(errors_only.len(), 1);
472    }
473
474    #[test]
475    fn event_log_ingest_boot_log() {
476        let mut boot_log = BootLog::new();
477        boot_log.push(BootEvent::info(BootPhase::Init, "booting"));
478        boot_log.push(BootEvent::info(BootPhase::Ready, "ready"));
479
480        let event_log = KernelEventLog::new();
481        event_log.ingest_boot_log(&boot_log);
482
483        assert_eq!(event_log.len(), 2);
484        let events = event_log.tail(0);
485        assert!(events[0].message.contains("booting"));
486    }
487
488    #[test]
489    fn event_log_empty() {
490        let log = KernelEventLog::new();
491        assert!(log.is_empty());
492        assert_eq!(log.len(), 0);
493        assert!(log.tail(10).is_empty());
494    }
495}