Skip to main content

web4_core/
coherence.rs

1// Copyright (c) 2026 MetaLINXX Inc.
2// SPDX-License-Identifier: AGPL-3.0-or-later
3//
4// This software is covered by US Patents 11,477,027 and 12,278,913,
5// and pending application 19/178,619. See PATENTS.md for details.
6
7//! Identity Coherence Implementation
8//!
9//! Identity coherence measures how well an entity maintains consistent identity
10//! over time. The formula is:
11//!
12//!   Coherence = C × S × Φ × R
13//!
14//! Where:
15//! - C (Continuity): Temporal consistency of identity
16//! - S (Stability): Resistance to perturbation
17//! - Φ (Phi): Information integration (IIT-inspired)
18//! - R (Reachability): Connection to the trust network
19//!
20//! Coherence is multiplicative because all factors are necessary:
21//! a zero in any dimension zeros the total coherence.
22
23use crate::error::{Result, Web4Error};
24use crate::lct::{EntityType, Lct};
25use serde::{Deserialize, Serialize};
26use uuid::Uuid;
27
28/// Identity coherence score
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct Coherence {
31    /// Continuity factor (0.0 to 1.0)
32    /// Measures temporal consistency of identity
33    pub continuity: f64,
34
35    /// Stability factor (0.0 to 1.0)
36    /// Measures resistance to perturbation
37    pub stability: f64,
38
39    /// Phi factor (0.0 to 1.0)
40    /// Measures information integration (inspired by IIT)
41    pub phi: f64,
42
43    /// Reachability factor (0.0 to 1.0)
44    /// Measures connection to the trust network
45    pub reachability: f64,
46}
47
48impl Default for Coherence {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl Coherence {
55    /// Create a new coherence score with neutral values
56    pub fn new() -> Self {
57        Self {
58            continuity: 0.5,
59            stability: 0.5,
60            phi: 0.5,
61            reachability: 0.5,
62        }
63    }
64
65    /// Create with specific values
66    pub fn with_values(continuity: f64, stability: f64, phi: f64, reachability: f64) -> Result<Self> {
67        for (name, value) in [
68            ("continuity", continuity),
69            ("stability", stability),
70            ("phi", phi),
71            ("reachability", reachability),
72        ] {
73            if !(0.0..=1.0).contains(&value) {
74                return Err(Web4Error::InvalidInput(format!(
75                    "{} must be in range [0.0, 1.0]",
76                    name
77                )));
78            }
79        }
80        Ok(Self {
81            continuity,
82            stability,
83            phi,
84            reachability,
85        })
86    }
87
88    /// Compute total coherence score (C × S × Φ × R)
89    pub fn total(&self) -> f64 {
90        self.continuity * self.stability * self.phi * self.reachability
91    }
92
93    /// Check if coherence meets a threshold
94    pub fn meets_threshold(&self, threshold: f64) -> bool {
95        self.total() >= threshold
96    }
97
98    /// Get the limiting factor (lowest component)
99    pub fn limiting_factor(&self) -> (&'static str, f64) {
100        let factors = [
101            ("continuity", self.continuity),
102            ("stability", self.stability),
103            ("phi", self.phi),
104            ("reachability", self.reachability),
105        ];
106        *factors.iter().min_by(|a, b| a.1.partial_cmp(&b.1).unwrap()).unwrap()
107    }
108}
109
110/// Coherence calculator for an LCT
111pub struct CoherenceCalculator {
112    /// Time window for continuity calculation (in seconds)
113    continuity_window: u64,
114
115    /// Minimum interactions for stability calculation
116    min_interactions: u64,
117
118    /// Network depth for reachability calculation
119    network_depth: u32,
120}
121
122impl Default for CoherenceCalculator {
123    fn default() -> Self {
124        Self {
125            continuity_window: 86400 * 30, // 30 days
126            min_interactions: 10,
127            network_depth: 3,
128        }
129    }
130}
131
132impl CoherenceCalculator {
133    /// Create a new calculator with custom parameters
134    pub fn new(continuity_window: u64, min_interactions: u64, network_depth: u32) -> Self {
135        Self {
136            continuity_window,
137            min_interactions,
138            network_depth,
139        }
140    }
141
142    /// Calculate continuity from activity history
143    ///
144    /// High continuity = consistent presence over time
145    /// Low continuity = sporadic or inconsistent activity
146    pub fn calculate_continuity(&self, activity_timestamps: &[i64]) -> f64 {
147        if activity_timestamps.is_empty() {
148            return 0.0;
149        }
150        if activity_timestamps.len() == 1 {
151            return 0.1;
152        }
153
154        let mut timestamps: Vec<i64> = activity_timestamps.to_vec();
155        timestamps.sort_unstable();
156
157        // Calculate gap distribution
158        let gaps: Vec<i64> = timestamps.windows(2).map(|w| w[1] - w[0]).collect();
159        let avg_gap = gaps.iter().sum::<i64>() as f64 / gaps.len() as f64;
160        let expected_gap = self.continuity_window as f64 / activity_timestamps.len() as f64;
161
162        // Continuity is higher when gaps are consistent and not too long
163        let gap_consistency = 1.0 / (1.0 + (avg_gap / expected_gap - 1.0).abs());
164
165        // Also consider total coverage of the window
166        let total_span = (timestamps.last().unwrap() - timestamps.first().unwrap()) as f64;
167        let coverage = (total_span / self.continuity_window as f64).min(1.0);
168
169        (gap_consistency * 0.7 + coverage * 0.3).min(1.0)
170    }
171
172    /// Calculate stability from interaction variance
173    ///
174    /// High stability = consistent behavior patterns
175    /// Low stability = erratic or unpredictable behavior
176    pub fn calculate_stability(&self, interaction_scores: &[f64]) -> f64 {
177        if interaction_scores.len() < self.min_interactions as usize {
178            // Not enough data for stability assessment
179            return 0.3;
180        }
181
182        let mean: f64 = interaction_scores.iter().sum::<f64>() / interaction_scores.len() as f64;
183        let variance: f64 = interaction_scores
184            .iter()
185            .map(|x| (x - mean).powi(2))
186            .sum::<f64>()
187            / interaction_scores.len() as f64;
188        let std_dev = variance.sqrt();
189
190        // Low variance = high stability
191        // Using logistic function to map std_dev to stability
192        1.0 / (1.0 + (std_dev * 5.0).exp())
193    }
194
195    /// Calculate phi (information integration)
196    ///
197    /// Inspired by Integrated Information Theory.
198    /// Measures how much the entity's outputs depend on integrated processing
199    /// rather than simple input-output mapping.
200    ///
201    /// For practical purposes, we approximate this using:
202    /// - Context sensitivity (same input, different context = different output)
203    /// - Cross-reference density (outputs reference multiple inputs)
204    pub fn calculate_phi(&self, context_sensitivity: f64, cross_reference_density: f64) -> f64 {
205        // Both factors contribute to phi
206        // High phi = high integration of information
207        ((context_sensitivity + cross_reference_density) / 2.0).min(1.0).max(0.0)
208    }
209
210    /// Calculate reachability in the trust network
211    ///
212    /// High reachability = well-connected to trusted entities
213    /// Low reachability = isolated or poorly connected
214    pub fn calculate_reachability(
215        &self,
216        direct_connections: u32,
217        indirect_connections: u32,
218        max_connections: u32,
219    ) -> f64 {
220        if max_connections == 0 {
221            return 0.0;
222        }
223
224        // Direct connections are weighted more heavily
225        let direct_weight = 0.7;
226        let indirect_weight = 0.3;
227
228        let direct_ratio = (direct_connections as f64 / max_connections as f64).min(1.0);
229        let indirect_ratio = (indirect_connections as f64 / (max_connections * self.network_depth) as f64).min(1.0);
230
231        direct_weight * direct_ratio + indirect_weight * indirect_ratio
232    }
233
234    /// Calculate full coherence score for an entity
235    pub fn calculate(&self, params: &CoherenceParams) -> Coherence {
236        let continuity = self.calculate_continuity(&params.activity_timestamps);
237        let stability = self.calculate_stability(&params.interaction_scores);
238        let phi = self.calculate_phi(params.context_sensitivity, params.cross_reference_density);
239        let reachability = self.calculate_reachability(
240            params.direct_connections,
241            params.indirect_connections,
242            params.max_connections,
243        );
244
245        Coherence {
246            continuity,
247            stability,
248            phi,
249            reachability,
250        }
251    }
252}
253
254/// Parameters for coherence calculation
255#[derive(Clone, Debug, Default)]
256pub struct CoherenceParams {
257    /// Unix timestamps of activity events
258    pub activity_timestamps: Vec<i64>,
259
260    /// Scores from past interactions (0.0 to 1.0)
261    pub interaction_scores: Vec<f64>,
262
263    /// Context sensitivity measure (0.0 to 1.0)
264    pub context_sensitivity: f64,
265
266    /// Cross-reference density (0.0 to 1.0)
267    pub cross_reference_density: f64,
268
269    /// Number of direct trust connections
270    pub direct_connections: u32,
271
272    /// Number of indirect (transitive) trust connections
273    pub indirect_connections: u32,
274
275    /// Maximum expected connections for normalization
276    pub max_connections: u32,
277}
278
279/// Coherence threshold requirements by entity type
280pub fn coherence_threshold_for_entity(entity_type: &EntityType) -> f64 {
281    match entity_type {
282        EntityType::Human => 0.5,        // Body-bound identity helps
283        EntityType::AiEmbodied => 0.6,   // Hardware binding helps
284        EntityType::AiSoftware => 0.7,   // Higher bar due to copyability
285        EntityType::Organization => 0.5,
286        EntityType::Role => 0.5,
287        EntityType::Task => 0.3,
288        EntityType::Resource => 0.3,
289        EntityType::Hybrid => 0.6,
290    }
291}
292
293/// Check if an LCT meets coherence requirements
294pub fn check_coherence(lct: &Lct, coherence: &Coherence) -> Result<()> {
295    let threshold = coherence_threshold_for_entity(&lct.entity_type);
296
297    // Also apply hardware binding ceiling
298    let effective_threshold = threshold.max(1.0 - lct.trust_ceiling());
299
300    if coherence.total() < effective_threshold {
301        return Err(Web4Error::CoherenceBelowThreshold {
302            score: coherence.total(),
303            threshold: effective_threshold,
304        });
305    }
306
307    Ok(())
308}
309
310/// Coherence event for tracking history
311#[derive(Clone, Debug, Serialize, Deserialize)]
312pub struct CoherenceEvent {
313    /// The entity's LCT ID
314    pub entity_id: Uuid,
315
316    /// Coherence score at this point
317    pub coherence: Coherence,
318
319    /// Timestamp of measurement
320    pub timestamp: chrono::DateTime<chrono::Utc>,
321
322    /// Optional context/reason
323    pub context: Option<String>,
324}
325
326impl CoherenceEvent {
327    /// Create a new coherence event
328    pub fn new(entity_id: Uuid, coherence: Coherence) -> Self {
329        Self {
330            entity_id,
331            coherence,
332            timestamp: chrono::Utc::now(),
333            context: None,
334        }
335    }
336
337    /// Create with context
338    pub fn with_context(entity_id: Uuid, coherence: Coherence, context: impl Into<String>) -> Self {
339        Self {
340            entity_id,
341            coherence,
342            timestamp: chrono::Utc::now(),
343            context: Some(context.into()),
344        }
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_coherence_total() {
354        let c = Coherence::with_values(0.8, 0.8, 0.8, 0.8).unwrap();
355        let total = c.total();
356        assert!((total - 0.4096).abs() < 0.001); // 0.8^4 = 0.4096
357    }
358
359    #[test]
360    fn test_zero_factor_zeros_total() {
361        let c = Coherence::with_values(0.9, 0.9, 0.0, 0.9).unwrap();
362        assert_eq!(c.total(), 0.0);
363    }
364
365    #[test]
366    fn test_limiting_factor() {
367        let c = Coherence::with_values(0.9, 0.5, 0.8, 0.7).unwrap();
368        let (name, value) = c.limiting_factor();
369        assert_eq!(name, "stability");
370        assert_eq!(value, 0.5);
371    }
372
373    #[test]
374    fn test_continuity_calculation() {
375        let calc = CoherenceCalculator::default();
376
377        // Regular activity = high continuity
378        let regular: Vec<i64> = (0..30).map(|i| i * 86400).collect();
379        let continuity = calc.calculate_continuity(&regular);
380        assert!(continuity > 0.5);
381
382        // Empty = zero continuity
383        assert_eq!(calc.calculate_continuity(&[]), 0.0);
384    }
385
386    #[test]
387    fn test_stability_calculation() {
388        let calc = CoherenceCalculator::default();
389
390        // Consistent scores = higher stability
391        let consistent: Vec<f64> = vec![0.8, 0.82, 0.79, 0.81, 0.8, 0.78, 0.81, 0.79, 0.8, 0.82];
392        let stability = calc.calculate_stability(&consistent);
393        assert!(stability > 0.4); // Consistent data should have reasonable stability
394
395        // Erratic scores = lower stability
396        let erratic: Vec<f64> = vec![0.1, 0.9, 0.2, 0.8, 0.3, 0.7, 0.15, 0.85, 0.25, 0.75];
397        let stability_erratic = calc.calculate_stability(&erratic);
398        assert!(stability_erratic < stability); // Erratic should be less stable
399    }
400
401    #[test]
402    fn test_entity_thresholds() {
403        assert_eq!(coherence_threshold_for_entity(&EntityType::Human), 0.5);
404        assert_eq!(coherence_threshold_for_entity(&EntityType::AiSoftware), 0.7);
405        assert_eq!(coherence_threshold_for_entity(&EntityType::Task), 0.3);
406    }
407
408    #[test]
409    fn test_coherence_check() {
410        let (lct, _) = Lct::new(EntityType::Human, None);
411
412        // High coherence passes
413        let high = Coherence::with_values(0.9, 0.9, 0.9, 0.9).unwrap();
414        assert!(check_coherence(&lct, &high).is_ok());
415
416        // Low coherence fails
417        let low = Coherence::with_values(0.5, 0.5, 0.5, 0.5).unwrap();
418        assert!(check_coherence(&lct, &low).is_err());
419    }
420}