tawasuyu

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.

Rust 24Markdown 15TOML 1358 Definiciones

⎇ main src/main.rs Rust · 146 líneas · 5.6 KB Historia Quién tocó qué crudo

T8c: invariante, I-confluence y la discrepancia buena (versión fuerte) d2514b766 · Sergio ·

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::invariant::AccountConfig;
12use sheafsync::oracle::{Config, Share};
13use sheafsync::sheaf::Sheaf;
14use sheafsync::verdict::Verdict;
15
16fn main() {
17    println!("sheafsync — hito M1\n");
18    corrida_a();
19    println!();
20    corrida_b();
21    println!();
22    contraste_tarski();
23    println!();
24    contraste_invariante();
25}
26
27/// Imprime el veredicto de un haz ya construido.
28fn veredicto(nombre: &str, sheaf: &Sheaf) -> Cohomology {
29    let coho = cohomology::compute(sheaf).expect("haz bien formado");
30    let verdict = Verdict::diagnose(sheaf, &coho);
31    println!(
32        "  cohomología: dim H⁰ = {}, dim H¹ = {}  (rank δ = {}, dim C⁰ = {}, dim C¹ = {})",
33        coho.dim_h0, coho.dim_h1, coho.rank_delta, coho.dim_c0, coho.dim_c1
34    );
35    println!("  veredicto [{nombre}]: {verdict}");
36    coho
37}
38
39/// Corrida A — G-Counter monótono sobre 3 réplicas que comparten en camino.
40///
41/// Cada réplica incrementa solo su casilla y difunde el vector a sus vecinas.
42/// Al fundir por máximo siempre convergen; la compartición es acíclica, así que
43/// no hay obstrucción posible: `H¹ = 0`.
44fn corrida_a() {
45    println!("Corrida A — G-Counter (monótono: {})", GCounter::is_monotone());
46
47    // Demostración del CRDT: tres réplicas incrementan lo suyo y se funden.
48    let mut r0 = GCounter::zeros(3);
49    let mut r1 = GCounter::zeros(3);
50    let mut r2 = GCounter::zeros(3);
51    r0.increment(0, 5);
52    r1.increment(1, 3);
53    r2.increment(2, 7);
54    let fundido = r0.join(&r1).join(&r2);
55    println!(
56        "  merge de las 3 réplicas: {:?}  (valor total = {})",
57        fundido.slots,
58        fundido.value()
59    );
60
61    // Haz: stalk = el vector contador (ℤ³), restricciones = identidad (comparten
62    // el vector completo), topología = camino 0—1—2 (acíclica).
63    let sheaf = Sheaf::constant(3, &[(0, 1), (1, 2)], 3);
64    veredicto("A", &sheaf);
65}
66
67/// Corrida B — ResettableRegister no monótono sobre 3 réplicas en ciclo.
68///
69/// Las réplicas 1 y 2 comparten un registro. El reset (no monótono) impone un
70/// segundo enlace sobre ese mismo dato que no puede reconciliarse globalmente:
71/// forma un ciclo entre 1 y 2. El haz lo detecta y lo localiza ahí.
72fn corrida_b() {
73    println!(
74        "Corrida B — ResettableRegister (monótono: {})",
75        ResettableRegister::is_monotone()
76    );
77
78    // Demostración del CRDT: un reset con valor menor gana por generación.
79    let alto = ResettableRegister::new(10);
80    let mut reseteado = alto.clone();
81    reseteado.reset(0);
82    let fundido = alto.join(&reseteado);
83    println!(
84        "  merge tras reset: value = {} (gen {}) — el reset retracta el 10 previo",
85        fundido.value, fundido.generation
86    );
87
88    // Haz: stalk = valor del registro (escalar), restricciones = identidad.
89    // Aristas: 0—1 (single) y un DOBLE enlace 1—2 (el original + el que fuerza el
90    // reset), irreconciliables → ciclo localizado entre réplicas 1 y 2.
91    let sheaf = Sheaf::constant(3, &[(0, 1), (1, 2), (1, 2)], 1);
92    veredicto("B", &sheaf);
93}
94
95/// Contraste de la pista de retículos (Addendum §F): el triángulo monótono.
96///
97/// El motor LINEAL ve la topología del ciclo e inventa una obstrucción; la
98/// difusión de TARSKI reconcilia el dato monótono sin colapsar → corre libre,
99/// igualando a CALM. Es la brecha de modelado (§2.3) cerrada, en vivo.
100fn contraste_tarski() {
101    println!("Contraste — triángulo monótono (0—1—2—0, dato que se funde solo)");
102
103    let triangulo = Config::new(
104        "triangulo-monotono",
105        3,
106        vec![
107            Share::monotona(0, 1),
108            Share::monotona(1, 2),
109            Share::monotona(2, 0),
110        ],
111    );
112
113    println!("  oráculo (CALM):     {:?}", triangulo.oracle_verdict());
114    println!(
115        "  haz lineal (H¹):    {:?}   ← falsa obstrucción topológica",
116        triangulo.sheaf_verdict()
117    );
118    println!(
119        "  difusión de Tarski: {:?}      ← reconcilia al join, discrepancia RESUELTA",
120        triangulo.tarski_verdict()
121    );
122}
123
124/// La discrepancia buena (T8c, §D): donde de verdad se supera al CALM sintáctico.
125///
126/// Dos réplicas retiran 10 de una cuenta de 100 (invariante: saldo ≥ 0). Los
127/// valores caben → el haz (I-confluence sobre el grafo) corre libre; el CALM
128/// sintáctico condena el retiro por-programa. Análisis sensible a la instancia.
129fn contraste_invariante() {
130    println!("Discrepancia buena — retiros que caben (saldo ≥ 0, cuenta de 100)");
131
132    let caben = AccountConfig::new("retiros-que-caben", 100, vec![10, 10], vec![(0, 1)]);
133    let sobregiran = AccountConfig::new("sobregiran", 100, vec![60, 60], vec![(0, 1)]);
134
135    println!("  A) retiros [10,10] → Σ=20 ≤ 100:");
136    println!("     CALM sintáctico: {:?}  (condena el retiro)", caben.calm_syntactic());
137    println!(
138        "     haz (I-confluence): {:?}   ← corre libre: los valores pegan",
139        caben.haz_coordination()
140    );
141    println!("  B) retiros [60,60] → Σ=120 > 100:");
142    println!(
143        "     haz (I-confluence): {:?}   ← coordina: el merge rompe el invariante",
144        sobregiran.haz_coordination()
145    );
146}