//! `native` — retículo derivado del merge del CRDT (T8a, Addendum §B). //! //! Mata la circularidad de T5–T7: el orden **no** se pone a mano ni se lee de la //! etiqueta monótono/reset, se deriva mecánicamente de la propia fusión: //! ```text //! join(a, b) := merge_del_CRDT(a, b) // la fusión que el tipo YA implementa //! a ≤ b := ( merge(a, b) == b ) // orden inducido por el merge //! ``` //! Con esta regla es imposible filtrar la respuesta de CALM. La difusión es una //! **propagación de joins** (gossip) al punto fijo: el merge de cada componente //! conexa. Sin invariante, un CRDT siempre reconcilia → corre libre (T8c añade //! el invariante que sí puede forzar coordinación). use crate::cell::{Cell, GCounter, ResettableRegister}; /// Un estado con una fusión CRDT y el orden que ésta induce. pub trait Merge: Clone + PartialEq { /// La fusión del CRDT (conmutativa, asociativa, idempotente). fn merge(&self, other: &Self) -> Self; /// Orden inducido por el merge: `a ≤ b ⟺ merge(a, b) == b`. fn leq(&self, other: &Self) -> bool { &self.merge(other) == other } } // Los CRDT del MVP ya traen su merge en `Cell::join`; lo reutilizamos tal cual. // (Usamos SOLO la fusión, nunca `is_monotone`: ahí estaba la circularidad.) impl Merge for GCounter { fn merge(&self, other: &Self) -> Self { Cell::join(self, other) } } impl Merge for ResettableRegister { fn merge(&self, other: &Self) -> Self { Cell::join(self, other) } } /// Propaga joins por las aristas hasta el punto fijo (Kleene ascendente sobre el /// orden del merge). El resultado en cada vértice es el merge de su componente /// conexa. Converge para estados de altura finita. pub fn reconcile(edges: &[(usize, usize)], seed: &[M]) -> Vec { let mut x = seed.to_vec(); let mut changed = true; let mut guard = 0usize; while changed { changed = false; for &(a, b) in edges { let m = x[a].merge(&x[b]); if x[a] != m { x[a] = m.clone(); changed = true; } if x[b] != m { x[b] = m; changed = true; } } guard += 1; assert!(guard <= 100_000, "reconcile no convergió (¿merge no idempotente?)"); } x } #[cfg(test)] mod tests { use super::*; #[test] fn orden_de_gcounter_es_componente_a_componente() { let a = GCounter { slots: vec![3, 0] }; let b = GCounter { slots: vec![1, 5] }; assert!(!a.leq(&b) && !b.leq(&a), "incomparables por casillas cruzadas"); let c = GCounter { slots: vec![3, 5] }; assert!(a.leq(&c) && b.leq(&c), "el join domina a ambos"); } #[test] fn orden_de_lww_es_una_cadena() { // El registro LWW (reset = mayor generación) es una CADENA: todo par // es comparable. Por eso su difusión NUNCA colapsa → corre libre. let viejo = ResettableRegister::new(10); // gen 0, value 10 let mut reset = viejo.clone(); reset.reset(0); // gen 1, value 0 (valor MENOR, pero generación mayor) // Comparables pese a que el valor baja: viejo ≤ reset (gana la generación). assert!(viejo.leq(&reset), "el reset domina en el orden del merge"); assert!(!reset.leq(&viejo)); } #[test] fn reconcile_lww_llega_al_maximo_de_la_cadena() { // Tres réplicas en ciclo, una reseteó. Bajo el orden nativo reconcilian // al elemento máximo de la cadena; sin invariante, no hay obstrucción. let seed = vec![ ResettableRegister::new(10), ResettableRegister::new(10), { let mut r = ResettableRegister::new(10); r.reset(7); r }, ]; let fix = reconcile(&[(0, 1), (1, 2), (2, 0)], &seed); // Todos convergen al mismo estado (el máximo): gen 1, value 7. assert!(fix.iter().all(|s| *s == fix[0]), "reconcilia a un estado común"); assert_eq!(fix[0].generation, 1); } }