cognitum-gate-kernel 0.1.1

No-std WASM kernel for 256-tile coherence gate fabric
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
//! Tile report structures for coherence gate coordination
//!
//! Defines the 64-byte cache-line aligned report structure that tiles
//! produce after each tick. These reports are aggregated by the coordinator
//! to form witness fragments for the coherence gate.

#![allow(missing_docs)]

use crate::delta::TileVertexId;
use crate::evidence::LogEValue;
use core::mem::size_of;

/// Tile status codes
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TileStatus {
    /// Tile is idle (no work)
    Idle = 0,
    /// Tile is processing deltas
    Processing = 1,
    /// Tile completed tick successfully
    Complete = 2,
    /// Tile encountered an error
    Error = 3,
    /// Tile is waiting for synchronization
    Waiting = 4,
    /// Tile is checkpointing
    Checkpointing = 5,
    /// Tile is recovering from checkpoint
    Recovering = 6,
    /// Tile is shutting down
    Shutdown = 7,
}

impl From<u8> for TileStatus {
    fn from(v: u8) -> Self {
        match v {
            0 => TileStatus::Idle,
            1 => TileStatus::Processing,
            2 => TileStatus::Complete,
            3 => TileStatus::Error,
            4 => TileStatus::Waiting,
            5 => TileStatus::Checkpointing,
            6 => TileStatus::Recovering,
            7 => TileStatus::Shutdown,
            _ => TileStatus::Error,
        }
    }
}

/// Witness fragment for aggregation
///
/// Compact representation of local cut/partition information
/// that can be merged across tiles.
#[derive(Debug, Clone, Copy, Default)]
#[repr(C, align(8))]
pub struct WitnessFragment {
    /// Seed vertex for this fragment
    pub seed: TileVertexId,
    /// Boundary size (cut edges crossing fragment)
    pub boundary_size: u16,
    /// Cardinality (vertices in fragment)
    pub cardinality: u16,
    /// Fragment hash for consistency checking
    pub hash: u16,
    /// Local minimum cut value (fixed-point)
    pub local_min_cut: u16,
    /// Component ID this fragment belongs to
    pub component: u16,
    /// Reserved padding
    pub _reserved: u16,
}

impl WitnessFragment {
    /// Create a new witness fragment
    #[inline]
    pub const fn new(
        seed: TileVertexId,
        boundary_size: u16,
        cardinality: u16,
        local_min_cut: u16,
    ) -> Self {
        Self {
            seed,
            boundary_size,
            cardinality,
            hash: 0,
            local_min_cut,
            component: 0,
            _reserved: 0,
        }
    }

    /// Compute fragment hash
    pub fn compute_hash(&mut self) {
        let mut h = self.seed as u32;
        h = h.wrapping_mul(31).wrapping_add(self.boundary_size as u32);
        h = h.wrapping_mul(31).wrapping_add(self.cardinality as u32);
        h = h.wrapping_mul(31).wrapping_add(self.local_min_cut as u32);
        self.hash = (h & 0xFFFF) as u16;
    }

    /// Check if fragment is empty
    #[inline]
    pub const fn is_empty(&self) -> bool {
        self.cardinality == 0
    }
}

/// Tile report produced after each tick (64 bytes, cache-line aligned)
///
/// This structure is designed to fit exactly in one cache line for
/// efficient memory access patterns in the coordinator.
#[derive(Debug, Clone, Copy)]
#[repr(C, align(64))]
pub struct TileReport {
    // --- Header (8 bytes) ---
    /// Tile ID (0-255)
    pub tile_id: u8,
    /// Tile status
    pub status: TileStatus,
    /// Generation/epoch number
    pub generation: u16,
    /// Current tick number
    pub tick: u32,

    // --- Graph state (8 bytes) ---
    /// Number of active vertices
    pub num_vertices: u16,
    /// Number of active edges
    pub num_edges: u16,
    /// Number of connected components
    pub num_components: u16,
    /// Graph flags
    pub graph_flags: u16,

    // --- Evidence state (8 bytes) ---
    /// Global log e-value (tile-local)
    pub log_e_value: LogEValue,
    /// Number of observations processed
    pub obs_count: u16,
    /// Number of rejected hypotheses
    pub rejected_count: u16,

    // --- Witness fragment (16 bytes) ---
    /// Primary witness fragment
    pub witness: WitnessFragment,

    // --- Performance metrics (8 bytes) ---
    /// Delta processing time (microseconds)
    pub delta_time_us: u16,
    /// Tick processing time (microseconds)
    pub tick_time_us: u16,
    /// Deltas processed this tick
    pub deltas_processed: u16,
    /// Memory usage (KB)
    pub memory_kb: u16,

    // --- Cross-tile coordination (8 bytes) ---
    /// Number of ghost vertices
    pub ghost_vertices: u16,
    /// Number of ghost edges
    pub ghost_edges: u16,
    /// Boundary vertices (shared with other tiles)
    pub boundary_vertices: u16,
    /// Pending sync messages
    pub pending_sync: u16,

    // --- Reserved for future use (8 bytes) ---
    /// Reserved fields
    pub _reserved: [u8; 8],
}

impl Default for TileReport {
    fn default() -> Self {
        Self::new(0)
    }
}

impl TileReport {
    /// Graph flag: graph is connected
    pub const GRAPH_CONNECTED: u16 = 0x0001;
    /// Graph flag: graph is dirty (needs recomputation)
    pub const GRAPH_DIRTY: u16 = 0x0002;
    /// Graph flag: graph is at capacity
    pub const GRAPH_FULL: u16 = 0x0004;
    /// Graph flag: graph has ghost edges
    pub const GRAPH_HAS_GHOSTS: u16 = 0x0008;

    /// Create a new report for a tile
    #[inline]
    pub const fn new(tile_id: u8) -> Self {
        Self {
            tile_id,
            status: TileStatus::Idle,
            generation: 0,
            tick: 0,
            num_vertices: 0,
            num_edges: 0,
            num_components: 0,
            graph_flags: 0,
            log_e_value: 0,
            obs_count: 0,
            rejected_count: 0,
            witness: WitnessFragment {
                seed: 0,
                boundary_size: 0,
                cardinality: 0,
                hash: 0,
                local_min_cut: 0,
                component: 0,
                _reserved: 0,
            },
            delta_time_us: 0,
            tick_time_us: 0,
            deltas_processed: 0,
            memory_kb: 0,
            ghost_vertices: 0,
            ghost_edges: 0,
            boundary_vertices: 0,
            pending_sync: 0,
            _reserved: [0; 8],
        }
    }

    /// Mark report as complete
    #[inline]
    pub fn set_complete(&mut self) {
        self.status = TileStatus::Complete;
    }

    /// Mark report as error
    #[inline]
    pub fn set_error(&mut self) {
        self.status = TileStatus::Error;
    }

    /// Set connected flag
    #[inline]
    pub fn set_connected(&mut self, connected: bool) {
        if connected {
            self.graph_flags |= Self::GRAPH_CONNECTED;
        } else {
            self.graph_flags &= !Self::GRAPH_CONNECTED;
        }
    }

    /// Check if graph is connected
    #[inline]
    pub const fn is_connected(&self) -> bool {
        self.graph_flags & Self::GRAPH_CONNECTED != 0
    }

    /// Check if graph is dirty
    #[inline]
    pub const fn is_dirty(&self) -> bool {
        self.graph_flags & Self::GRAPH_DIRTY != 0
    }

    /// Get e-value as approximate f32
    pub fn e_value_approx(&self) -> f32 {
        let log2_val = (self.log_e_value as f32) / 65536.0;
        libm::exp2f(log2_val)
    }

    /// Update witness fragment
    pub fn set_witness(&mut self, witness: WitnessFragment) {
        self.witness = witness;
    }

    /// Get the witness fragment
    #[inline]
    pub const fn get_witness(&self) -> &WitnessFragment {
        &self.witness
    }

    /// Check if tile has any rejections
    #[inline]
    pub const fn has_rejections(&self) -> bool {
        self.rejected_count > 0
    }

    /// Get processing rate (deltas per microsecond)
    pub fn processing_rate(&self) -> f32 {
        if self.tick_time_us == 0 {
            0.0
        } else {
            (self.deltas_processed as f32) / (self.tick_time_us as f32)
        }
    }
}

/// Report aggregator for combining multiple tile reports
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct AggregatedReport {
    /// Total vertices across all tiles
    pub total_vertices: u32,
    /// Total edges across all tiles
    pub total_edges: u32,
    /// Total components across all tiles
    pub total_components: u16,
    /// Number of tiles reporting
    pub tiles_reporting: u16,
    /// Tiles with errors
    pub tiles_with_errors: u16,
    /// Tiles with rejections
    pub tiles_with_rejections: u16,
    /// Global log e-value (sum of tile e-values)
    pub global_log_e: i64,
    /// Minimum local cut across tiles
    pub global_min_cut: u16,
    /// Tile with minimum cut
    pub min_cut_tile: u8,
    /// Reserved padding
    pub _reserved: u8,
    /// Total processing time (microseconds)
    pub total_time_us: u32,
    /// Tick number
    pub tick: u32,
}

impl AggregatedReport {
    /// Create a new aggregated report
    pub const fn new(tick: u32) -> Self {
        Self {
            total_vertices: 0,
            total_edges: 0,
            total_components: 0,
            tiles_reporting: 0,
            tiles_with_errors: 0,
            tiles_with_rejections: 0,
            global_log_e: 0,
            global_min_cut: u16::MAX,
            min_cut_tile: 0,
            _reserved: 0,
            total_time_us: 0,
            tick,
        }
    }

    /// Merge a tile report into the aggregate
    pub fn merge(&mut self, report: &TileReport) {
        self.total_vertices += report.num_vertices as u32;
        self.total_edges += report.num_edges as u32;
        self.total_components += report.num_components;
        self.tiles_reporting += 1;

        if report.status == TileStatus::Error {
            self.tiles_with_errors += 1;
        }

        if report.rejected_count > 0 {
            self.tiles_with_rejections += 1;
        }

        self.global_log_e += report.log_e_value as i64;

        if report.witness.local_min_cut < self.global_min_cut {
            self.global_min_cut = report.witness.local_min_cut;
            self.min_cut_tile = report.tile_id;
        }

        self.total_time_us = self.total_time_us.max(report.tick_time_us as u32);
    }

    /// Check if all tiles completed successfully
    pub fn all_complete(&self, expected_tiles: u16) -> bool {
        self.tiles_reporting == expected_tiles && self.tiles_with_errors == 0
    }

    /// Get global e-value as approximate f64
    pub fn global_e_value(&self) -> f64 {
        let log2_val = (self.global_log_e as f64) / 65536.0;
        libm::exp2(log2_val)
    }
}

// Compile-time size assertions
const _: () = assert!(
    size_of::<TileReport>() == 64,
    "TileReport must be exactly 64 bytes"
);
const _: () = assert!(
    size_of::<WitnessFragment>() == 16,
    "WitnessFragment must be 16 bytes"
);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tile_report_size() {
        assert_eq!(size_of::<TileReport>(), 64);
    }

    #[test]
    fn test_tile_report_alignment() {
        assert_eq!(core::mem::align_of::<TileReport>(), 64);
    }

    #[test]
    fn test_witness_fragment_size() {
        assert_eq!(size_of::<WitnessFragment>(), 16);
    }

    #[test]
    fn test_new_report() {
        let report = TileReport::new(5);
        assert_eq!(report.tile_id, 5);
        assert_eq!(report.status, TileStatus::Idle);
        assert_eq!(report.tick, 0);
    }

    #[test]
    fn test_set_status() {
        let mut report = TileReport::new(0);
        report.set_complete();
        assert_eq!(report.status, TileStatus::Complete);

        report.set_error();
        assert_eq!(report.status, TileStatus::Error);
    }

    #[test]
    fn test_connected_flag() {
        let mut report = TileReport::new(0);
        assert!(!report.is_connected());

        report.set_connected(true);
        assert!(report.is_connected());

        report.set_connected(false);
        assert!(!report.is_connected());
    }

    #[test]
    fn test_witness_fragment() {
        let mut frag = WitnessFragment::new(10, 5, 20, 100);
        assert_eq!(frag.seed, 10);
        assert_eq!(frag.boundary_size, 5);
        assert_eq!(frag.cardinality, 20);
        assert_eq!(frag.local_min_cut, 100);

        frag.compute_hash();
        assert_ne!(frag.hash, 0);
    }

    #[test]
    fn test_aggregated_report() {
        let mut agg = AggregatedReport::new(1);

        let mut report1 = TileReport::new(0);
        report1.num_vertices = 50;
        report1.num_edges = 100;
        report1.witness.local_min_cut = 200;

        let mut report2 = TileReport::new(1);
        report2.num_vertices = 75;
        report2.num_edges = 150;
        report2.witness.local_min_cut = 150;

        agg.merge(&report1);
        agg.merge(&report2);

        assert_eq!(agg.tiles_reporting, 2);
        assert_eq!(agg.total_vertices, 125);
        assert_eq!(agg.total_edges, 250);
        assert_eq!(agg.global_min_cut, 150);
        assert_eq!(agg.min_cut_tile, 1);
    }

    #[test]
    fn test_tile_status_roundtrip() {
        for i in 0..=7 {
            let status = TileStatus::from(i);
            assert_eq!(status as u8, i);
        }
    }

    #[test]
    fn test_processing_rate() {
        let mut report = TileReport::new(0);
        report.deltas_processed = 100;
        report.tick_time_us = 50;

        assert!((report.processing_rate() - 2.0).abs() < 0.01);
    }
}