ggen_dod/
kernel.rs

1//! μ-Kernel: Deterministic decision engine with timing guarantees
2//!
3//! The kernel is the beating heart of ggen. It takes observations (O),
4//! contracts (Σ), and invariants (Q), and produces deterministic actions (A).
5//! Every decision is:
6//! - Deterministic: Same input always produces identical output
7//! - Idempotent: Applying twice has same effect as applying once (when declared)
8//! - Timed: Execution must complete within τ ≤ 8ms
9//! - Traced: Full provenance recorded in Γ
10
11use crate::error::DoDResult;
12use crate::observation::Observation;
13use crate::timing::TimingMeasurement;
14use serde::{Deserialize, Serialize};
15use std::collections::BTreeMap;
16use std::time::Instant;
17use uuid::Uuid;
18
19/// Unique identifier for kernel actions
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21pub struct KernelActionId(Uuid);
22
23impl KernelActionId {
24    /// Generate a new action ID
25    pub fn new() -> Self {
26        Self(Uuid::new_v4())
27    }
28}
29
30impl Default for KernelActionId {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl std::fmt::Display for KernelActionId {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        write!(f, "{}", self.0)
39    }
40}
41
42/// Type of action the kernel can emit
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub enum ActionType {
45    /// Schema evolution (ΔΣ)
46    SchemaEvolution,
47    /// Projection update (ΔΠ)
48    ProjectionUpdate,
49    /// Invariant adjustment (ΔQ)
50    InvariantAdjustment,
51    /// Marketplace action (promote, deprecate, reroute)
52    MarketplaceAction,
53    /// Autonomic loop action (monitor, analyze, plan, execute)
54    AutonomicAction,
55    /// State update
56    StateUpdate,
57    /// Custom action
58    Custom(String),
59}
60
61/// Idempotence mode of an action
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub enum IdempotenceMode {
64    /// μ ∘ μ = μ (safe to apply multiple times)
65    Idempotent,
66    /// Not idempotent (side effects on each application)
67    NonIdempotent,
68    /// Idempotent only in specific mode (e.g., learning-only)
69    ConditionallyIdempotent(String),
70}
71
72/// An action emitted by the kernel
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct KernelAction {
75    /// Unique action ID
76    id: KernelActionId,
77    /// Type of action
78    action_type: ActionType,
79    /// Action payload
80    payload: serde_json::Value,
81    /// Idempotence mode
82    idempotence: IdempotenceMode,
83    /// Tenant this action belongs to
84    tenant_id: String,
85    /// Observations that triggered this action
86    triggering_observations: Vec<crate::observation::ObservationId>,
87}
88
89impl KernelAction {
90    /// Create a new kernel action
91    pub fn new(
92        action_type: ActionType, payload: serde_json::Value, tenant_id: impl Into<String>,
93    ) -> Self {
94        Self {
95            id: KernelActionId::new(),
96            action_type,
97            payload,
98            idempotence: IdempotenceMode::NonIdempotent,
99            tenant_id: tenant_id.into(),
100            triggering_observations: Vec::new(),
101        }
102    }
103
104    /// Get action ID
105    pub fn id(&self) -> KernelActionId {
106        self.id
107    }
108
109    /// Get action type
110    pub fn action_type(&self) -> &ActionType {
111        &self.action_type
112    }
113
114    /// Get payload
115    pub fn payload(&self) -> &serde_json::Value {
116        &self.payload
117    }
118
119    /// Set idempotence mode
120    pub fn with_idempotence(mut self, mode: IdempotenceMode) -> Self {
121        self.idempotence = mode;
122        self
123    }
124
125    /// Mark as idempotent
126    pub fn idempotent(self) -> Self {
127        self.with_idempotence(IdempotenceMode::Idempotent)
128    }
129
130    /// Add triggering observation
131    pub fn with_triggering_observation(
132        mut self, obs_id: crate::observation::ObservationId,
133    ) -> Self {
134        self.triggering_observations.push(obs_id);
135        self
136    }
137
138    /// Get idempotence mode
139    pub fn idempotence(&self) -> IdempotenceMode {
140        self.idempotence.clone()
141    }
142
143    /// Get tenant ID
144    pub fn tenant_id(&self) -> &str {
145        &self.tenant_id
146    }
147
148    /// Get triggering observations
149    pub fn triggering_observations(&self) -> &[crate::observation::ObservationId] {
150        &self.triggering_observations
151    }
152}
153
154/// Kernel decision (μ(O) → A)
155#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct KernelDecision {
157    /// Unique decision ID
158    decision_id: String,
159    /// Observations that led to this decision
160    observations: Vec<Observation>,
161    /// Actions decided
162    actions: Vec<KernelAction>,
163    /// Timing measurement
164    timing: TimingMeasurement,
165    /// Whether this decision is deterministic replay
166    is_replay: bool,
167    /// Determinism hash (hash of (O, Σ*, Q, Γ) → hash(A))
168    determinism_hash: Option<String>,
169}
170
171impl KernelDecision {
172    /// Create a new kernel decision
173    pub fn new() -> Self {
174        Self {
175            decision_id: Uuid::new_v4().to_string(),
176            observations: Vec::new(),
177            actions: Vec::new(),
178            timing: TimingMeasurement::new(),
179            is_replay: false,
180            determinism_hash: None,
181        }
182    }
183
184    /// Add an observation
185    pub fn with_observation(mut self, obs: Observation) -> Self {
186        self.observations.push(obs);
187        self
188    }
189
190    /// Add an action
191    pub fn with_action(mut self, action: KernelAction) -> Self {
192        self.actions.push(action);
193        self
194    }
195
196    /// Get decision ID
197    pub fn decision_id(&self) -> &str {
198        &self.decision_id
199    }
200
201    /// Get observations
202    pub fn observations(&self) -> &[Observation] {
203        &self.observations
204    }
205
206    /// Get actions
207    pub fn actions(&self) -> &[KernelAction] {
208        &self.actions
209    }
210
211    /// Get timing
212    pub fn timing(&self) -> &TimingMeasurement {
213        &self.timing
214    }
215
216    /// Is this a replay?
217    pub fn is_replay(&self) -> bool {
218        self.is_replay
219    }
220
221    /// Mark as replay
222    pub fn as_replay(mut self) -> Self {
223        self.is_replay = true;
224        self
225    }
226
227    /// Set determinism hash
228    pub fn with_determinism_hash(mut self, hash: String) -> Self {
229        self.determinism_hash = Some(hash);
230        self
231    }
232
233    /// Get determinism hash
234    pub fn determinism_hash(&self) -> Option<&str> {
235        self.determinism_hash.as_deref()
236    }
237}
238
239impl Default for KernelDecision {
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245/// The μ-Kernel: deterministic decision engine
246pub struct Kernel {
247    /// Current ontology (Σ*)
248    contracts: BTreeMap<String, serde_json::Value>,
249    /// Invariant rules (Q)
250    invariants: BTreeMap<String, String>,
251    /// Execution history for determinism checking
252    execution_history: BTreeMap<String, KernelDecision>,
253}
254
255impl Kernel {
256    /// Create a new kernel
257    pub fn new() -> Self {
258        Self {
259            contracts: BTreeMap::new(),
260            invariants: BTreeMap::new(),
261            execution_history: BTreeMap::new(),
262        }
263    }
264
265    /// Update schema (ΔΣ)
266    pub fn update_schema(
267        &mut self, name: impl Into<String>, schema: serde_json::Value,
268    ) -> DoDResult<()> {
269        self.contracts.insert(name.into(), schema);
270        Ok(())
271    }
272
273    /// Add invariant
274    pub fn add_invariant(
275        &mut self, name: impl Into<String>, constraint: impl Into<String>,
276    ) -> DoDResult<()> {
277        self.invariants.insert(name.into(), constraint.into());
278        Ok(())
279    }
280
281    /// Execute decision with timing enforcement
282    pub fn decide(
283        &mut self, observations: Vec<Observation>, tenant_id: &str,
284    ) -> DoDResult<KernelDecision> {
285        let start = Instant::now();
286        let _decision_start = TimingMeasurement::new();
287
288        // Validate inputs
289        if observations.is_empty() {
290            return Err(crate::error::DoDError::KernelDecision(
291                "no observations provided".to_string(),
292            ));
293        }
294
295        // Tenant isolation check
296        let tenant = observations[0].tenant_id();
297        if !observations.iter().all(|o| o.tenant_id() == tenant) {
298            return Err(crate::error::DoDError::TenantIsolation(
299                "observations from different tenants".to_string(),
300            ));
301        }
302
303        if tenant != tenant_id {
304            return Err(crate::error::DoDError::TenantIsolation(
305                "tenant mismatch".to_string(),
306            ));
307        }
308
309        // Create decision
310        let mut decision = KernelDecision::new();
311        for obs in observations {
312            decision = decision.with_observation(obs);
313        }
314
315        // Derive actions from observations
316        // This is where the actual μ logic would go
317        let actions = self.derive_actions(&decision)?;
318        for action in actions {
319            decision = decision.with_action(action);
320        }
321
322        // Measure timing
323        let elapsed = start.elapsed().as_millis() as u64;
324        let _timing = TimingMeasurement::new().finished(elapsed);
325
326        // Check timing constraint
327        if elapsed > crate::constants::KERNEL_MAX_TIME_MS {
328            return Err(crate::error::DoDError::TimingViolation {
329                expected: crate::constants::KERNEL_MAX_TIME_MS,
330                actual: elapsed,
331            });
332        }
333
334        let hash = self.compute_determinism_hash(&decision);
335        decision = decision.with_determinism_hash(hash);
336
337        // Store in history
338        self.execution_history
339            .insert(decision.decision_id().to_string(), decision.clone());
340
341        Ok(decision)
342    }
343
344    /// Derive actions from observations
345    fn derive_actions(&self, decision: &KernelDecision) -> DoDResult<Vec<KernelAction>> {
346        let mut actions = Vec::new();
347
348        // Analyze observations and create corresponding actions
349        for obs in decision.observations() {
350            let action = match obs.obs_type() {
351                crate::observation::ObservationType::Metric(_) => KernelAction::new(
352                    ActionType::StateUpdate,
353                    serde_json::json!({"type": "metric_update"}),
354                    obs.tenant_id(),
355                ),
356                crate::observation::ObservationType::Anomaly(_) => KernelAction::new(
357                    ActionType::AutonomicAction,
358                    serde_json::json!({"type": "anomaly_response"}),
359                    obs.tenant_id(),
360                )
361                .idempotent(),
362                crate::observation::ObservationType::SLOBreach(_) => KernelAction::new(
363                    ActionType::SchemaEvolution,
364                    serde_json::json!({"type": "slo_response"}),
365                    obs.tenant_id(),
366                ),
367                _ => KernelAction::new(
368                    ActionType::Custom("unknown".to_string()),
369                    serde_json::json!({"observation": obs.data()}),
370                    obs.tenant_id(),
371                ),
372            }
373            .with_triggering_observation(obs.id());
374
375            actions.push(action);
376        }
377
378        Ok(actions)
379    }
380
381    /// Compute determinism hash (proves μ is deterministic)
382    fn compute_determinism_hash(&self, decision: &KernelDecision) -> String {
383        use sha2::Digest;
384        let mut hasher = sha2::Sha256::new();
385
386        // Hash all observations (O)
387        for obs in decision.observations() {
388            hasher.update(obs.id().to_string());
389            hasher.update(serde_json::to_string(&obs.data()).unwrap_or_default());
390        }
391
392        // Hash all contracts (Σ*)
393        for (name, schema) in &self.contracts {
394            hasher.update(name);
395            hasher.update(schema.to_string());
396        }
397
398        // Hash all invariants (Q)
399        for (name, constraint) in &self.invariants {
400            hasher.update(name);
401            hasher.update(constraint);
402        }
403
404        // Hash all actions (A)
405        for action in decision.actions() {
406            hasher.update(action.id().to_string());
407            hasher.update(action.payload().to_string());
408        }
409
410        hex::encode(hasher.finalize())
411    }
412
413    /// Verify determinism: replay should produce identical hash
414    pub fn verify_determinism(
415        &self, original_decision: &KernelDecision, replay_decision: &KernelDecision,
416    ) -> DoDResult<()> {
417        let original_hash = original_decision
418            .determinism_hash()
419            .ok_or_else(|| crate::error::DoDError::DeterminismViolation)?;
420
421        let replay_hash = replay_decision
422            .determinism_hash()
423            .ok_or_else(|| crate::error::DoDError::DeterminismViolation)?;
424
425        if original_hash != replay_hash {
426            return Err(crate::error::DoDError::DeterminismViolation);
427        }
428
429        Ok(())
430    }
431
432    /// Get execution history
433    pub fn execution_history(&self) -> &BTreeMap<String, KernelDecision> {
434        &self.execution_history
435    }
436}
437
438impl Default for Kernel {
439    fn default() -> Self {
440        Self::new()
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447
448    #[test]
449    fn test_kernel_creation() {
450        let kernel = Kernel::new();
451        assert!(kernel.contracts.is_empty());
452        assert!(kernel.invariants.is_empty());
453    }
454
455    #[test]
456    fn test_kernel_decision() -> DoDResult<()> {
457        let mut kernel = Kernel::new();
458        let obs = crate::observation::Observation::new(
459            crate::observation::ObservationType::Metric(crate::observation::MetricType::Latency),
460            serde_json::json!({"value": 42}),
461            "test",
462            "1.0",
463            "tenant-1",
464        )?;
465
466        let decision = kernel.decide(vec![obs], "tenant-1")?;
467        assert!(!decision.actions().is_empty());
468        Ok(())
469    }
470
471    #[test]
472    fn test_kernel_timing() -> DoDResult<()> {
473        let mut kernel = Kernel::new();
474        let obs = crate::observation::Observation::new(
475            crate::observation::ObservationType::Metric(crate::observation::MetricType::Latency),
476            serde_json::json!({"value": 1}),
477            "test",
478            "1.0",
479            "tenant-1",
480        )?;
481
482        let decision = kernel.decide(vec![obs], "tenant-1")?;
483        assert!(decision.timing().elapsed_ms() < crate::constants::KERNEL_MAX_TIME_MS);
484        Ok(())
485    }
486}