Files
SergioandClaude Opus 4.8 6595dbed85 M0/T0: scaffold del crate sheafsync
Estructura de módulos (§5) sin lógica todavía: cell, nerve, sheaf,
cohomology, verdict, oracle, más error y linalg (trait LinAlg con impl
f64 vacía). Deps petgraph/nalgebra/thiserror + proptest dev-dep.
main imprime "hello sheaf"; test dummy pasa. DESIGN.md (antes sdd.txt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:02:43 +00:00

21 KiB
Raw Permalink Blame History

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⁰ = secciones globales = los estados globales consistentes que existen.
  • = 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 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 () 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:

  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 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 , 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_basisKnot) 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⁰, ): 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).