Skip to main content

deepstrike_core/runtime/
event_log.rs

1//! Unified kernel OS event log — category taxonomy for observations and session events.
2//!
3//! Phase 5: every kernel decision is classifiable as `syscall` / `sched` / `mm` / `proc` / `ipc`
4//! so SDK session logs can be audited and replayed as a single OS event stream.
5//!
6//! Three-primitives lens (M4): every kernel event rolls up to exactly one of the three kernel
7//! primitives — **P1 syscall** (the adjudication trap), **P2 sched** (the TCB/task table + the
8//! scheduler), **P3 mm** (the handle table + paging). The five wire categories above are retained
9//! as finer-grained audit labels (a stable, shipped field), but `proc` and `ipc` are facets of the
10//! P2 scheduler — the process table *is* the task table, and signal disposition *feeds* the
11//! scheduler — so they project onto [`Primitive::Sched`]. See [`KernelEventCategory::primitive`].
12
13use serde::{Deserialize, Serialize};
14
15/// One of the three kernel primitives every OS event belongs to (the canonical decision planes).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum Primitive {
19    /// P1 — the single syscall trap (governance/capability/spawn/memory adjudication).
20    Syscall,
21    /// P2 — the TCB/task table + scheduler (budgets, lifecycle, process table, signal disposition).
22    Sched,
23    /// P3 — the handle table + paging (compression, page-in/out, renewal, long-term memory).
24    Mm,
25}
26
27impl Primitive {
28    pub fn label(self) -> &'static str {
29        match self {
30            Self::Syscall => "syscall",
31            Self::Sched => "sched",
32            Self::Mm => "mm",
33        }
34    }
35}
36
37/// Agent OS event category (kernel decision plane). Finer-grained than [`Primitive`]; retained as a
38/// stable wire field. Use [`KernelEventCategory::primitive`] for the three-primitives rollup.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
40#[serde(rename_all = "snake_case")]
41pub enum KernelEventCategory {
42    /// Governance gates, capability mount/unmount, tool approval.
43    Syscall,
44    /// Scheduling, budgets, suspend/resume, checkpoints, milestones, rollback.
45    Sched,
46    /// Working ↔ long-term memory: compression, page-in/out, renewal.
47    Mm,
48    /// Sub-agent process table lifecycle.
49    Proc,
50    /// Signal routing and disposition.
51    Ipc,
52}
53
54impl KernelEventCategory {
55    /// Roll this fine-grained category up to its kernel primitive. `Proc` and `Ipc` are facets of
56    /// the P2 scheduler (process table = task table; signals feed the scheduler).
57    pub fn primitive(self) -> Primitive {
58        match self {
59            Self::Syscall => Primitive::Syscall,
60            Self::Sched | Self::Proc | Self::Ipc => Primitive::Sched,
61            Self::Mm => Primitive::Mm,
62        }
63    }
64}
65
66/// The kernel primitive an observation/session `kind` belongs to (three-primitives rollup).
67pub fn primitive_for_kind(kind: &str) -> Primitive {
68    category_for_kind(kind).primitive()
69}
70
71/// Snake_case observation / session `kind` string.
72pub fn category_for_kind(kind: &str) -> KernelEventCategory {
73    match kind {
74        "tool_gated" | "capability_changed" => KernelEventCategory::Syscall,
75        "suspended"
76        | "resumed"
77        | "budget_exceeded"
78        | "checkpoint_taken"
79        | "rollbacked"
80        | "milestone_advanced"
81        | "milestone_blocked" => KernelEventCategory::Sched,
82        "compressed"
83        | "page_out"
84        | "page_in"
85        | "renewed"
86        | "context_renewed"
87        | "large_result_spooled" => KernelEventCategory::Mm,
88        "agent_process_changed" | "agent_spawned" | "workflow_batch_spawned"
89        | "workflow_completed" | "agent_preempted" => KernelEventCategory::Proc,
90        "signal_disposed" => KernelEventCategory::Ipc,
91        "memory_written" | "memory_queried" | "memory_validation_failed" => KernelEventCategory::Mm,
92        _ => KernelEventCategory::Sched,
93    }
94}
95
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn maps_observation_kinds_to_categories() {
103        assert_eq!(category_for_kind("tool_gated"), KernelEventCategory::Syscall);
104        assert_eq!(category_for_kind("page_out"), KernelEventCategory::Mm);
105        assert_eq!(category_for_kind("agent_process_changed"), KernelEventCategory::Proc);
106        assert_eq!(category_for_kind("signal_disposed"), KernelEventCategory::Ipc);
107        assert_eq!(category_for_kind("suspended"), KernelEventCategory::Sched);
108    }
109
110    #[test]
111    fn categories_roll_up_to_three_primitives() {
112        // Proc and Ipc are facets of the P2 scheduler.
113        assert_eq!(KernelEventCategory::Syscall.primitive(), Primitive::Syscall);
114        assert_eq!(KernelEventCategory::Sched.primitive(), Primitive::Sched);
115        assert_eq!(KernelEventCategory::Proc.primitive(), Primitive::Sched);
116        assert_eq!(KernelEventCategory::Ipc.primitive(), Primitive::Sched);
117        assert_eq!(KernelEventCategory::Mm.primitive(), Primitive::Mm);
118    }
119
120    #[test]
121    fn every_kernel_observation_kind_maps_to_a_primitive() {
122        // syscall trap, scheduler, and paging cover the entire ABI surface — no orphans.
123        assert_eq!(primitive_for_kind("agent_process_changed"), Primitive::Sched);
124        assert_eq!(primitive_for_kind("signal_disposed"), Primitive::Sched);
125        assert_eq!(primitive_for_kind("tool_gated"), Primitive::Syscall);
126        assert_eq!(primitive_for_kind("page_out"), Primitive::Mm);
127    }
128}