1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
21pub struct KernelActionId(Uuid);
22
23impl KernelActionId {
24 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44pub enum ActionType {
45 SchemaEvolution,
47 ProjectionUpdate,
49 InvariantAdjustment,
51 MarketplaceAction,
53 AutonomicAction,
55 StateUpdate,
57 Custom(String),
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub enum IdempotenceMode {
64 Idempotent,
66 NonIdempotent,
68 ConditionallyIdempotent(String),
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct KernelAction {
75 id: KernelActionId,
77 action_type: ActionType,
79 payload: serde_json::Value,
81 idempotence: IdempotenceMode,
83 tenant_id: String,
85 triggering_observations: Vec<crate::observation::ObservationId>,
87}
88
89impl KernelAction {
90 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 pub fn id(&self) -> KernelActionId {
106 self.id
107 }
108
109 pub fn action_type(&self) -> &ActionType {
111 &self.action_type
112 }
113
114 pub fn payload(&self) -> &serde_json::Value {
116 &self.payload
117 }
118
119 pub fn with_idempotence(mut self, mode: IdempotenceMode) -> Self {
121 self.idempotence = mode;
122 self
123 }
124
125 pub fn idempotent(self) -> Self {
127 self.with_idempotence(IdempotenceMode::Idempotent)
128 }
129
130 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 pub fn idempotence(&self) -> IdempotenceMode {
140 self.idempotence.clone()
141 }
142
143 pub fn tenant_id(&self) -> &str {
145 &self.tenant_id
146 }
147
148 pub fn triggering_observations(&self) -> &[crate::observation::ObservationId] {
150 &self.triggering_observations
151 }
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct KernelDecision {
157 decision_id: String,
159 observations: Vec<Observation>,
161 actions: Vec<KernelAction>,
163 timing: TimingMeasurement,
165 is_replay: bool,
167 determinism_hash: Option<String>,
169}
170
171impl KernelDecision {
172 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 pub fn with_observation(mut self, obs: Observation) -> Self {
186 self.observations.push(obs);
187 self
188 }
189
190 pub fn with_action(mut self, action: KernelAction) -> Self {
192 self.actions.push(action);
193 self
194 }
195
196 pub fn decision_id(&self) -> &str {
198 &self.decision_id
199 }
200
201 pub fn observations(&self) -> &[Observation] {
203 &self.observations
204 }
205
206 pub fn actions(&self) -> &[KernelAction] {
208 &self.actions
209 }
210
211 pub fn timing(&self) -> &TimingMeasurement {
213 &self.timing
214 }
215
216 pub fn is_replay(&self) -> bool {
218 self.is_replay
219 }
220
221 pub fn as_replay(mut self) -> Self {
223 self.is_replay = true;
224 self
225 }
226
227 pub fn with_determinism_hash(mut self, hash: String) -> Self {
229 self.determinism_hash = Some(hash);
230 self
231 }
232
233 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
245pub struct Kernel {
247 contracts: BTreeMap<String, serde_json::Value>,
249 invariants: BTreeMap<String, String>,
251 execution_history: BTreeMap<String, KernelDecision>,
253}
254
255impl Kernel {
256 pub fn new() -> Self {
258 Self {
259 contracts: BTreeMap::new(),
260 invariants: BTreeMap::new(),
261 execution_history: BTreeMap::new(),
262 }
263 }
264
265 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 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 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 if observations.is_empty() {
290 return Err(crate::error::DoDError::KernelDecision(
291 "no observations provided".to_string(),
292 ));
293 }
294
295 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 let mut decision = KernelDecision::new();
311 for obs in observations {
312 decision = decision.with_observation(obs);
313 }
314
315 let actions = self.derive_actions(&decision)?;
318 for action in actions {
319 decision = decision.with_action(action);
320 }
321
322 let elapsed = start.elapsed().as_millis() as u64;
324 let _timing = TimingMeasurement::new().finished(elapsed);
325
326 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 self.execution_history
339 .insert(decision.decision_id().to_string(), decision.clone());
340
341 Ok(decision)
342 }
343
344 fn derive_actions(&self, decision: &KernelDecision) -> DoDResult<Vec<KernelAction>> {
346 let mut actions = Vec::new();
347
348 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 fn compute_determinism_hash(&self, decision: &KernelDecision) -> String {
383 use sha2::Digest;
384 let mut hasher = sha2::Sha256::new();
385
386 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 for (name, schema) in &self.contracts {
394 hasher.update(name);
395 hasher.update(schema.to_string());
396 }
397
398 for (name, constraint) in &self.invariants {
400 hasher.update(name);
401 hasher.update(constraint);
402 }
403
404 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 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 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}