rustkernel_compliance/
ring_messages.rs

1//! Ring message types for Compliance domain kernels.
2//!
3//! These messages implement the `RingMessage` trait for GPU-native persistent
4//! actor communication in compliance/AML operations.
5//!
6//! Type ID range: 300-399 (Compliance domain)
7//!
8//! ## Type ID Assignments
9//! - 300-309: TransactionMonitoring messages
10//! - 310-319: CircularFlowRatio messages
11//! - 320-329: AMLPatternDetection messages
12//! - 330-339: Reserved for KYC
13//! - 340-349: Reserved for Sanctions
14
15use ringkernel_core::message::{CorrelationId, MessageId};
16use ringkernel_derive::RingMessage;
17use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
18
19// ============================================================================
20// Transaction Monitoring Ring Messages (300-309)
21// ============================================================================
22
23/// Ring message for real-time transaction monitoring.
24///
25/// Type ID: 300
26#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
27#[message(type_id = 300)]
28#[archive(check_bytes)]
29pub struct MonitorTransactionRing {
30    /// Message ID.
31    #[message(id)]
32    pub id: MessageId,
33    /// Correlation ID.
34    #[message(correlation)]
35    pub correlation_id: CorrelationId,
36    /// Transaction ID.
37    pub tx_id: u64,
38    /// Source entity ID.
39    pub source_id: u64,
40    /// Destination entity ID.
41    pub dest_id: u64,
42    /// Transaction amount (fixed-point, 8 decimals).
43    pub amount: i64,
44    /// Timestamp (nanoseconds since epoch).
45    pub timestamp: u64,
46    /// Transaction type code.
47    pub tx_type: u8,
48    /// Currency code (3-letter ISO).
49    pub currency: [u8; 4],
50}
51
52impl MonitorTransactionRing {
53    /// Create a new transaction monitoring message.
54    pub fn new(
55        tx_id: u64,
56        source_id: u64,
57        dest_id: u64,
58        amount: f64,
59        timestamp: u64,
60        tx_type: u8,
61        currency: &str,
62    ) -> Self {
63        let mut curr = [0u8; 4];
64        let bytes = currency.as_bytes();
65        let len = bytes.len().min(3);
66        curr[..len].copy_from_slice(&bytes[..len]);
67
68        Self {
69            id: MessageId::generate(),
70            correlation_id: CorrelationId::generate(),
71            tx_id,
72            source_id,
73            dest_id,
74            amount: (amount * 100_000_000.0) as i64,
75            timestamp,
76            tx_type,
77            currency: curr,
78        }
79    }
80}
81
82/// Response from transaction monitoring.
83///
84/// Type ID: 301
85#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
86#[message(type_id = 301)]
87#[archive(check_bytes)]
88pub struct MonitorTransactionResponse {
89    /// Correlation ID.
90    #[message(correlation)]
91    pub correlation_id: CorrelationId,
92    /// Transaction ID.
93    pub tx_id: u64,
94    /// Number of alerts generated.
95    pub alert_count: u32,
96    /// Risk score (0-100).
97    pub risk_score: u8,
98    /// Alert flags (bitmask).
99    pub alert_flags: u64,
100}
101
102/// Alert emitted by transaction monitoring.
103///
104/// Type ID: 302
105#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
106#[message(type_id = 302)]
107#[archive(check_bytes)]
108pub struct TransactionAlert {
109    /// Message ID.
110    #[message(id)]
111    pub id: MessageId,
112    /// Transaction ID that triggered the alert.
113    pub tx_id: u64,
114    /// Alert type code.
115    pub alert_type: u16,
116    /// Alert severity (1-5).
117    pub severity: u8,
118    /// Related entity ID.
119    pub entity_id: u64,
120    /// Threshold that was exceeded.
121    pub threshold: i64,
122    /// Actual value.
123    pub actual_value: i64,
124    /// Timestamp.
125    pub timestamp: u64,
126}
127
128// ============================================================================
129// Circular Flow Ratio Ring Messages (310-319)
130// ============================================================================
131
132/// Ring message for adding an edge to the transaction graph.
133///
134/// Type ID: 310
135#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
136#[message(type_id = 310)]
137#[archive(check_bytes)]
138pub struct AddGraphEdgeRing {
139    /// Message ID.
140    #[message(id)]
141    pub id: MessageId,
142    /// Correlation ID.
143    #[message(correlation)]
144    pub correlation_id: CorrelationId,
145    /// Source entity ID.
146    pub source_id: u64,
147    /// Destination entity ID.
148    pub dest_id: u64,
149    /// Transaction amount.
150    pub amount: i64,
151    /// Timestamp.
152    pub timestamp: u64,
153}
154
155impl AddGraphEdgeRing {
156    /// Create a new add edge message.
157    pub fn new(source_id: u64, dest_id: u64, amount: f64, timestamp: u64) -> Self {
158        Self {
159            id: MessageId::generate(),
160            correlation_id: CorrelationId::generate(),
161            source_id,
162            dest_id,
163            amount: (amount * 100_000_000.0) as i64,
164            timestamp,
165        }
166    }
167}
168
169/// Response from adding a graph edge.
170///
171/// Type ID: 311
172#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
173#[message(type_id = 311)]
174#[archive(check_bytes)]
175pub struct AddGraphEdgeResponse {
176    /// Correlation ID.
177    #[message(correlation)]
178    pub correlation_id: CorrelationId,
179    /// Whether a new cycle was detected.
180    pub cycle_detected: bool,
181    /// Number of entities in detected cycle (0 if none).
182    pub cycle_size: u32,
183    /// Current circular flow ratio for source entity.
184    pub source_ratio: f32,
185}
186
187/// Query for circular flow ratio.
188///
189/// Type ID: 312
190#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
191#[message(type_id = 312)]
192#[archive(check_bytes)]
193pub struct QueryCircularRatioRing {
194    /// Message ID.
195    #[message(id)]
196    pub id: MessageId,
197    /// Correlation ID.
198    #[message(correlation)]
199    pub correlation_id: CorrelationId,
200    /// Entity ID to query.
201    pub entity_id: u64,
202}
203
204/// Response with circular flow ratio.
205///
206/// Type ID: 313
207#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
208#[message(type_id = 313)]
209#[archive(check_bytes)]
210pub struct QueryCircularRatioResponse {
211    /// Correlation ID.
212    #[message(correlation)]
213    pub correlation_id: CorrelationId,
214    /// Entity ID.
215    pub entity_id: u64,
216    /// Circular flow ratio (0.0-1.0).
217    pub ratio: f32,
218    /// Number of SCCs the entity belongs to.
219    pub scc_count: u32,
220    /// Total transaction volume in cycles.
221    pub cycle_volume: i64,
222}
223
224/// Cycle detection alert.
225///
226/// Type ID: 314
227#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
228#[message(type_id = 314)]
229#[archive(check_bytes)]
230pub struct CycleDetectedAlert {
231    /// Message ID.
232    #[message(id)]
233    pub id: MessageId,
234    /// Cycle identifier.
235    pub cycle_id: u64,
236    /// Number of entities in cycle.
237    pub cycle_size: u32,
238    /// Total value flowing through cycle.
239    pub total_value: i64,
240    /// Timestamp of detection.
241    pub timestamp: u64,
242    /// Risk level (1-5).
243    pub risk_level: u8,
244}
245
246// ============================================================================
247// AML Pattern Detection Ring Messages (320-329)
248// ============================================================================
249
250/// Ring message for pattern matching on transaction stream.
251///
252/// Type ID: 320
253#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
254#[message(type_id = 320)]
255#[archive(check_bytes)]
256pub struct MatchPatternRing {
257    /// Message ID.
258    #[message(id)]
259    pub id: MessageId,
260    /// Correlation ID.
261    #[message(correlation)]
262    pub correlation_id: CorrelationId,
263    /// Transaction data (encoded).
264    pub tx_id: u64,
265    /// Source entity.
266    pub source_id: u64,
267    /// Destination entity.
268    pub dest_id: u64,
269    /// Amount.
270    pub amount: i64,
271    /// Transaction type.
272    pub tx_type: u8,
273    /// Timestamp.
274    pub timestamp: u64,
275}
276
277/// Response from pattern matching.
278///
279/// Type ID: 321
280#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
281#[message(type_id = 321)]
282#[archive(check_bytes)]
283pub struct MatchPatternResponse {
284    /// Correlation ID.
285    #[message(correlation)]
286    pub correlation_id: CorrelationId,
287    /// Transaction ID.
288    pub tx_id: u64,
289    /// Patterns matched (bitmask).
290    pub patterns_matched: u64,
291    /// Highest pattern score.
292    pub max_score: f32,
293    /// Number of patterns matched.
294    pub match_count: u32,
295}
296
297/// AML pattern alert.
298///
299/// Type ID: 322
300#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
301#[message(type_id = 322)]
302#[archive(check_bytes)]
303pub struct AMLPatternAlert {
304    /// Message ID.
305    #[message(id)]
306    pub id: MessageId,
307    /// Pattern type.
308    pub pattern_type: u16,
309    /// Entity ID involved.
310    pub entity_id: u64,
311    /// Confidence score (0.0-1.0).
312    pub confidence: f32,
313    /// Evidence transaction IDs (up to 4).
314    pub evidence_tx_ids: [u64; 4],
315    /// Number of evidence transactions.
316    pub evidence_count: u8,
317    /// Timestamp.
318    pub timestamp: u64,
319}
320
321// ============================================================================
322// K2K Cross-Compliance Alert Coordination (350-369)
323// ============================================================================
324
325/// K2K alert broadcast - share alert across compliance kernels.
326///
327/// Type ID: 350
328#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
329#[message(type_id = 350)]
330#[archive(check_bytes)]
331pub struct K2KAlertBroadcast {
332    /// Message ID.
333    #[message(id)]
334    pub id: MessageId,
335    /// Source kernel ID (hashed).
336    pub source_kernel: u64,
337    /// Alert type: 1=transaction, 2=cycle, 3=pattern, 4=sanctions.
338    pub alert_type: u8,
339    /// Alert severity (1-5).
340    pub severity: u8,
341    /// Primary entity ID.
342    pub entity_id: u64,
343    /// Related entity ID (if applicable).
344    pub related_entity_id: u64,
345    /// Risk score (0-100).
346    pub risk_score: u8,
347    /// Alert timestamp.
348    pub timestamp: u64,
349    /// Additional context (encoded).
350    pub context: u64,
351}
352
353/// K2K alert acknowledgment.
354///
355/// Type ID: 351
356#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
357#[message(type_id = 351)]
358#[archive(check_bytes)]
359pub struct K2KAlertAck {
360    /// Original message ID.
361    pub request_id: u64,
362    /// Acknowledging kernel ID (hashed).
363    pub kernel_id: u64,
364    /// Whether this kernel will take action.
365    pub will_action: bool,
366    /// Correlation evidence found.
367    pub correlation_found: bool,
368}
369
370/// K2K entity risk aggregation request.
371///
372/// Aggregates risk scores from multiple compliance kernels for an entity.
373///
374/// Type ID: 352
375#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
376#[message(type_id = 352)]
377#[archive(check_bytes)]
378pub struct K2KEntityRiskRequest {
379    /// Message ID.
380    #[message(id)]
381    pub id: MessageId,
382    /// Requesting kernel ID.
383    pub source_kernel: u64,
384    /// Entity ID to assess.
385    pub entity_id: u64,
386    /// Time window (microseconds) for aggregation.
387    pub time_window_us: u64,
388}
389
390/// K2K entity risk response.
391///
392/// Type ID: 353
393#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
394#[message(type_id = 353)]
395#[archive(check_bytes)]
396pub struct K2KEntityRiskResponse {
397    /// Original message ID.
398    pub request_id: u64,
399    /// Responding kernel ID.
400    pub kernel_id: u64,
401    /// Entity ID.
402    pub entity_id: u64,
403    /// Risk score from this kernel (0-100).
404    pub risk_score: u8,
405    /// Number of alerts in time window.
406    pub alert_count: u32,
407    /// Highest severity alert (1-5).
408    pub max_severity: u8,
409    /// Pattern types detected (bitmask).
410    pub patterns_detected: u64,
411}
412
413/// K2K aggregated entity risk.
414///
415/// Final aggregated risk after collecting from all kernels.
416///
417/// Type ID: 354
418#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
419#[message(type_id = 354)]
420#[archive(check_bytes)]
421pub struct K2KAggregatedRisk {
422    /// Message ID.
423    #[message(id)]
424    pub id: MessageId,
425    /// Entity ID.
426    pub entity_id: u64,
427    /// Aggregated risk score (weighted average, 0-100).
428    pub aggregated_score: u8,
429    /// Number of kernels contributing.
430    pub kernel_count: u8,
431    /// Total alerts across all kernels.
432    pub total_alerts: u32,
433    /// Recommendation: 0=none, 1=monitor, 2=escalate, 3=block.
434    pub recommendation: u8,
435    /// Timestamp of aggregation.
436    pub timestamp: u64,
437}
438
439/// K2K cross-kernel case creation.
440///
441/// When multiple kernels detect related suspicious activity, create a unified case.
442///
443/// Type ID: 355
444#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
445#[message(type_id = 355)]
446#[archive(check_bytes)]
447pub struct K2KCaseCreation {
448    /// Message ID.
449    #[message(id)]
450    pub id: MessageId,
451    /// Case ID.
452    pub case_id: u64,
453    /// Primary entity ID.
454    pub entity_id: u64,
455    /// Related entity IDs (up to 4).
456    pub related_entities: [u64; 4],
457    /// Number of related entities.
458    pub related_count: u8,
459    /// Contributing kernel IDs (up to 4).
460    pub kernel_ids: [u64; 4],
461    /// Number of contributing kernels.
462    pub kernel_count: u8,
463    /// Total risk score.
464    pub total_risk: u8,
465    /// Case priority: 1=low, 2=medium, 3=high, 4=critical.
466    pub priority: u8,
467    /// Creation timestamp.
468    pub timestamp: u64,
469}
470
471/// K2K case update.
472///
473/// Type ID: 356
474#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
475#[message(type_id = 356)]
476#[archive(check_bytes)]
477pub struct K2KCaseUpdate {
478    /// Message ID.
479    #[message(id)]
480    pub id: MessageId,
481    /// Case ID.
482    pub case_id: u64,
483    /// Updating kernel ID.
484    pub kernel_id: u64,
485    /// Update type: 1=new_evidence, 2=risk_change, 3=status_change.
486    pub update_type: u8,
487    /// New risk score (if applicable).
488    pub new_risk: u8,
489    /// New evidence transaction IDs (up to 4).
490    pub evidence: [u64; 4],
491    /// Evidence count.
492    pub evidence_count: u8,
493    /// Update timestamp.
494    pub timestamp: u64,
495}
496
497/// K2K sanctions match notification.
498///
499/// When SanctionsScreening finds a match, notify other kernels.
500///
501/// Type ID: 357
502#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
503#[message(type_id = 357)]
504#[archive(check_bytes)]
505pub struct K2KSanctionsMatch {
506    /// Message ID.
507    #[message(id)]
508    pub id: MessageId,
509    /// Entity ID with sanctions match.
510    pub entity_id: u64,
511    /// Match type: 1=exact, 2=fuzzy, 3=alias.
512    pub match_type: u8,
513    /// Match confidence (0-100).
514    pub confidence: u8,
515    /// Sanctions list code.
516    pub list_code: u32,
517    /// Entry ID on sanctions list.
518    pub list_entry_id: u64,
519    /// Detection timestamp.
520    pub timestamp: u64,
521}
522
523/// K2K request to freeze entity across all kernels.
524///
525/// Type ID: 358
526#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
527#[message(type_id = 358)]
528#[archive(check_bytes)]
529pub struct K2KFreezeRequest {
530    /// Message ID.
531    #[message(id)]
532    pub id: MessageId,
533    /// Requesting kernel ID.
534    pub source_kernel: u64,
535    /// Entity ID to freeze.
536    pub entity_id: u64,
537    /// Reason code.
538    pub reason: u16,
539    /// Duration (0 = indefinite, otherwise seconds).
540    pub duration_secs: u32,
541    /// Request timestamp.
542    pub timestamp: u64,
543}
544
545/// K2K freeze acknowledgment.
546///
547/// Type ID: 359
548#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
549#[message(type_id = 359)]
550#[archive(check_bytes)]
551pub struct K2KFreezeAck {
552    /// Original message ID.
553    pub request_id: u64,
554    /// Acknowledging kernel ID.
555    pub kernel_id: u64,
556    /// Entity ID.
557    pub entity_id: u64,
558    /// Whether freeze was applied.
559    pub frozen: bool,
560    /// Transactions blocked since freeze.
561    pub blocked_count: u32,
562}
563
564// ============================================================================
565// Common Ring Types
566// ============================================================================
567
568/// Alert severity levels.
569#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, RkyvSerialize, RkyvDeserialize)]
570#[archive(check_bytes)]
571#[repr(u8)]
572pub enum AlertSeverity {
573    /// Low severity.
574    Low = 1,
575    /// Medium severity.
576    Medium = 2,
577    /// High severity.
578    High = 3,
579    /// Critical severity.
580    Critical = 4,
581    /// Emergency.
582    Emergency = 5,
583}
584
585/// AML pattern types.
586#[derive(Debug, Clone, Copy, PartialEq, Eq, Archive, RkyvSerialize, RkyvDeserialize)]
587#[archive(check_bytes)]
588#[repr(u16)]
589pub enum AMLPatternType {
590    /// Structuring/smurfing.
591    Structuring = 1,
592    /// Layering.
593    Layering = 2,
594    /// Circular flow.
595    CircularFlow = 3,
596    /// Rapid movement.
597    RapidMovement = 4,
598    /// Unusual volume.
599    UnusualVolume = 5,
600    /// Geographic anomaly.
601    GeographicAnomaly = 6,
602    /// Timing anomaly.
603    TimingAnomaly = 7,
604    /// Counterparty risk.
605    CounterpartyRisk = 8,
606}
607
608#[cfg(test)]
609mod tests {
610    use super::*;
611
612    #[test]
613    fn test_monitor_transaction_ring() {
614        let msg = MonitorTransactionRing::new(1, 100, 200, 1000.0, 1234567890, 1, "USD");
615        assert_eq!(msg.tx_id, 1);
616        assert_eq!(msg.source_id, 100);
617        assert_eq!(msg.dest_id, 200);
618        assert_eq!(msg.amount, 100_000_000_000); // 1000 * 10^8
619    }
620
621    #[test]
622    fn test_add_graph_edge_ring() {
623        let msg = AddGraphEdgeRing::new(1, 2, 500.0, 1234567890);
624        assert_eq!(msg.source_id, 1);
625        assert_eq!(msg.dest_id, 2);
626        assert_eq!(msg.amount, 50_000_000_000); // 500 * 10^8
627    }
628
629    // K2K Cross-Compliance Tests
630
631    #[test]
632    fn test_k2k_alert_broadcast() {
633        let msg = K2KAlertBroadcast {
634            id: MessageId(1),
635            source_kernel: 12345,
636            alert_type: 2, // cycle
637            severity: 4,
638            entity_id: 100,
639            related_entity_id: 200,
640            risk_score: 85,
641            timestamp: 1234567890,
642            context: 0,
643        };
644        assert_eq!(msg.alert_type, 2);
645        assert_eq!(msg.severity, 4);
646        assert_eq!(msg.risk_score, 85);
647    }
648
649    #[test]
650    fn test_k2k_entity_risk_request() {
651        let msg = K2KEntityRiskRequest {
652            id: MessageId(2),
653            source_kernel: 111,
654            entity_id: 500,
655            time_window_us: 3_600_000_000, // 1 hour
656        };
657        assert_eq!(msg.entity_id, 500);
658        assert_eq!(msg.time_window_us, 3_600_000_000);
659    }
660
661    #[test]
662    fn test_k2k_aggregated_risk() {
663        let msg = K2KAggregatedRisk {
664            id: MessageId(3),
665            entity_id: 500,
666            aggregated_score: 75,
667            kernel_count: 4,
668            total_alerts: 12,
669            recommendation: 2, // escalate
670            timestamp: 1234567890,
671        };
672        assert_eq!(msg.aggregated_score, 75);
673        assert_eq!(msg.kernel_count, 4);
674        assert_eq!(msg.recommendation, 2);
675    }
676
677    #[test]
678    fn test_k2k_case_creation() {
679        let msg = K2KCaseCreation {
680            id: MessageId(4),
681            case_id: 9001,
682            entity_id: 100,
683            related_entities: [200, 300, 0, 0],
684            related_count: 2,
685            kernel_ids: [111, 222, 333, 0],
686            kernel_count: 3,
687            total_risk: 90,
688            priority: 3, // high
689            timestamp: 1234567890,
690        };
691        assert_eq!(msg.case_id, 9001);
692        assert_eq!(msg.related_count, 2);
693        assert_eq!(msg.kernel_count, 3);
694        assert_eq!(msg.priority, 3);
695    }
696
697    #[test]
698    fn test_k2k_sanctions_match() {
699        let msg = K2KSanctionsMatch {
700            id: MessageId(5),
701            entity_id: 999,
702            match_type: 1, // exact
703            confidence: 98,
704            list_code: 1, // OFAC SDN
705            list_entry_id: 12345,
706            timestamp: 1234567890,
707        };
708        assert_eq!(msg.entity_id, 999);
709        assert_eq!(msg.match_type, 1);
710        assert_eq!(msg.confidence, 98);
711    }
712
713    #[test]
714    fn test_k2k_freeze_request() {
715        let msg = K2KFreezeRequest {
716            id: MessageId(6),
717            source_kernel: 111,
718            entity_id: 999,
719            reason: 1,        // sanctions match
720            duration_secs: 0, // indefinite
721            timestamp: 1234567890,
722        };
723        assert_eq!(msg.entity_id, 999);
724        assert_eq!(msg.duration_secs, 0);
725    }
726
727    #[test]
728    fn test_k2k_freeze_ack() {
729        let msg = K2KFreezeAck {
730            request_id: 6,
731            kernel_id: 222,
732            entity_id: 999,
733            frozen: true,
734            blocked_count: 3,
735        };
736        assert!(msg.frozen);
737        assert_eq!(msg.blocked_count, 3);
738    }
739}