# 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¹`: - `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 ```rust /// 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; /// 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 } /// 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 ```rust use petgraph::graph::UnGraph; pub struct Nerve { /// Grafo no dirigido: vértices = réplicas, aristas = pares que comparten datos. pub graph: UnGraph, } pub struct ReplicaId(pub usize); pub struct SharedLink { /* qué coordenadas se comparten en esta arista */ } ``` ### 6.3 `sheaf` — el haz y el coboundary ```rust pub struct Sheaf { /// Para cada réplica: su espacio local (dimensión de la stalk). pub vertex_stalks: Vec, /// Para cada arista: su espacio (dimensión). pub edge_stalks: Vec, /// 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 ```rust 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>, } pub fn compute(sheaf: &Sheaf) -> Cohomology { /* rank vía eliminación gaussiana */ } ``` ### 6.5 `verdict` — traducción a lenguaje humano ```rust pub enum Verdict { RunsFree, // H¹ = 0 NeedsCoordination { knots: Vec } // H¹ ≠ 0, con localización } pub struct Knot { pub replicas: Vec, // réplicas implicadas en el nudo pub edges: Vec, // 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 - `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: 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: - 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).