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 14TOML 1358 Definiciones

⎇ 9de0cc601fd2eb7b9cf9b8a26ef8ed1ac1c9cd8d DESIGN.md Markdown · 430 líneas · 21.1 KB Historia crudo

M0/T0: scaffold del crate sheafsync 6595dbed8 · Sergio ·

1# SDD — Motor de Consistencia sin Coordinación
2
3**Nombre en clave:** `sheafsync`
4**Lenguaje:** Rust (edición 2021)
5**Autor del diseño:** (tú)
6**Estado:** Diseño v1 — listo para scaffolding con Claude Code
7**Última edición:** 2026-07-01
8
9---
10
11## 0. Cómo usar este documento con Claude Code
12
13Este SDD está escrito para ser el contexto raíz de un proyecto en Claude Code (CLI).
14La forma recomendada de trabajarlo:
15
161. Coloca este archivo en la raíz del repo como `DESIGN.md`.
172. Arranca Claude Code en esa carpeta y dile: *"Lee DESIGN.md completo. No escribas código todavía; primero resúmeme el plan de módulos y confírmame que entendiste el hito M1."*
183. Trabaja **un módulo por sesión**, en el orden de la sección 8. No dejes que genere todo de golpe: pídele un módulo, corre `cargo test`, revisa, y solo entonces avanza.
194. La sección 12 (Apéndice) trae los "tickets" ya redactados para copiar/pegar como tareas.
20
21Regla de oro para dirigirlo: **el criterio de éxito de cada pieza es un test que pasa, no que "se ve bien".** Este proyecto vive o muere por la corrección matemática, no por la estética.
22
23---
24
25## 1. Resumen ejecutivo
26
27`sheafsync` es un motor en Rust que responde, con demostración y no a ojo, una pregunta central de los sistemas soberanos / local-first:
28
29> Dado un conjunto de réplicas que editan copias locales de un estado compartido,
30> ¿cuándo se pueden fundir esas copias en un estado global coherente **sin coordinación**,
31> y cuándo existe una obstrucción que obliga a sincronizar?
32
33La herramienta modela el estado distribuido como un **haz celular** (cellular sheaf) sobre el
34grafo de comunicación entre réplicas (el "nervio"), y calcula su **cohomología** `H⁰` y `H¹`:
35
36- `H⁰` = secciones globales = los estados globales consistentes que existen.
37- `H¹` = obstrucciones = los "nudos" donde lo local es consistente por pares pero no pega globalmente.
38
39El motor cruza ese resultado topológico con dos anclas conocidas del mundo de sistemas:
40
41- **CRDTs** (tipos de datos que ya convergen solos; retículos join-semilattice).
42- **Teorema CALM** (un cómputo tiene implementación libre de coordinación **si y solo si** es monótono).
43
44**Salida práctica:** dada una configuración de réplicas + datos, el motor imprime
45`H¹ = 0 → corre libre (sin jefe)` o `H¹ ≠ 0 → nudo localizado entre réplicas X e Y → coordina aquí`.
46
47---
48
49## 2. Fundamento teórico (preciso pero legible)
50
51### 2.1 Las tres piezas
52
53**CRDTs (Shapiro et al., 2011).** Estructuras de datos que se pegan solas porque su operación
54de fusión es un `join` sobre un retículo (conmutativa, asociativa, idempotente). El ejemplo mínimo
55es el *G-Counter*: cada réplica solo incrementa su propia casilla; fusionar = tomar el máximo casilla
56por casilla. Nunca hay conflicto porque nunca se resta.
57
58**Teorema CALM (Hellerstein, "Keeping CALM").** Formaliza la intuición anterior:
59un programa admite una implementación consistente **sin coordinación** exactamente cuando es
60*monótono* (solo agrega conocimiento, nunca lo retracta). En cuanto aparece una operación no monótona
61(borrar, resetear, sobrescribir, negar), la coordinación se vuelve necesaria.
62
63**Haces celulares y cohomología (Hansen & Ghrist, "Toward a spectral theory of cellular sheaves").**
64Un haz celular sobre un grafo asigna:
65- a cada **vértice** `v` (una réplica) un espacio de estado local, la *stalk* `F(v)`;
66- a cada **arista** `e` (dos réplicas que comparten datos) un espacio `F(e)`;
67- a cada incidencia vértice→arista un *mapa de restricción* lineal `F(v ⊴ e): F(v) → F(e)`
68  (la proyección del estado de la réplica sobre el dato que comparte en esa arista).
69
70Con eso se construye un complejo de cocadenas y su **coboundary** `δ: C⁰ → C¹`.
71La cohomología es:
72- `H⁰ = ker δ` — asignaciones locales que coinciden en TODA arista compartida = estados globales coherentes.
73- `H¹ = coker δ = C¹ / im δ` — las obstrucciones a pegar. `dim H¹ = dim C¹ − rank δ`.
74
75### 2.2 El puente (la hipótesis central del proyecto)
76
77La apuesta intelectual de `sheafsync` es que **la cohomología del haz de sincronización coincide
78con lo que CALM predice sobre la coordinación**:
79
80> **Hipótesis H:** para un estado compartido codificado linealmente,
81> `H¹ = 0` sobre el grafo de réplicas ⟺ la configuración admite fusión libre de coordinación
82> (i.e. el caso monótono / CALM-libre).
83
84**Esto NO es un teorema publicado.** Es la conjetura que el proyecto explora y valida empíricamente.
85Por eso el corazón de la validación (sección 9) es: en los casos donde YA conocemos la respuesta por
86CRDT/CALM, comprobar que el `H¹` calculado concuerda. Si concuerda en los casos fáciles, ganamos
87confianza para usarlo en los casos difíciles donde ya no es obvio a ojo. Ese es el valor de la herramienta,
88y también su honestidad: no vendemos un teorema, construimos y probamos una correspondencia.
89
90### 2.3 La brecha de modelado que hay que respetar
91
92La cohomología de haces es **lineal** (sobre un cuerpo o anillo). Los retículos de los CRDTs son
93**de orden** (join-semilattices), no espacios vectoriales. Reconciliar ambos es la parte de frontera.
94
95Estrategia:
96- **MVP:** trabajar con haces *valuados en espacios vectoriales*, donde la cohomología es álgebra
97  lineal estándar. Esto cubre limpiamente los datos que embeben en un grupo (un G-Counter embebe en
98  `ℤⁿ`; la pertenencia a conjuntos sobre `GF(2)`), y las restricciones son proyecciones lineales.
99- **Extensión de investigación:** haces valuados en retículos / posets (la generalización "de Tarski"),
100  necesaria para capturar operaciones que no embeben en un grupo. Fuera del alcance del MVP; ver §11.
101
102---
103
104## 3. Objetivos y no-objetivos
105
106### 3.1 Objetivos (MVP)
107- G1. Representar réplicas, datos compartidos y su grafo de sincronización (nervio).
108- G2. Construir el haz celular y su matriz de coboundary `δ`.
109- G3. Calcular `rank δ`, `dim H⁰`, `dim H¹` de forma exacta.
110- G4. Localizar la obstrucción: no solo decir *que* hay un nudo, sino *entre qué aristas/réplicas*.
111- G5. Reportar el veredicto en lenguaje de sistemas: "corre libre" vs "coordina aquí".
112- G6. Validar contra un oráculo CRDT/CALM en una batería de casos conocidos.
113
114### 3.2 No-objetivos (explícitos, para no dispersarse)
115- N1. NO es un motor de replicación en producción. No hay red real, ni persistencia, ni wire protocol.
116      Es un *analizador* de configuraciones.
117- N2. NO implementa haces sobre retículos generales en el MVP (solo espacios vectoriales).
118- N3. NO resuelve la coordinación cuando hace falta; solo *detecta y localiza* dónde hace falta.
119- N4. NO busca rendimiento a gran escala en v1. Corrección > velocidad. Grafos de decenas de réplicas.
120- N5. NO integra con Tawasuyu/Hammer todavía. Eso es un hito posterior (M4), con API estable primero.
121
122---
123
124## 4. Modelo de dominio
125
126Vocabulario que usa todo el código y este documento:
127
128| Término | Significado |
129|---|---|
130| **Réplica** | Un nodo que tiene una copia local del estado. Vértice del grafo. |
131| **Nervio** | El grafo de qué réplicas comparten datos con cuáles. Aristas. |
132| **Stalk** | El espacio de estado local de una réplica (o de una arista). |
133| **Dato compartido** | La porción del estado que dos réplicas deben mantener coherente en su arista. |
134| **Mapa de restricción** | Proyección lineal del estado de una réplica sobre el dato de una arista. |
135| **Coboundary `δ`** | Operador lineal que mide desacuerdos en todas las aristas a la vez. |
136| **Sección global (`H⁰`)** | Un estado global coherente: todos concuerdan en todo lo compartido. |
137| **Obstrucción (`H¹`)** | Un nudo: coherencia por pares que no se puede globalizar. |
138
139---
140
141## 5. Arquitectura
142
143Crate único (`sheafsync`) con módulos claros. Es un binario + librería (`src/lib.rs` + `src/main.rs`).
144
145```
146Réplicas + datos            ──►  módulo `cell`     (qué es un dato local que sabe fundirse)
147Quién comparte con quién    ──►  módulo `nerve`    (el grafo)
148Pegar lo local              ──►  módulo `sheaf`    (haz + coboundary δ)
149Detector de choques         ──►  módulo `cohomology` (rank, H⁰, H¹, localización)
150Veredicto                   ──►  módulo `verdict`  (traducción a lenguaje de sistemas)
151Oráculo de validación       ──►  módulo `oracle`   (CRDT/CALM de referencia)
152```
153
154Cada módulo corresponde exactamente a una caja del diagrama de tubería del diseño conceptual.
155
156### 5.1 Principios de diseño de código
157- Núcleo matemático **puro y determinista**: sin I/O, sin aleatoriedad, sin estado global.
158- Los tipos de datos compartidos entran por un `trait`; el motor no sabe (ni le importa) si son
159  contadores, conjuntos o registros.
160- El álgebra lineal se aísla tras una interfaz `LinAlg`, con dos implementaciones intercambiables:
161  `f64` (rápida, para arrancar) y `GF(2)` exacta (para el sabor topológico puro).
162- Errores explícitos con `thiserror`. Nada de `unwrap()` en rutas de librería.
163
164---
165
166## 6. Modelo de tipos (bocetos de Rust)
167
168Estos son bocetos de *forma*, no código final. Claude Code debe completarlos y ajustarlos.
169
170### 6.1 `cell` — el dato local
171
172```rust
173/// Un pedacito de estado local que sabe cómo intentar fundirse con otro,
174/// y cómo proyectarse al espacio de una arista compartida.
175pub trait Cell: Clone + PartialEq {
176    /// El vector que representa este estado en el espacio de la stalk.
177    fn to_vector(&self) -> Vec<Scalar>;
178
179    /// Fusión de retículo (join). Debe ser conmutativa, asociativa, idempotente.
180    fn join(&self, other: &Self) -> Self;
181
182    /// ¿Esta operación es monótona? (marca para el oráculo CALM)
183    fn is_monotone() -> bool;
184}
185
186/// CRDT mínimo del MVP: cuenta por réplica, fusiona por máximo. Siempre monótono.
187pub struct GCounter { pub slots: Vec<u64> }
188
189/// CRDT no monótono para el caso "difícil": permite reset/borrado.
190/// Sirve para provocar H¹ ≠ 0 deliberadamente.
191pub struct ResettableRegister { /* ... */ }
192```
193
194`Scalar` es un alias que resuelve la implementación de álgebra lineal elegida (ver §7).
195
196### 6.2 `nerve` — el grafo
197
198```rust
199use petgraph::graph::UnGraph;
200
201pub struct Nerve {
202    /// Grafo no dirigido: vértices = réplicas, aristas = pares que comparten datos.
203    pub graph: UnGraph<ReplicaId, SharedLink>,
204}
205
206pub struct ReplicaId(pub usize);
207pub struct SharedLink { /* qué coordenadas se comparten en esta arista */ }
208```
209
210### 6.3 `sheaf` — el haz y el coboundary
211
212```rust
213pub struct Sheaf {
214    /// Para cada réplica: su espacio local (dimensión de la stalk).
215    pub vertex_stalks: Vec<usize>,
216    /// Para cada arista: su espacio (dimensión).
217    pub edge_stalks: Vec<usize>,
218    /// Mapas de restricción: por arista, dos matrices (una por extremo).
219    pub restrictions: Vec<(RestrictionMap, RestrictionMap)>,
220}
221
222impl Sheaf {
223    /// Ensambla la matriz global de coboundary δ: C⁰ → C¹.
224    /// Convención: para arista e=(u→v), (δx)_e = R_{u⊴e}·x_u − R_{v⊴e}·x_v.
225    pub fn coboundary(&self) -> Matrix { /* ... */ }
226}
227```
228
229### 6.4 `cohomology` — la única cuenta que importa
230
231```rust
232pub struct Cohomology {
233    pub dim_c0: usize,
234    pub dim_c1: usize,
235    pub rank_delta: usize,
236    pub dim_h0: usize, // = dim_c0 - rank_delta
237    pub dim_h1: usize, // = dim_c1 - rank_delta
238    /// Si dim_h1 > 0, una base del cokernel: cada vector señala aristas
239    /// que participan en la obstrucción (para localizar el nudo).
240    pub obstruction_basis: Vec<Vec<Scalar>>,
241}
242
243pub fn compute(sheaf: &Sheaf) -> Cohomology { /* rank vía eliminación gaussiana */ }
244```
245
246### 6.5 `verdict` — traducción a lenguaje humano
247
248```rust
249pub enum Verdict {
250    RunsFree,                              // H¹ = 0
251    NeedsCoordination { knots: Vec<Knot> } // H¹ ≠ 0, con localización
252}
253
254pub struct Knot {
255    pub replicas: Vec<ReplicaId>, // réplicas implicadas en el nudo
256    pub edges: Vec<usize>,        // aristas del ciclo obstruido
257}
258```
259
260---
261
262## 7. La cuenta de cohomología, paso a paso
263
264Esto es lo que Claude Code debe implementar en `cohomology::compute`, y es TODO el corazón:
265
2661. Ensamblar `δ` como una matriz de tamaño `(Σ dim F(e)) × (Σ dim F(v))`.
267   Bloque de la arista `e=(u,v)`: `+R_{u⊴e}` en las columnas de `u`, `−R_{v⊴e}` en las de `v`.
2682. Calcular `rank(δ)` por eliminación gaussiana.
2693. `dim H⁰ = dim C⁰ − rank(δ)`  (núcleo del coboundary).
2704. `dim H¹ = dim C¹ − rank(δ)`  (cokernel).
2715. Si `dim H¹ > 0`, extraer una base del cokernel (vectores de `C¹` que no están en `im δ`).
272   Las aristas con soporte no nulo en esos vectores son el nudo → poblar `Knot`.
273
274### 7.1 Dos aritméticas, misma interfaz
275- `Scalar = f64` con `nalgebra`: rápido, para tener algo corriendo ya. Riesgo: rango numérico
276  aproximado (tolerancia epsilon). Suficiente para el MVP y demos.
277- `Scalar = GF2` (bit-packed, eliminación gaussiana sobre `𝔽₂`): **exacto**, sin epsilon, y es la
278  versión "topológica pura". Recomendado en cuanto el MVP funcione. Se implementa a mano con `u64`
279  como filas de bits; es código corto y muy robusto.
280
281Ambas detrás de un `trait LinAlg { fn rank(&Matrix) -> usize; fn cokernel_basis(&Matrix) -> ...; }`.
282
283### 7.2 Prueba de cordura obligatoria (¡implementar primero!)
284Para el **haz constante** (todas las stalks iguales, todas las restricciones = identidad), la cohomología
285DEBE coincidir con la topología del grafo:
286- `dim H⁰ = (#componentes conexas) × d`
287- `dim H¹ = (#ciclos independientes, i.e. primer número de Betti) × d`
288donde `d` es la dimensión de la stalk. Si esto no cuadra, el coboundary está mal ensamblado.
289Este test es el andamio: hazlo pasar antes de tocar CRDTs.
290
291---
292
293## 8. Plan de hitos
294
295### M0 — Scaffolding (día 1)
296`cargo new`, estructura de módulos vacía, `LinAlg` con la impl `f64`, `petgraph` y `thiserror` como deps.
297Un `main` que imprime "hello sheaf". Test dummy que pasa.
298
299### M1 — EL HITO DE DOS SEMANAS (el que prueba el núcleo entero)
300Objetivo: dos corridas, una que da cero y otra que no.
301
302- Implementar `cohomology::compute` + la prueba de cordura del haz constante (§7.2).
303- **Corrida A (monótona):** 3 réplicas, un `GCounter` compartido, cada una incrementa por su lado.
304  El motor construye el haz, calcula `H¹`, y **debe imprimir `H¹ = 0 → corre libre`**.
305- **Corrida B (no monótona):** mismas 3 réplicas en ciclo, pero con `ResettableRegister` y una
306  configuración de constraints cíclica inconsistente. El motor **debe imprimir
307  `H¹ ≠ 0 → nudo entre réplicas 2 y 3 → coordina aquí`**, con localización correcta.
308
309Criterio de éxito: A da 0, B da ≠0, y la localización de B apunta al ciclo correcto.
310Si logras esto, el núcleo está probado. Todo lo demás es crecer, no reinventar.
311
312### M2 — Aritmética exacta y localización robusta
313Añadir la impl `GF(2)`. Migrar los tests a exacto (sin epsilon). Mejorar `Knot` para reportar todos
314los ciclos obstruidos, no solo el primero. Property-based tests con `proptest`.
315
316### M3 — Oráculo y batería de validación
317Implementar `oracle`: un evaluador CRDT/CALM de referencia que, para cada caso de prueba, dice
318independientemente si es coordination-free. Batería de ≥20 casos (monótonos y no monótonos, con y sin
319ciclos). Test maestro: **el veredicto del haz coincide con el del oráculo en todos**. Esto valida la
320Hipótesis H (§2.2) empíricamente. Documentar cualquier caso donde discrepen — esos son oro científico.
321
322### M4 — API estable + puente a Tawasuyu/Hammer (posterior)
323Congelar la API pública de `verdict`. Adaptador que tome un estado real de tus sistemas soberanos y lo
324codifique como haz. Recién aquí se conecta a lo tuyo.
325
326### Extensión de investigación (sin fecha)
327Haces valuados en retículos/posets para cubrir datos no embebibles en grupos (§2.3). Aquí es donde el
328proyecto deja de replicar lo conocido y aporta algo nuevo de verdad.
329
330---
331
332## 9. Estrategia de pruebas
333
334Tres capas, de más barata a más valiosa:
335
3361. **Unitarias de álgebra lineal.** `rank`, `cokernel_basis` sobre matrices con respuesta conocida a mano.
3372. **Cordura topológica.** Haz constante sobre grafos con Betti conocido (árbol → H¹=0; triángulo →
338   H¹ de dimensión 1; dos triángulos pegados → dimensión 2). No negociable (§7.2).
3393. **Oráculo CRDT/CALM.** El corazón de la validación (M3). Cada caso trae su respuesta "verdadera"
340   calculada por un camino independiente (monotonicidad de las operaciones + estructura del grafo), y el
341   motor debe coincidir. `proptest` para generar configuraciones aleatorias y verificar invariantes
342   (p.ej. "toda configuración puramente monótona sobre grafo arbitrario ⟹ H¹=0").
343
344Invariantes que `proptest` debe intentar romper:
345- Solo-suma sobre cualquier grafo ⟹ `H¹ = 0`.
346- Añadir una arista nunca reduce `dim H⁰` (más restricciones, no menos).
347- El haz constante siempre reproduce los números de Betti del grafo.
348
349---
350
351## 10. Dependencias
352
353| Crate | Para qué | Nota |
354|---|---|---|
355| `petgraph` | El nervio (grafo de réplicas) | Maduro, estándar. |
356| `nalgebra` | Álgebra lineal `f64` del MVP | Para arrancar; rango con tolerancia. |
357| `thiserror` | Errores de librería tipados | Sin `unwrap` en rutas públicas. |
358| `proptest` | Tests basados en propiedades | Clave para la validación de invariantes. |
359| (propio) | `GF(2)` bit-packed | Implementación a mano, ~1 archivo. Sin dependencia externa. |
360
361Deliberadamente mínimo. Los CRDTs del MVP (`GCounter`, `ResettableRegister`) se implementan inline para
362controlar su semántica exacta; no se depende de un crate de CRDTs en v1.
363
364---
365
366## 11. Riesgos y preguntas abiertas (honestas)
367
368- **R1 (el grande).** La Hipótesis H puede fallar en casos sutiles. La codificación lineal de datos de
369  retículo puede perder información (§2.3), haciendo que el haz "vea" obstrucciones que CALM no, o
370  viceversa. *Mitigación:* M3 está diseñado justo para cazar esas discrepancias; son el resultado
371  científico, no un bug a esconder.
372- **R2.** La localización del nudo (`obstruction_basis` → `Knot`) es interpretable de forma limpia solo
373  cuando la base del cokernel es "esparcida". En casos densos, señalar "las réplicas culpables" puede ser
374  ambiguo. *Mitigación:* empezar con localización por ciclos del grafo, refinar después.
375- **R3.** Rango en `f64` es aproximado. *Mitigación:* migrar a `GF(2)` exacto en M2; no confiar en el
376  MVP `f64` para conclusiones finas.
377- **Q1.** ¿Qué codificación lineal de cada CRDT preserva exactamente la monotonicidad? Es una decisión de
378  diseño por tipo de dato, no automática.
379- **Q2.** Para datos no monótonos, ¿el haz sobre `GF(2)` basta, o hace falta ya la generalización a
380  retículos? Probable que baste para el MVP; confirmar empíricamente en M3.
381
382---
383
384## 12. Apéndice — Tickets para Claude Code
385
386Copia/pega estos como tareas, en orden. Uno por sesión. Corre `cargo test` entre cada uno.
387
388**T0.** "Crea el scaffold del crate `sheafsync` según §5: módulos `cell`, `nerve`, `sheaf`,
389`cohomology`, `verdict`, `oracle`, más `lib.rs` y `main.rs`. Añade deps `petgraph`, `nalgebra`,
390`thiserror`, y `proptest` como dev-dependency. Define el trait `LinAlg` con una impl `f64` vacía.
391Deja todo compilando con un test dummy que pase. No implementes lógica aún."
392
393**T1.** "Implementa `cohomology::compute` según §7, sobre la impl `f64` de `LinAlg`. Implementa la
394prueba de cordura del haz constante de §7.2 como tests: árbol (H¹=0), triángulo (H¹ dim 1), dos
395triángulos pegados (H¹ dim 2). Que pasen."
396
397**T2.** "Implementa `GCounter` y `ResettableRegister` en `cell` según §6.1, con `is_monotone`
398correcto. Escribe el ejemplo del hito M1 (§8): Corrida A (GCounter, 3 réplicas) que debe dar H¹=0, y
399Corrida B (ResettableRegister en ciclo inconsistente) que debe dar H¹≠0 con nudo localizado.
400Muéstralos desde `main`."
401
402**T3.** "Añade la impl `GF(2)` de `LinAlg` (bit-packed con u64, eliminación gaussiana exacta). Migra
403los tests a exacto. Extrae `Knot` con localización de todos los ciclos obstruidos según §6.5."
404
405**T4.** "Implementa `oracle` (§8 M3): un evaluador CRDT/CALM de referencia independiente. Crea una
406batería de ≥20 casos y un test maestro que verifique que el veredicto del haz coincide con el del
407oráculo. Añade los invariantes `proptest` de §9. Documenta cualquier discrepancia en un archivo
408`DISCREPANCIES.md`."
409
410---
411
412## 13. Glosario mínimo
413
414- **Coordination-free / libre de coordinación:** que las réplicas pueden converger sin un protocolo
415  de consenso ni un coordinador central.
416- **Monótono:** una operación que solo agrega información y nunca la retracta (base de CALM).
417- **Cohomología (`H⁰`, `H¹`):** invariantes lineales del haz; aquí, "estados globales coherentes" y
418  "obstrucciones a la coherencia global".
419- **Cokernel:** `C¹ / im δ`; lo que "sobra" en el espacio de aristas y no proviene de ningún estado
420  global — la firma matemática de una obstrucción.
421- **Betti (primer número de):** cantidad de ciclos independientes de un grafo; sirve de patrón de
422  cordura para el haz constante.
423
424## 14. Referencias
425
426- M. Shapiro, N. Preguiça, C. Baquero, M. Zawirski — *A Comprehensive Study of Convergent and
427  Commutative Replicated Data Types* (2011).
428- J. M. Hellerstein, P. Alvaro — *Keeping CALM: When Distributed Consistency is Easy* (CACM).
429- J. Hansen, R. Ghrist — *Toward a Spectral Theory of Cellular Sheaves* (2019).
430- R. Ghrist — *Elementary Applied Topology* (para el andamiaje de cohomología celular).

DESIGN.md

SDD — Motor de Consistencia sin Coordinación

Nombre en clave: sheafsync Lenguaje: Rust (edición 2021) Autor del diseño: (tú) Estado: Diseño v1 — listo para scaffolding con Claude Code Última edición: 2026-07-01


0. Cómo usar este documento con Claude Code

Este SDD está escrito para ser el contexto raíz de un proyecto en Claude Code (CLI). La forma recomendada de trabajarlo:

  1. Coloca este archivo en la raíz del repo como DESIGN.md.
  2. Arranca Claude Code en esa carpeta y dile: "Lee DESIGN.md completo. No escribas código todavía; primero resúmeme el plan de módulos y confírmame que entendiste el hito M1."
  3. Trabaja un módulo por sesión, en el orden de la sección 8. No dejes que genere todo de golpe: pídele un módulo, corre cargo test, revisa, y solo entonces avanza.
  4. La sección 12 (Apéndice) trae los "tickets" ya redactados para copiar/pegar como tareas.

Regla de oro para dirigirlo: el criterio de éxito de cada pieza es un test que pasa, no que "se ve bien". Este proyecto vive o muere por la corrección matemática, no por la estética.


1. Resumen ejecutivo

sheafsync es un motor en Rust que responde, con demostración y no a ojo, una pregunta central de los sistemas soberanos / local-first:

Dado un conjunto de réplicas que editan copias locales de un estado compartido, ¿cuándo se pueden fundir esas copias en un estado global coherente sin coordinación, y cuándo existe una obstrucción que obliga a sincronizar?

La herramienta modela el estado distribuido como un haz celular (cellular sheaf) sobre el grafo de comunicación entre réplicas (el "nervio"), y calcula su cohomología H⁰ y H¹:

El motor cruza ese resultado topológico con dos anclas conocidas del mundo de sistemas:

Salida práctica: dada una configuración de réplicas + datos, el motor imprime H¹ = 0 → corre libre (sin jefe) o H¹ ≠ 0 → nudo localizado entre réplicas X e Y → coordina aquí.


2. Fundamento teórico (preciso pero legible)

2.1 Las tres piezas

CRDTs (Shapiro et al., 2011). Estructuras de datos que se pegan solas porque su operación de fusión es un join sobre un retículo (conmutativa, asociativa, idempotente). El ejemplo mínimo es el G-Counter: cada réplica solo incrementa su propia casilla; fusionar = tomar el máximo casilla por casilla. Nunca hay conflicto porque nunca se resta.

Teorema CALM (Hellerstein, "Keeping CALM"). Formaliza la intuición anterior: un programa admite una implementación consistente sin coordinación exactamente cuando es monótono (solo agrega conocimiento, nunca lo retracta). En cuanto aparece una operación no monótona (borrar, resetear, sobrescribir, negar), la coordinación se vuelve necesaria.

Haces celulares y cohomología (Hansen & Ghrist, "Toward a spectral theory of cellular sheaves"). Un haz celular sobre un grafo asigna:

Con eso se construye un complejo de cocadenas y su coboundary δ: C⁰ → C¹. La cohomología es:

2.2 El puente (la hipótesis central del proyecto)

La apuesta intelectual de sheafsync es que la cohomología del haz de sincronización coincide con lo que CALM predice sobre la coordinación:

Hipótesis H: para un estado compartido codificado linealmente, H¹ = 0 sobre el grafo de réplicas ⟺ la configuración admite fusión libre de coordinación (i.e. el caso monótono / CALM-libre).

Esto NO es un teorema publicado. Es la conjetura que el proyecto explora y valida empíricamente. Por eso el corazón de la validación (sección 9) es: en los casos donde YA conocemos la respuesta por CRDT/CALM, comprobar que el H¹ calculado concuerda. Si concuerda en los casos fáciles, ganamos confianza para usarlo en los casos difíciles donde ya no es obvio a ojo. Ese es el valor de la herramienta, y también su honestidad: no vendemos un teorema, construimos y probamos una correspondencia.

2.3 La brecha de modelado que hay que respetar

La cohomología de haces es lineal (sobre un cuerpo o anillo). Los retículos de los CRDTs son de orden (join-semilattices), no espacios vectoriales. Reconciliar ambos es la parte de frontera.

Estrategia:


3. Objetivos y no-objetivos

3.1 Objetivos (MVP)

3.2 No-objetivos (explícitos, para no dispersarse)


4. Modelo de dominio

Vocabulario que usa todo el código y este documento:

TérminoSignificado
RéplicaUn nodo que tiene una copia local del estado. Vértice del grafo.
NervioEl grafo de qué réplicas comparten datos con cuáles. Aristas.
StalkEl espacio de estado local de una réplica (o de una arista).
Dato compartidoLa porción del estado que dos réplicas deben mantener coherente en su arista.
Mapa de restricciónProyección lineal del estado de una réplica sobre el dato de una arista.
Coboundary δOperador lineal que mide desacuerdos en todas las aristas a la vez.
Sección global (H⁰)Un estado global coherente: todos concuerdan en todo lo compartido.
Obstrucción (H¹)Un nudo: coherencia por pares que no se puede globalizar.

5. Arquitectura

Crate único (sheafsync) con módulos claros. Es un binario + librería (src/lib.rs + src/main.rs).

Réplicas + datos            ──►  módulo `cell`     (qué es un dato local que sabe fundirse)
Quién comparte con quién    ──►  módulo `nerve`    (el grafo)
Pegar lo local              ──►  módulo `sheaf`    (haz + coboundary δ)
Detector de choques         ──►  módulo `cohomology` (rank, H⁰, H¹, localización)
Veredicto                   ──►  módulo `verdict`  (traducción a lenguaje de sistemas)
Oráculo de validación       ──►  módulo `oracle`   (CRDT/CALM de referencia)

Cada módulo corresponde exactamente a una caja del diagrama de tubería del diseño conceptual.

5.1 Principios de diseño de código


6. Modelo de tipos (bocetos de Rust)

Estos son bocetos de forma, no código final. Claude Code debe completarlos y ajustarlos.

6.1 cell — el dato local

/// Un pedacito de estado local que sabe cómo intentar fundirse con otro,
/// y cómo proyectarse al espacio de una arista compartida.
pub trait Cell: Clone + PartialEq {
    /// El vector que representa este estado en el espacio de la stalk.
    fn to_vector(&self) -> Vec<Scalar>;

    /// Fusión de retículo (join). Debe ser conmutativa, asociativa, idempotente.
    fn join(&self, other: &Self) -> Self;

    /// ¿Esta operación es monótona? (marca para el oráculo CALM)
    fn is_monotone() -> bool;
}

/// CRDT mínimo del MVP: cuenta por réplica, fusiona por máximo. Siempre monótono.
pub struct GCounter { pub slots: Vec<u64> }

/// CRDT no monótono para el caso "difícil": permite reset/borrado.
/// Sirve para provocar H¹ ≠ 0 deliberadamente.
pub struct ResettableRegister { /* ... */ }

Scalar es un alias que resuelve la implementación de álgebra lineal elegida (ver §7).

6.2 nerve — el grafo

use petgraph::graph::UnGraph;

pub struct Nerve {
    /// Grafo no dirigido: vértices = réplicas, aristas = pares que comparten datos.
    pub graph: UnGraph<ReplicaId, SharedLink>,
}

pub struct ReplicaId(pub usize);
pub struct SharedLink { /* qué coordenadas se comparten en esta arista */ }

6.3 sheaf — el haz y el coboundary

pub struct Sheaf {
    /// Para cada réplica: su espacio local (dimensión de la stalk).
    pub vertex_stalks: Vec<usize>,
    /// Para cada arista: su espacio (dimensión).
    pub edge_stalks: Vec<usize>,
    /// Mapas de restricción: por arista, dos matrices (una por extremo).
    pub restrictions: Vec<(RestrictionMap, RestrictionMap)>,
}

impl Sheaf {
    /// Ensambla la matriz global de coboundary δ: C⁰ → C¹.
    /// Convención: para arista e=(u→v), (δx)_e = R_{u⊴e}·x_u − R_{v⊴e}·x_v.
    pub fn coboundary(&self) -> Matrix { /* ... */ }
}

6.4 cohomology — la única cuenta que importa

pub struct Cohomology {
    pub dim_c0: usize,
    pub dim_c1: usize,
    pub rank_delta: usize,
    pub dim_h0: usize, // = dim_c0 - rank_delta
    pub dim_h1: usize, // = dim_c1 - rank_delta
    /// Si dim_h1 > 0, una base del cokernel: cada vector señala aristas
    /// que participan en la obstrucción (para localizar el nudo).
    pub obstruction_basis: Vec<Vec<Scalar>>,
}

pub fn compute(sheaf: &Sheaf) -> Cohomology { /* rank vía eliminación gaussiana */ }

6.5 verdict — traducción a lenguaje humano

pub enum Verdict {
    RunsFree,                              // H¹ = 0
    NeedsCoordination { knots: Vec<Knot> } // H¹ ≠ 0, con localización
}

pub struct Knot {
    pub replicas: Vec<ReplicaId>, // réplicas implicadas en el nudo
    pub edges: Vec<usize>,        // aristas del ciclo obstruido
}

7. La cuenta de cohomología, paso a paso

Esto es lo que Claude Code debe implementar en cohomology::compute, y es TODO el corazón:

  1. Ensamblar δ como una matriz de tamaño (Σ dim F(e)) × (Σ dim F(v)). Bloque de la arista e=(u,v): +R_{u⊴e} en las columnas de u, −R_{v⊴e} en las de v.
  2. Calcular rank(δ) por eliminación gaussiana.
  3. dim H⁰ = dim C⁰ − rank(δ) (núcleo del coboundary).
  4. dim H¹ = dim C¹ − rank(δ) (cokernel).
  5. Si dim H¹ > 0, extraer una base del cokernel (vectores de C¹ que no están en im δ). Las aristas con soporte no nulo en esos vectores son el nudo → poblar Knot.

7.1 Dos aritméticas, misma interfaz

Ambas detrás de un trait LinAlg { fn rank(&Matrix) -> usize; fn cokernel_basis(&Matrix) -> ...; }.

7.2 Prueba de cordura obligatoria (¡implementar primero!)

Para el haz constante (todas las stalks iguales, todas las restricciones = identidad), la cohomología DEBE coincidir con la topología del grafo:


8. Plan de hitos

M0 — Scaffolding (día 1)

cargo new, estructura de módulos vacía, LinAlg con la impl f64, petgraph y thiserror como deps. Un main que imprime "hello sheaf". Test dummy que pasa.

M1 — EL HITO DE DOS SEMANAS (el que prueba el núcleo entero)

Objetivo: dos corridas, una que da cero y otra que no.

Criterio de éxito: A da 0, B da ≠0, y la localización de B apunta al ciclo correcto. Si logras esto, el núcleo está probado. Todo lo demás es crecer, no reinventar.

M2 — Aritmética exacta y localización robusta

Añadir la impl GF(2). Migrar los tests a exacto (sin epsilon). Mejorar Knot para reportar todos los ciclos obstruidos, no solo el primero. Property-based tests con proptest.

M3 — Oráculo y batería de validación

Implementar oracle: un evaluador CRDT/CALM de referencia que, para cada caso de prueba, dice independientemente si es coordination-free. Batería de ≥20 casos (monótonos y no monótonos, con y sin ciclos). Test maestro: el veredicto del haz coincide con el del oráculo en todos. Esto valida la Hipótesis H (§2.2) empíricamente. Documentar cualquier caso donde discrepen — esos son oro científico.

M4 — API estable + puente a Tawasuyu/Hammer (posterior)

Congelar la API pública de verdict. Adaptador que tome un estado real de tus sistemas soberanos y lo codifique como haz. Recién aquí se conecta a lo tuyo.

Extensión de investigación (sin fecha)

Haces valuados en retículos/posets para cubrir datos no embebibles en grupos (§2.3). Aquí es donde el proyecto deja de replicar lo conocido y aporta algo nuevo de verdad.


9. Estrategia de pruebas

Tres capas, de más barata a más valiosa:

  1. Unitarias de álgebra lineal. rank, cokernel_basis sobre matrices con respuesta conocida a mano.
  2. Cordura topológica. Haz constante sobre grafos con Betti conocido (árbol → H¹=0; triángulo → H¹ de dimensión 1; dos triángulos pegados → dimensión 2). No negociable (§7.2).
  3. Oráculo CRDT/CALM. El corazón de la validación (M3). Cada caso trae su respuesta "verdadera" calculada por un camino independiente (monotonicidad de las operaciones + estructura del grafo), y el motor debe coincidir. proptest para generar configuraciones aleatorias y verificar invariantes (p.ej. "toda configuración puramente monótona sobre grafo arbitrario ⟹ H¹=0").

Invariantes que proptest debe intentar romper:


10. Dependencias

CratePara quéNota
petgraphEl nervio (grafo de réplicas)Maduro, estándar.
nalgebraÁlgebra lineal f64 del MVPPara arrancar; rango con tolerancia.
thiserrorErrores de librería tipadosSin unwrap en rutas públicas.
proptestTests basados en propiedadesClave para la validación de invariantes.
(propio)GF(2) bit-packedImplementación a mano, ~1 archivo. Sin dependencia externa.

Deliberadamente mínimo. Los CRDTs del MVP (GCounter, ResettableRegister) se implementan inline para controlar su semántica exacta; no se depende de un crate de CRDTs en v1.


11. Riesgos y preguntas abiertas (honestas)


12. Apéndice — Tickets para Claude Code

Copia/pega estos como tareas, en orden. Uno por sesión. Corre cargo test entre cada uno.

T0. "Crea el scaffold del crate sheafsync según §5: módulos cell, nerve, sheaf, cohomology, verdict, oracle, más lib.rs y main.rs. Añade deps petgraph, nalgebra, thiserror, y proptest como dev-dependency. Define el trait LinAlg con una impl f64 vacía. Deja todo compilando con un test dummy que pase. No implementes lógica aún."

T1. "Implementa cohomology::compute según §7, sobre la impl f64 de LinAlg. Implementa la prueba de cordura del haz constante de §7.2 como tests: árbol (H¹=0), triángulo (H¹ dim 1), dos triángulos pegados (H¹ dim 2). Que pasen."

T2. "Implementa GCounter y ResettableRegister en cell según §6.1, con is_monotone correcto. Escribe el ejemplo del hito M1 (§8): Corrida A (GCounter, 3 réplicas) que debe dar H¹=0, y Corrida B (ResettableRegister en ciclo inconsistente) que debe dar H¹≠0 con nudo localizado. Muéstralos desde main."

T3. "Añade la impl GF(2) de LinAlg (bit-packed con u64, eliminación gaussiana exacta). Migra los tests a exacto. Extrae Knot con localización de todos los ciclos obstruidos según §6.5."

T4. "Implementa oracle (§8 M3): un evaluador CRDT/CALM de referencia independiente. Crea una batería de ≥20 casos y un test maestro que verifique que el veredicto del haz coincide con el del oráculo. Añade los invariantes proptest de §9. Documenta cualquier discrepancia en un archivo DISCREPANCIES.md."


13. Glosario mínimo

14. Referencias