1use serde::{Deserialize, Serialize};
2
3use crate::epoch::{ContainerEpochBudget, EpochController, Phase};
4use crate::error::{ContainerError, Result};
5use crate::memory::{MemoryConfig, MemorySlab};
6use crate::witness::{
7 CoherenceDecision, ContainerWitnessReceipt, VerificationResult, WitnessChain,
8};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ContainerConfig {
13 pub memory: MemoryConfig,
15 pub epoch_budget: ContainerEpochBudget,
17 pub instance_id: u64,
19 pub max_receipts: usize,
21}
22
23impl Default for ContainerConfig {
24 fn default() -> Self {
25 Self {
26 memory: MemoryConfig::default(),
27 epoch_budget: ContainerEpochBudget::default(),
28 instance_id: 0,
29 max_receipts: 1024,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub enum Delta {
37 EdgeAdd { u: usize, v: usize, weight: f64 },
38 EdgeRemove { u: usize, v: usize },
39 WeightUpdate { u: usize, v: usize, new_weight: f64 },
40 Observation { node: usize, value: f64 },
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct ComponentMask(pub u8);
46
47impl ComponentMask {
48 pub const INGEST: Self = Self(0b0000_0001);
49 pub const MINCUT: Self = Self(0b0000_0010);
50 pub const SPECTRAL: Self = Self(0b0000_0100);
51 pub const EVIDENCE: Self = Self(0b0000_1000);
52 pub const WITNESS: Self = Self(0b0001_0000);
53 pub const ALL: Self = Self(0b0001_1111);
54
55 pub fn contains(&self, other: Self) -> bool {
57 self.0 & other.0 == other.0
58 }
59
60 pub fn insert(&mut self, other: Self) {
62 self.0 |= other.0;
63 }
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct TickResult {
69 pub receipt: ContainerWitnessReceipt,
71 pub partial: bool,
73 pub components_completed: u8,
75 pub tick_time_us: u64,
77}
78
79struct GraphState {
81 num_vertices: usize,
82 num_edges: usize,
83 edges: Vec<(usize, usize, f64)>,
84 min_cut_value: f64,
85 canonical_hash: [u8; 32],
86}
87
88impl GraphState {
89 fn new() -> Self {
90 Self {
91 num_vertices: 0,
92 num_edges: 0,
93 edges: Vec::new(),
94 min_cut_value: 0.0,
95 canonical_hash: [0u8; 32],
96 }
97 }
98}
99
100struct SpectralState {
102 scs: f64,
103 fiedler: f64,
104 gap: f64,
105}
106
107impl SpectralState {
108 fn new() -> Self {
109 Self {
110 scs: 0.0,
111 fiedler: 0.0,
112 gap: 0.0,
113 }
114 }
115}
116
117struct EvidenceState {
119 observations: Vec<f64>,
120 accumulated_evidence: f64,
121 threshold: f64,
122}
123
124impl EvidenceState {
125 fn new() -> Self {
126 Self {
127 observations: Vec::new(),
128 accumulated_evidence: 0.0,
129 threshold: 1.0,
130 }
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ContainerSnapshot {
137 pub epoch: u64,
138 pub config: ContainerConfig,
139 pub graph_edges: Vec<(usize, usize, f64)>,
140 pub spectral_scs: f64,
141 pub evidence_accumulated: f64,
142}
143
144pub struct CognitiveContainer {
147 config: ContainerConfig,
148 #[allow(dead_code)]
149 slab: MemorySlab,
150 epoch: EpochController,
151 witness: WitnessChain,
152 graph: GraphState,
153 spectral: SpectralState,
154 evidence: EvidenceState,
155 initialized: bool,
156}
157
158impl CognitiveContainer {
159 pub fn new(config: ContainerConfig) -> Result<Self> {
161 let slab = MemorySlab::new(config.memory.clone())?;
162 let epoch = EpochController::new(config.epoch_budget.clone());
163 let witness = WitnessChain::new(config.max_receipts);
164
165 Ok(Self {
166 config,
167 slab,
168 epoch,
169 witness,
170 graph: GraphState::new(),
171 spectral: SpectralState::new(),
172 evidence: EvidenceState::new(),
173 initialized: true,
174 })
175 }
176
177 pub fn tick(&mut self, deltas: &[Delta]) -> Result<TickResult> {
180 if !self.initialized {
181 return Err(ContainerError::NotInitialized);
182 }
183
184 let start = std::time::Instant::now();
185 self.epoch.reset();
186 let mut completed = ComponentMask(0);
187
188 if self.epoch.try_budget(Phase::Ingest) {
190 for delta in deltas {
191 self.apply_delta(delta);
192 }
193 self.epoch.consume(deltas.len().max(1) as u64);
194 completed.insert(ComponentMask::INGEST);
195 }
196
197 if self.epoch.try_budget(Phase::MinCut) {
199 self.recompute_mincut();
200 self.epoch.consume(self.graph.num_edges.max(1) as u64);
201 completed.insert(ComponentMask::MINCUT);
202 }
203
204 if self.epoch.try_budget(Phase::Spectral) {
206 self.update_spectral();
207 self.epoch.consume(self.graph.num_vertices.max(1) as u64);
208 completed.insert(ComponentMask::SPECTRAL);
209 }
210
211 if self.epoch.try_budget(Phase::Evidence) {
213 self.accumulate_evidence();
214 self.epoch
215 .consume(self.evidence.observations.len().max(1) as u64);
216 completed.insert(ComponentMask::EVIDENCE);
217 }
218
219 let decision = self.make_decision();
221 let input_bytes = self.serialize_deltas(deltas);
222 let mincut_bytes = self.graph.min_cut_value.to_le_bytes();
223 let evidence_bytes = self.evidence.accumulated_evidence.to_le_bytes();
224
225 let receipt = self.witness.generate_receipt(
226 &input_bytes,
227 &mincut_bytes,
228 self.spectral.scs,
229 &evidence_bytes,
230 decision,
231 );
232 completed.insert(ComponentMask::WITNESS);
233
234 Ok(TickResult {
235 receipt,
236 partial: completed.0 != ComponentMask::ALL.0,
237 components_completed: completed.0,
238 tick_time_us: start.elapsed().as_micros() as u64,
239 })
240 }
241
242 pub fn config(&self) -> &ContainerConfig {
244 &self.config
245 }
246
247 pub fn current_epoch(&self) -> u64 {
249 self.witness.current_epoch()
250 }
251
252 pub fn receipt_chain(&self) -> &[ContainerWitnessReceipt] {
254 self.witness.receipt_chain()
255 }
256
257 pub fn verify_chain(&self) -> VerificationResult {
259 WitnessChain::verify_chain(self.witness.receipt_chain())
260 }
261
262 pub fn snapshot(&self) -> ContainerSnapshot {
264 ContainerSnapshot {
265 epoch: self.witness.current_epoch(),
266 config: self.config.clone(),
267 graph_edges: self.graph.edges.clone(),
268 spectral_scs: self.spectral.scs,
269 evidence_accumulated: self.evidence.accumulated_evidence,
270 }
271 }
272
273 fn apply_delta(&mut self, delta: &Delta) {
276 match delta {
277 Delta::EdgeAdd { u, v, weight } => {
278 self.graph.edges.push((*u, *v, *weight));
279 self.graph.num_edges += 1;
280 let max_node = (*u).max(*v) + 1;
281 if max_node > self.graph.num_vertices {
282 self.graph.num_vertices = max_node;
283 }
284 }
285 Delta::EdgeRemove { u, v } => {
286 self.graph.edges.retain(|(a, b, _)| !(*a == *u && *b == *v));
287 self.graph.num_edges = self.graph.edges.len();
288 }
289 Delta::WeightUpdate { u, v, new_weight } => {
290 for edge in &mut self.graph.edges {
291 if edge.0 == *u && edge.1 == *v {
292 edge.2 = *new_weight;
293 }
294 }
295 }
296 Delta::Observation { value, .. } => {
297 self.evidence.observations.push(*value);
298 }
299 }
300 }
301
302 fn recompute_mincut(&mut self) {
306 if self.graph.edges.is_empty() {
307 self.graph.min_cut_value = 0.0;
308 self.graph.canonical_hash = [0u8; 32];
309 return;
310 }
311
312 let n = self.graph.num_vertices;
314 let mut degree = vec![0.0f64; n];
315 for &(u, v, w) in &self.graph.edges {
316 if u < n {
317 degree[u] += w;
318 }
319 if v < n {
320 degree[v] += w;
321 }
322 }
323
324 self.graph.min_cut_value = degree
325 .iter()
326 .copied()
327 .filter(|&d| d > 0.0)
328 .fold(f64::MAX, f64::min);
329 if self.graph.min_cut_value == f64::MAX {
330 self.graph.min_cut_value = 0.0;
331 }
332
333 let mut sorted = self.graph.edges.clone();
335 sorted.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
336 let bytes: Vec<u8> = sorted
337 .iter()
338 .flat_map(|(u, v, w)| {
339 let mut b = Vec::with_capacity(24);
340 b.extend_from_slice(&u.to_le_bytes());
341 b.extend_from_slice(&v.to_le_bytes());
342 b.extend_from_slice(&w.to_le_bytes());
343 b
344 })
345 .collect();
346 self.graph.canonical_hash = crate::witness::deterministic_hash_public(&bytes);
347 }
348
349 fn update_spectral(&mut self) {
351 let total_weight: f64 = self.graph.edges.iter().map(|e| e.2).sum();
352 if total_weight > 0.0 {
353 self.spectral.scs = self.graph.min_cut_value / total_weight;
354 self.spectral.fiedler = self.spectral.scs;
355 self.spectral.gap = 1.0 - self.spectral.scs;
356 } else {
357 self.spectral.scs = 0.0;
358 self.spectral.fiedler = 0.0;
359 self.spectral.gap = 0.0;
360 }
361 }
362
363 fn accumulate_evidence(&mut self) {
365 if self.evidence.observations.is_empty() {
366 return;
367 }
368 let mean: f64 = self.evidence.observations.iter().sum::<f64>()
369 / self.evidence.observations.len() as f64;
370 self.evidence.accumulated_evidence += mean.abs();
371 }
372
373 fn make_decision(&self) -> CoherenceDecision {
375 if self.graph.edges.is_empty() {
376 return CoherenceDecision::Inconclusive;
377 }
378 if self.spectral.scs >= 0.5 && self.evidence.accumulated_evidence < self.evidence.threshold
379 {
380 return CoherenceDecision::Pass;
381 }
382 if self.spectral.scs < 0.2 {
383 let severity = ((1.0 - self.spectral.scs) * 10.0).min(255.0) as u8;
384 return CoherenceDecision::Fail { severity };
385 }
386 CoherenceDecision::Inconclusive
387 }
388
389 fn serialize_deltas(&self, deltas: &[Delta]) -> Vec<u8> {
390 serde_json::to_vec(deltas).unwrap_or_default()
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 fn default_container() -> CognitiveContainer {
399 CognitiveContainer::new(ContainerConfig::default()).unwrap()
400 }
401
402 #[test]
403 fn test_container_lifecycle() {
404 let mut container = default_container();
405 assert_eq!(container.current_epoch(), 0);
406
407 let result = container.tick(&[]).unwrap();
408 assert_eq!(result.receipt.epoch, 0);
409 assert_eq!(container.current_epoch(), 1);
410
411 match container.verify_chain() {
412 VerificationResult::Valid { chain_length, .. } => {
413 assert_eq!(chain_length, 1);
414 }
415 other => panic!("Expected Valid, got {other:?}"),
416 }
417 }
418
419 #[test]
420 fn test_container_tick_with_deltas() {
421 let mut container = default_container();
422
423 let deltas = vec![
424 Delta::EdgeAdd {
425 u: 0,
426 v: 1,
427 weight: 1.0,
428 },
429 Delta::EdgeAdd {
430 u: 1,
431 v: 2,
432 weight: 2.0,
433 },
434 Delta::EdgeAdd {
435 u: 2,
436 v: 0,
437 weight: 1.5,
438 },
439 Delta::Observation {
440 node: 0,
441 value: 0.8,
442 },
443 ];
444
445 let result = container.tick(&deltas).unwrap();
446 assert!(!result.partial);
447 assert_eq!(result.components_completed, ComponentMask::ALL.0);
448
449 let snap = container.snapshot();
451 assert_eq!(snap.graph_edges.len(), 3);
452 assert!(snap.spectral_scs > 0.0);
453 }
454
455 #[test]
456 fn test_container_snapshot_restore() {
457 let mut container = default_container();
458 container
459 .tick(&[Delta::EdgeAdd {
460 u: 0,
461 v: 1,
462 weight: 3.0,
463 }])
464 .unwrap();
465
466 let snap = container.snapshot();
467 let json = serde_json::to_string(&snap).expect("serialize snapshot");
468 let restored: ContainerSnapshot =
469 serde_json::from_str(&json).expect("deserialize snapshot");
470
471 assert_eq!(restored.epoch, snap.epoch);
472 assert_eq!(restored.graph_edges.len(), snap.graph_edges.len());
473 assert!((restored.spectral_scs - snap.spectral_scs).abs() < f64::EPSILON);
474 }
475
476 #[test]
477 fn test_container_decision_logic() {
478 let mut container = default_container();
479
480 let r = container.tick(&[]).unwrap();
482 assert_eq!(r.receipt.decision, CoherenceDecision::Inconclusive);
483
484 let r = container
486 .tick(&[Delta::EdgeAdd {
487 u: 0,
488 v: 1,
489 weight: 5.0,
490 }])
491 .unwrap();
492 assert_eq!(r.receipt.decision, CoherenceDecision::Pass);
493 }
494
495 #[test]
496 fn test_container_multiple_epochs() {
497 let mut container = default_container();
498 for i in 0..10 {
499 container
500 .tick(&[Delta::EdgeAdd {
501 u: i,
502 v: i + 1,
503 weight: 1.0,
504 }])
505 .unwrap();
506 }
507 assert_eq!(container.current_epoch(), 10);
508
509 match container.verify_chain() {
510 VerificationResult::Valid {
511 chain_length,
512 first_epoch,
513 last_epoch,
514 } => {
515 assert_eq!(chain_length, 10);
516 assert_eq!(first_epoch, 0);
517 assert_eq!(last_epoch, 9);
518 }
519 other => panic!("Expected Valid, got {other:?}"),
520 }
521 }
522
523 #[test]
524 fn test_container_edge_remove() {
525 let mut container = default_container();
526 container
527 .tick(&[
528 Delta::EdgeAdd {
529 u: 0,
530 v: 1,
531 weight: 1.0,
532 },
533 Delta::EdgeAdd {
534 u: 1,
535 v: 2,
536 weight: 2.0,
537 },
538 ])
539 .unwrap();
540
541 container.tick(&[Delta::EdgeRemove { u: 0, v: 1 }]).unwrap();
542
543 let snap = container.snapshot();
544 assert_eq!(snap.graph_edges.len(), 1);
545 assert_eq!(snap.graph_edges[0], (1, 2, 2.0));
546 }
547
548 #[test]
549 fn test_container_weight_update() {
550 let mut container = default_container();
551 container
552 .tick(&[Delta::EdgeAdd {
553 u: 0,
554 v: 1,
555 weight: 1.0,
556 }])
557 .unwrap();
558
559 container
560 .tick(&[Delta::WeightUpdate {
561 u: 0,
562 v: 1,
563 new_weight: 5.0,
564 }])
565 .unwrap();
566
567 let snap = container.snapshot();
568 assert_eq!(snap.graph_edges[0].2, 5.0);
569 }
570
571 #[test]
572 fn test_component_mask() {
573 let mut mask = ComponentMask(0);
574 assert!(!mask.contains(ComponentMask::INGEST));
575
576 mask.insert(ComponentMask::INGEST);
577 assert!(mask.contains(ComponentMask::INGEST));
578 assert!(!mask.contains(ComponentMask::MINCUT));
579
580 mask.insert(ComponentMask::MINCUT);
581 assert!(mask.contains(ComponentMask::INGEST));
582 assert!(mask.contains(ComponentMask::MINCUT));
583
584 assert!(!mask.contains(ComponentMask::ALL));
585 }
586}