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