tawasuyu/hifas
Motor en Rust para decidir, con demostración y no a ojo, cuándo un conjunto de réplicas local-first puede operar sin coordinación y cuándo no — y, cuando la respuesta es "sí, dentro de límites", proveer el mecanismo que lo garantiza.
T2: CRDTs GCounter/ResettableRegister + hito M1 de punta a punta
1//! Binario de `sheafsync`. Hito M1 (§8): dos corridas de signo opuesto que
2//! prueban el núcleo entero de punta a punta.
3//!
4//! - Corrida A (G-Counter, monótono): compartición acíclica → `H¹ = 0` → corre libre.
5//! - Corrida B (ResettableRegister, no monótono): el reset introduce un enlace
6//! irreconciliable sobre el dato ya compartido por las réplicas 1 y 2 → ciclo
7//! localizado → `H¹ ≠ 0` → coordina ahí.
8
9use sheafsync::cell::{Cell, GCounter, ResettableRegister};
10use sheafsync::cohomology::{self, Cohomology};
11use sheafsync::sheaf::Sheaf;
12use sheafsync::verdict::Verdict;
13
14fn main() {
15 println!("sheafsync — hito M1\n");
16 corrida_a();
17 println!();
18 corrida_b();
19}
20
21/// Imprime el veredicto de un haz ya construido.
22fn veredicto(nombre: &str, sheaf: &Sheaf) -> Cohomology {
23 let coho = cohomology::compute(sheaf).expect("haz bien formado");
24 let verdict = Verdict::diagnose(sheaf, &coho);
25 println!(
26 " cohomología: dim H⁰ = {}, dim H¹ = {} (rank δ = {}, dim C⁰ = {}, dim C¹ = {})",
27 coho.dim_h0, coho.dim_h1, coho.rank_delta, coho.dim_c0, coho.dim_c1
28 );
29 println!(" veredicto [{nombre}]: {verdict}");
30 coho
31}
32
33/// Corrida A — G-Counter monótono sobre 3 réplicas que comparten en camino.
34///
35/// Cada réplica incrementa solo su casilla y difunde el vector a sus vecinas.
36/// Al fundir por máximo siempre convergen; la compartición es acíclica, así que
37/// no hay obstrucción posible: `H¹ = 0`.
38fn corrida_a() {
39 println!("Corrida A — G-Counter (monótono: {})", GCounter::is_monotone());
40
41 // Demostración del CRDT: tres réplicas incrementan lo suyo y se funden.
42 let mut r0 = GCounter::zeros(3);
43 let mut r1 = GCounter::zeros(3);
44 let mut r2 = GCounter::zeros(3);
45 r0.increment(0, 5);
46 r1.increment(1, 3);
47 r2.increment(2, 7);
48 let fundido = r0.join(&r1).join(&r2);
49 println!(
50 " merge de las 3 réplicas: {:?} (valor total = {})",
51 fundido.slots,
52 fundido.value()
53 );
54
55 // Haz: stalk = el vector contador (ℤ³), restricciones = identidad (comparten
56 // el vector completo), topología = camino 0—1—2 (acíclica).
57 let sheaf = Sheaf::constant(3, &[(0, 1), (1, 2)], 3);
58 veredicto("A", &sheaf);
59}
60
61/// Corrida B — ResettableRegister no monótono sobre 3 réplicas en ciclo.
62///
63/// Las réplicas 1 y 2 comparten un registro. El reset (no monótono) impone un
64/// segundo enlace sobre ese mismo dato que no puede reconciliarse globalmente:
65/// forma un ciclo entre 1 y 2. El haz lo detecta y lo localiza ahí.
66fn corrida_b() {
67 println!(
68 "Corrida B — ResettableRegister (monótono: {})",
69 ResettableRegister::is_monotone()
70 );
71
72 // Demostración del CRDT: un reset con valor menor gana por generación.
73 let alto = ResettableRegister::new(10);
74 let mut reseteado = alto.clone();
75 reseteado.reset(0);
76 let fundido = alto.join(&reseteado);
77 println!(
78 " merge tras reset: value = {} (gen {}) — el reset retracta el 10 previo",
79 fundido.value, fundido.generation
80 );
81
82 // Haz: stalk = valor del registro (escalar), restricciones = identidad.
83 // Aristas: 0—1 (single) y un DOBLE enlace 1—2 (el original + el que fuerza el
84 // reset), irreconciliables → ciclo localizado entre réplicas 1 y 2.
85 let sheaf = Sheaf::constant(3, &[(0, 1), (1, 2), (1, 2)], 1);
86 veredicto("B", &sheaf);
87}