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        | "budget_usage_reported"
79        | "checkpoint_taken"
80        | "rollbacked"
81        | "milestone_advanced"
82        | "milestone_blocked" => KernelEventCategory::Sched,
83        "compressed"
84        | "page_out"
85        | "page_in"
86        | "renewed"
87        | "context_renewed"
88        | "large_result_spooled" => KernelEventCategory::Mm,
89        "agent_process_changed"
90        | "agent_spawned"
91        | "workflow_batch_spawned"
92        | "workflow_completed"
93        | "agent_preempted" => KernelEventCategory::Proc,
94        "signal_delivery_disposed" => KernelEventCategory::Ipc,
95        "memory_written" | "memory_queried" | "memory_validation_failed" => KernelEventCategory::Mm,
96        _ => KernelEventCategory::Sched,
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn maps_observation_kinds_to_categories() {
106        assert_eq!(
107            category_for_kind("tool_gated"),
108            KernelEventCategory::Syscall
109        );
110        assert_eq!(category_for_kind("page_out"), KernelEventCategory::Mm);
111        assert_eq!(
112            category_for_kind("agent_process_changed"),
113            KernelEventCategory::Proc
114        );
115        assert_eq!(
116            category_for_kind("signal_delivery_disposed"),
117            KernelEventCategory::Ipc
118        );
119        assert_eq!(category_for_kind("suspended"), KernelEventCategory::Sched);
120    }
121
122    #[test]
123    fn categories_roll_up_to_three_primitives() {
124        // Proc and Ipc are facets of the P2 scheduler.
125        assert_eq!(KernelEventCategory::Syscall.primitive(), Primitive::Syscall);
126        assert_eq!(KernelEventCategory::Sched.primitive(), Primitive::Sched);
127        assert_eq!(KernelEventCategory::Proc.primitive(), Primitive::Sched);
128        assert_eq!(KernelEventCategory::Ipc.primitive(), Primitive::Sched);
129        assert_eq!(KernelEventCategory::Mm.primitive(), Primitive::Mm);
130    }
131
132    #[test]
133    fn every_kernel_observation_kind_maps_to_a_primitive() {
134        // syscall trap, scheduler, and paging cover the entire ABI surface — no orphans.
135        assert_eq!(
136            primitive_for_kind("agent_process_changed"),
137            Primitive::Sched
138        );
139        assert_eq!(
140            primitive_for_kind("signal_delivery_disposed"),
141            Primitive::Sched
142        );
143        assert_eq!(primitive_for_kind("tool_gated"), Primitive::Syscall);
144        assert_eq!(primitive_for_kind("page_out"), Primitive::Mm);
145    }
146}