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:
- Coloca este archivo en la raíz del repo como
DESIGN.md.
- 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."
- 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.
- 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¹:
H⁰ = secciones globales = los estados globales consistentes que existen.
H¹ = obstrucciones = los "nudos" donde lo local es consistente por pares pero no pega globalmente.
El motor cruza ese resultado topológico con dos anclas conocidas del mundo de sistemas:
- CRDTs (tipos de datos que ya convergen solos; retículos join-semilattice).
- Teorema CALM (un cómputo tiene implementación libre de coordinación si y solo si es monótono).
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:
- a cada vértice
v (una réplica) un espacio de estado local, la stalk F(v);
- a cada arista
e (dos réplicas que comparten datos) un espacio F(e);
- a cada incidencia vértice→arista un mapa de restricción lineal
F(v ⊴ e): F(v) → F(e)
(la proyección del estado de la réplica sobre el dato que comparte en esa arista).
Con eso se construye un complejo de cocadenas y su coboundary δ: C⁰ → C¹.
La cohomología es:
H⁰ = ker δ — asignaciones locales que coinciden en TODA arista compartida = estados globales coherentes.
H¹ = coker δ = C¹ / im δ — las obstrucciones a pegar. dim H¹ = dim C¹ − rank δ.
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:
- MVP: trabajar con haces valuados en espacios vectoriales, donde la cohomología es álgebra
lineal estándar. Esto cubre limpiamente los datos que embeben en un grupo (un G-Counter embebe en
ℤⁿ; la pertenencia a conjuntos sobre GF(2)), y las restricciones son proyecciones lineales.
- Extensión de investigación: haces valuados en retículos / posets (la generalización "de Tarski"),
necesaria para capturar operaciones que no embeben en un grupo. Fuera del alcance del MVP; ver §11.
3. Objetivos y no-objetivos
3.1 Objetivos (MVP)
- G1. Representar réplicas, datos compartidos y su grafo de sincronización (nervio).
- G2. Construir el haz celular y su matriz de coboundary
δ.
- G3. Calcular
rank δ, dim H⁰, dim H¹ de forma exacta.
- G4. Localizar la obstrucción: no solo decir que hay un nudo, sino entre qué aristas/réplicas.
- G5. Reportar el veredicto en lenguaje de sistemas: "corre libre" vs "coordina aquí".
- G6. Validar contra un oráculo CRDT/CALM en una batería de casos conocidos.
3.2 No-objetivos (explícitos, para no dispersarse)
- N1. NO es un motor de replicación en producción. No hay red real, ni persistencia, ni wire protocol.
Es un analizador de configuraciones.
- N2. NO implementa haces sobre retículos generales en el MVP (solo espacios vectoriales).
- N3. NO resuelve la coordinación cuando hace falta; solo detecta y localiza dónde hace falta.
- N4. NO busca rendimiento a gran escala en v1. Corrección > velocidad. Grafos de decenas de réplicas.
- N5. NO integra con Tawasuyu/Hammer todavía. Eso es un hito posterior (M4), con API estable primero.
4. Modelo de dominio
Vocabulario que usa todo el código y este documento:
| Término | Significado |
| Réplica | Un nodo que tiene una copia local del estado. Vértice del grafo. |
| Nervio | El grafo de qué réplicas comparten datos con cuáles. Aristas. |
| Stalk | El espacio de estado local de una réplica (o de una arista). |
| Dato compartido | La porción del estado que dos réplicas deben mantener coherente en su arista. |
| Mapa de restricción | Proyecció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
- Núcleo matemático puro y determinista: sin I/O, sin aleatoriedad, sin estado global.
- Los tipos de datos compartidos entran por un
trait; el motor no sabe (ni le importa) si son
contadores, conjuntos o registros.
- El álgebra lineal se aísla tras una interfaz
LinAlg, con dos implementaciones intercambiables:
f64 (rápida, para arrancar) y GF(2) exacta (para el sabor topológico puro).
- Errores explícitos con
thiserror. Nada de unwrap() en rutas de librería.
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:
- 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.
- Calcular
rank(δ) por eliminación gaussiana.
dim H⁰ = dim C⁰ − rank(δ) (núcleo del coboundary).
dim H¹ = dim C¹ − rank(δ) (cokernel).
- 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
Scalar = f64 con nalgebra: rápido, para tener algo corriendo ya. Riesgo: rango numérico
aproximado (tolerancia epsilon). Suficiente para el MVP y demos.
Scalar = GF2 (bit-packed, eliminación gaussiana sobre 𝔽₂): exacto, sin epsilon, y es la
versión "topológica pura". Recomendado en cuanto el MVP funcione. Se implementa a mano con u64
como filas de bits; es código corto y muy robusto.
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:
dim H⁰ = (#componentes conexas) × d
dim H¹ = (#ciclos independientes, i.e. primer número de Betti) × d
donde d es la dimensión de la stalk. Si esto no cuadra, el coboundary está mal ensamblado.
Este test es el andamio: hazlo pasar antes de tocar CRDTs.
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.
- Implementar
cohomology::compute + la prueba de cordura del haz constante (§7.2).
- Corrida A (monótona): 3 réplicas, un
GCounter compartido, cada una incrementa por su lado.
El motor construye el haz, calcula H¹, y debe imprimir H¹ = 0 → corre libre.
- Corrida B (no monótona): mismas 3 réplicas en ciclo, pero con
ResettableRegister y una
configuración de constraints cíclica inconsistente. El motor debe imprimir
H¹ ≠ 0 → nudo entre réplicas 2 y 3 → coordina aquí, con localización correcta.
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:
- Unitarias de álgebra lineal.
rank, cokernel_basis sobre matrices con respuesta conocida a mano.
- 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).
- 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:
- Solo-suma sobre cualquier grafo ⟹
H¹ = 0.
- Añadir una arista nunca reduce
dim H⁰ (más restricciones, no menos).
- El haz constante siempre reproduce los números de Betti del grafo.
10. Dependencias
| Crate | Para qué | Nota |
petgraph | El nervio (grafo de réplicas) | Maduro, estándar. |
nalgebra | Álgebra lineal f64 del MVP | Para arrancar; rango con tolerancia. |
thiserror | Errores de librería tipados | Sin unwrap en rutas públicas. |
proptest | Tests basados en propiedades | Clave para la validación de invariantes. |
| (propio) | GF(2) bit-packed | Implementació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)
- R1 (el grande). La Hipótesis H puede fallar en casos sutiles. La codificación lineal de datos de
retículo puede perder información (§2.3), haciendo que el haz "vea" obstrucciones que CALM no, o
viceversa. Mitigación: M3 está diseñado justo para cazar esas discrepancias; son el resultado
científico, no un bug a esconder.
- R2. La localización del nudo (
obstruction_basis → Knot) es interpretable de forma limpia solo
cuando la base del cokernel es "esparcida". En casos densos, señalar "las réplicas culpables" puede ser
ambiguo. Mitigación: empezar con localización por ciclos del grafo, refinar después.
- R3. Rango en
f64 es aproximado. Mitigación: migrar a GF(2) exacto en M2; no confiar en el
MVP f64 para conclusiones finas.
- Q1. ¿Qué codificación lineal de cada CRDT preserva exactamente la monotonicidad? Es una decisión de
diseño por tipo de dato, no automática.
- Q2. Para datos no monótonos, ¿el haz sobre
GF(2) basta, o hace falta ya la generalización a
retículos? Probable que baste para el MVP; confirmar empíricamente en M3.
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
- Coordination-free / libre de coordinación: que las réplicas pueden converger sin un protocolo
de consenso ni un coordinador central.
- Monótono: una operación que solo agrega información y nunca la retracta (base de CALM).
- Cohomología (
H⁰, H¹): invariantes lineales del haz; aquí, "estados globales coherentes" y
"obstrucciones a la coherencia global".
- Cokernel:
C¹ / im δ; lo que "sobra" en el espacio de aristas y no proviene de ningún estado
global — la firma matemática de una obstrucción.
- Betti (primer número de): cantidad de ciclos independientes de un grafo; sirve de patrón de
cordura para el haz constante.
14. Referencias
- M. Shapiro, N. Preguiça, C. Baquero, M. Zawirski — A Comprehensive Study of Convergent and
Commutative Replicated Data Types (2011).
- J. M. Hellerstein, P. Alvaro — Keeping CALM: When Distributed Consistency is Easy (CACM).
- J. Hansen, R. Ghrist — Toward a Spectral Theory of Cellular Sheaves (2019).
- R. Ghrist — Elementary Applied Topology (para el andamiaje de cohomología celular).