peat-protocol 0.9.0-rc.8

Peat Coordination Protocol — hierarchical capability composition over CRDTs for heterogeneous mesh networks
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
//! QoS Registry for policy lookups (ADR-019)
//!
//! Provides a centralized registry for QoS policy management, enabling
//! custom policy overrides and runtime configuration.

use super::classification::DataType;
use super::context_manager::ContextManager;
use super::{QoSClass, QoSPolicy};
use std::collections::HashMap;

/// QoS policy registry with customizable per-data-type policies
///
/// The registry maintains a mapping from data types to QoS policies,
/// allowing for runtime customization while providing sensible defaults.
#[derive(Debug, Clone)]
pub struct QoSRegistry {
    /// Custom policy overrides (data type -> policy)
    overrides: HashMap<DataType, QoSPolicy>,
}

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

impl QoSRegistry {
    /// Create an empty registry (uses default policies for all data types)
    pub fn new() -> Self {
        Self {
            overrides: HashMap::new(),
        }
    }

    /// Create a registry with default military operational policies
    ///
    /// This is the recommended starting point for tactical operations.
    /// All data types use their default policies from `DataType::default_policy()`.
    pub fn default_military() -> Self {
        Self::new()
    }

    /// Create a registry optimized for low-bandwidth conditions
    ///
    /// Reduces max sizes and increases latency tolerances.
    pub fn low_bandwidth() -> Self {
        let mut registry = Self::new();

        // Reduce image/media sizes for low bandwidth
        registry.override_policy(
            DataType::TargetImage,
            QoSPolicy {
                base_class: QoSClass::High,
                max_latency_ms: Some(30_000), // 30s instead of 5s
                max_size_bytes: Some(2 * 1024 * 1024), // 2MB instead of 10MB
                ttl_seconds: Some(7200),
                retention_priority: 4,
                preemptable: true,
            },
        );

        registry.override_policy(
            DataType::AudioIntercept,
            QoSPolicy {
                base_class: QoSClass::High,
                max_latency_ms: Some(30_000),
                max_size_bytes: Some(1024 * 1024), // 1MB instead of 5MB
                ttl_seconds: Some(7200),
                retention_priority: 4,
                preemptable: true,
            },
        );

        // Disable bulk transfers
        registry.override_policy(
            DataType::ModelUpdate,
            QoSPolicy {
                base_class: QoSClass::Bulk,
                max_latency_ms: None,
                max_size_bytes: Some(50 * 1024 * 1024), // 50MB instead of 500MB
                ttl_seconds: Some(86400),
                retention_priority: 1,
                preemptable: true,
            },
        );

        registry
    }

    /// Create a registry optimized for high-priority operations
    ///
    /// Tighter latency requirements, more bandwidth for critical data.
    pub fn high_priority() -> Self {
        let mut registry = Self::new();

        // Tighter latency for contact reports
        registry.override_policy(
            DataType::ContactReport,
            QoSPolicy {
                base_class: QoSClass::Critical,
                max_latency_ms: Some(250), // 250ms instead of 500ms
                max_size_bytes: Some(64 * 1024),
                ttl_seconds: None,
                retention_priority: 5,
                preemptable: false,
            },
        );

        // Promote mission retasking to critical
        registry.override_policy(
            DataType::MissionRetasking,
            QoSPolicy {
                base_class: QoSClass::Critical,
                max_latency_ms: Some(500),
                max_size_bytes: Some(64 * 1024),
                ttl_seconds: Some(7200),
                retention_priority: 5,
                preemptable: false,
            },
        );

        registry
    }

    /// Get the QoS policy for a data type
    ///
    /// Returns the custom policy if one exists, otherwise the default.
    pub fn get_policy(&self, data_type: DataType) -> QoSPolicy {
        self.overrides
            .get(&data_type)
            .cloned()
            .unwrap_or_else(|| data_type.default_policy())
    }

    /// Get the QoS class for a data type
    pub fn classify(&self, data_type: DataType) -> QoSClass {
        self.get_policy(data_type).base_class
    }

    /// Get the effective policy for a data type considering the current mission context
    ///
    /// This applies context-aware adjustments to the base policy, enabling
    /// dynamic priority changes based on mission phase.
    pub fn get_effective_policy(
        &self,
        data_type: DataType,
        context_manager: &ContextManager,
    ) -> QoSPolicy {
        let base = self.get_policy(data_type.clone());
        context_manager.adjust_policy(&base, &data_type)
    }

    /// Get the effective QoS class for a data type in the current context
    ///
    /// This is a convenience method that combines registry lookup with context adjustment.
    pub fn classify_with_context(
        &self,
        data_type: DataType,
        context_manager: &ContextManager,
    ) -> QoSClass {
        self.get_effective_policy(data_type, context_manager)
            .base_class
    }

    /// Override the policy for a specific data type
    pub fn override_policy(&mut self, data_type: DataType, policy: QoSPolicy) {
        self.overrides.insert(data_type, policy);
    }

    /// Remove a custom policy override, reverting to default
    pub fn clear_override(&mut self, data_type: &DataType) {
        self.overrides.remove(data_type);
    }

    /// Clear all custom policy overrides
    pub fn clear_all_overrides(&mut self) {
        self.overrides.clear();
    }

    /// Check if a data type has a custom policy override
    pub fn has_override(&self, data_type: &DataType) -> bool {
        self.overrides.contains_key(data_type)
    }

    /// Get the number of custom policy overrides
    pub fn override_count(&self) -> usize {
        self.overrides.len()
    }

    /// Get all data types with custom overrides
    pub fn overridden_types(&self) -> impl Iterator<Item = &DataType> {
        self.overrides.keys()
    }

    /// Check if a message with given characteristics meets the policy requirements
    pub fn meets_requirements(
        &self,
        data_type: DataType,
        latency_ms: Option<u64>,
        size_bytes: Option<usize>,
    ) -> bool {
        let policy = self.get_policy(data_type);

        // Check latency constraint
        if let (Some(max), Some(actual)) = (policy.max_latency_ms, latency_ms) {
            if actual > max {
                return false;
            }
        }

        // Check size constraint
        if let (Some(max), Some(actual)) = (policy.max_size_bytes, size_bytes) {
            if actual > max {
                return false;
            }
        }

        true
    }

    /// Get all policies grouped by QoS class
    pub fn policies_by_class(&self) -> HashMap<QoSClass, Vec<(DataType, QoSPolicy)>> {
        let mut result: HashMap<QoSClass, Vec<(DataType, QoSPolicy)>> = HashMap::new();

        for dt in DataType::all_predefined() {
            let policy = self.get_policy(dt.clone());
            result
                .entry(policy.base_class)
                .or_default()
                .push((dt.clone(), policy));
        }

        result
    }
}

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

    #[test]
    fn test_registry_default() {
        let registry = QoSRegistry::default();

        // Should use default policies
        let policy = registry.get_policy(DataType::ContactReport);
        assert_eq!(policy.base_class, QoSClass::Critical);
        assert_eq!(policy.max_latency_ms, Some(500));
    }

    #[test]
    fn test_registry_classify() {
        let registry = QoSRegistry::default_military();

        assert_eq!(
            registry.classify(DataType::ContactReport),
            QoSClass::Critical
        );
        assert_eq!(registry.classify(DataType::TargetImage), QoSClass::High);
        assert_eq!(registry.classify(DataType::HealthStatus), QoSClass::Normal);
        assert_eq!(registry.classify(DataType::PositionUpdate), QoSClass::Low);
        assert_eq!(registry.classify(DataType::DebugLog), QoSClass::Bulk);
    }

    #[test]
    fn test_registry_override_policy() {
        let mut registry = QoSRegistry::new();

        // Override position update to be higher priority
        registry.override_policy(
            DataType::PositionUpdate,
            QoSPolicy {
                base_class: QoSClass::Normal,
                max_latency_ms: Some(30_000),
                max_size_bytes: Some(2048),
                ttl_seconds: Some(3600),
                retention_priority: 3,
                preemptable: true,
            },
        );

        // Check override applied
        let policy = registry.get_policy(DataType::PositionUpdate);
        assert_eq!(policy.base_class, QoSClass::Normal);
        assert_eq!(policy.max_latency_ms, Some(30_000));

        // Other types unchanged
        assert_eq!(
            registry.get_policy(DataType::ContactReport).base_class,
            QoSClass::Critical
        );
    }

    #[test]
    fn test_registry_clear_override() {
        let mut registry = QoSRegistry::new();

        registry.override_policy(DataType::PositionUpdate, QoSPolicy::high());

        assert!(registry.has_override(&DataType::PositionUpdate));
        assert_eq!(registry.override_count(), 1);

        registry.clear_override(&DataType::PositionUpdate);

        assert!(!registry.has_override(&DataType::PositionUpdate));
        assert_eq!(registry.override_count(), 0);

        // Should revert to default
        let policy = registry.get_policy(DataType::PositionUpdate);
        assert_eq!(policy.base_class, QoSClass::Low);
    }

    #[test]
    fn test_low_bandwidth_registry() {
        let registry = QoSRegistry::low_bandwidth();

        // Check reduced sizes
        let image_policy = registry.get_policy(DataType::TargetImage);
        assert_eq!(image_policy.max_size_bytes, Some(2 * 1024 * 1024));
        assert_eq!(image_policy.max_latency_ms, Some(30_000));

        let model_policy = registry.get_policy(DataType::ModelUpdate);
        assert_eq!(model_policy.max_size_bytes, Some(50 * 1024 * 1024));
    }

    #[test]
    fn test_high_priority_registry() {
        let registry = QoSRegistry::high_priority();

        // Contact report has tighter latency
        let contact_policy = registry.get_policy(DataType::ContactReport);
        assert_eq!(contact_policy.max_latency_ms, Some(250));

        // Mission retasking promoted to critical
        let retask_policy = registry.get_policy(DataType::MissionRetasking);
        assert_eq!(retask_policy.base_class, QoSClass::Critical);
    }

    #[test]
    fn test_meets_requirements() {
        let registry = QoSRegistry::default_military();

        // Contact report: max 500ms latency, 32KB size
        assert!(registry.meets_requirements(DataType::ContactReport, Some(400), Some(20_000)));
        assert!(!registry.meets_requirements(DataType::ContactReport, Some(600), Some(20_000)));
        assert!(!registry.meets_requirements(DataType::ContactReport, Some(400), Some(50_000)));

        // Bulk data has no latency constraint
        assert!(registry.meets_requirements(DataType::DebugLog, Some(1_000_000), None));
    }

    #[test]
    fn test_policies_by_class() {
        let registry = QoSRegistry::default_military();
        let by_class = registry.policies_by_class();

        // Should have entries for all 5 classes
        assert_eq!(by_class.len(), 5);

        // Critical should have 4 types
        let critical = by_class.get(&QoSClass::Critical).unwrap();
        assert_eq!(critical.len(), 4);
    }

    #[test]
    fn test_overridden_types() {
        let mut registry = QoSRegistry::new();
        registry.override_policy(DataType::HealthStatus, QoSPolicy::critical());
        registry.override_policy(DataType::Heartbeat, QoSPolicy::high());

        let overridden: Vec<_> = registry.overridden_types().collect();
        assert_eq!(overridden.len(), 2);
        assert!(overridden.contains(&&DataType::HealthStatus));
        assert!(overridden.contains(&&DataType::Heartbeat));
    }

    #[test]
    fn test_get_effective_policy_standby() {
        use super::super::context::MissionContext;

        let registry = QoSRegistry::default_military();
        let ctx_manager = ContextManager::with_context(MissionContext::Standby);

        // In standby, no adjustments - effective policy matches base
        let effective = registry.get_effective_policy(DataType::TargetImage, &ctx_manager);
        let base = registry.get_policy(DataType::TargetImage);

        assert_eq!(effective.base_class, base.base_class);
        assert_eq!(effective.max_latency_ms, base.max_latency_ms);
    }

    #[test]
    fn test_get_effective_policy_execution() {
        use super::super::context::MissionContext;

        let registry = QoSRegistry::default_military();
        let ctx_manager = ContextManager::with_context(MissionContext::Execution);

        // In execution, target images are elevated to P1
        let effective = registry.get_effective_policy(DataType::TargetImage, &ctx_manager);

        assert_eq!(effective.base_class, QoSClass::Critical);
    }

    #[test]
    fn test_get_effective_policy_emergency() {
        use super::super::context::MissionContext;

        let registry = QoSRegistry::default_military();
        let ctx_manager = ContextManager::with_context(MissionContext::Emergency);

        // In emergency, health status is elevated to critical
        let effective = registry.get_effective_policy(DataType::HealthStatus, &ctx_manager);

        assert_eq!(effective.base_class, QoSClass::Critical);
    }

    #[test]
    fn test_classify_with_context() {
        use super::super::context::MissionContext;

        let registry = QoSRegistry::default_military();
        let ctx_manager = ContextManager::with_context(MissionContext::Execution);

        // TargetImage: P2 base → P1 in execution
        assert_eq!(
            registry.classify_with_context(DataType::TargetImage, &ctx_manager),
            QoSClass::Critical
        );

        // ContactReport: P1 base → P1 (no change, already at max)
        assert_eq!(
            registry.classify_with_context(DataType::ContactReport, &ctx_manager),
            QoSClass::Critical
        );

        // DebugLog: P5 base → P5 (unchanged in execution)
        assert_eq!(
            registry.classify_with_context(DataType::DebugLog, &ctx_manager),
            QoSClass::Bulk
        );
    }

    #[test]
    fn test_effective_policy_with_override() {
        use super::super::context::MissionContext;

        let mut registry = QoSRegistry::new();
        let ctx_manager = ContextManager::with_context(MissionContext::Execution);

        // Override PositionUpdate to be High priority
        registry.override_policy(
            DataType::PositionUpdate,
            QoSPolicy {
                base_class: QoSClass::High,
                max_latency_ms: Some(1000),
                max_size_bytes: Some(1024),
                ttl_seconds: Some(60),
                retention_priority: 4,
                preemptable: true,
            },
        );

        // Now get effective policy - should apply context adjustment to our override
        let effective = registry.get_effective_policy(DataType::PositionUpdate, &ctx_manager);

        // Our override set it to High (P2), and execution profile elevates position
        // updates, so we should see the context-adjusted result
        assert!(effective.base_class <= QoSClass::High);
    }
}