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>
This commit is contained in:
Sergio
2026-07-01 18:02:43 +00:00
co-authored by Claude Opus 4.8
commit 6595dbed85
14 changed files with 1415 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+612
View File
@@ -0,0 +1,612 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "bytemuck"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "fixedbitset"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"libc",
"r-efi 5.3.0",
"wasip2",
]
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi 6.0.0",
]
[[package]]
name = "glam"
version = "0.30.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9"
[[package]]
name = "glam"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556f6b2ea90b8d15a74e0e7bb41671c9bdf38cd9f78c284d750b9ce58a2b5be7"
[[package]]
name = "glam"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0"
[[package]]
name = "glam"
version = "0.33.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f22fb22f065b308be0d8724e3706c7fa3fc2a6c7d6899df4cad7860e7a75436"
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
]
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "matrixmultiply"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08"
dependencies = [
"autocfg",
"rawpointer",
]
[[package]]
name = "nalgebra"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc43a60c217b0c6ff46e47f26911015ad8d2e5a8be1af668c67e370d99a4346"
dependencies = [
"approx",
"glam 0.30.10",
"glam 0.31.1",
"glam 0.32.1",
"glam 0.33.2",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "petgraph"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
dependencies = [
"fixedbitset",
"hashbrown 0.15.5",
"indexmap",
"serde",
]
[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proptest"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
dependencies = [
"bit-set",
"bit-vec",
"bitflags",
"num-traits",
"rand",
"rand_chacha",
"rand_xorshift",
"regex-syntax",
"rusty-fork",
"tempfile",
"unarray",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
dependencies = [
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "rand_xorshift"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
dependencies = [
"rand_core",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "regex-syntax"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rusty-fork"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
dependencies = [
"fnv",
"quick-error",
"tempfile",
"wait-timeout",
]
[[package]]
name = "safe_arch"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7caad094bd561859bcd467734a720c3c1f5d1f338995351fefe2190c45efed"
dependencies = [
"bytemuck",
]
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sheafsync"
version = "0.1.0"
dependencies = [
"nalgebra",
"petgraph",
"proptest",
"thiserror",
]
[[package]]
name = "simba"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f45c644a9f3a386f9288625d9f0c1e999e1acf07a37df35d0516c7f199d9cb2"
dependencies = [
"approx",
"num-complex",
"num-traits",
"wide",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.3",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wait-timeout"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
dependencies = [
"libc",
]
[[package]]
name = "wasip2"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "wide"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfdfe6a32973f2d1b268b8895845a8a96cac2f0191e72c27cc929036060dbf89"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "zerocopy"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+21
View File
@@ -0,0 +1,21 @@
[package]
name = "sheafsync"
version = "0.1.0"
edition = "2021"
description = "Motor de consistencia sin coordinación vía cohomología de haces celulares"
[lib]
name = "sheafsync"
path = "src/lib.rs"
[[bin]]
name = "sheafsync"
path = "src/main.rs"
[dependencies]
nalgebra = "0.35.0"
petgraph = "0.8.3"
thiserror = "2.0.18"
[dev-dependencies]
proptest = "1.11.0"
+430
View File
@@ -0,0 +1,430 @@
# 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<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
```rust
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
```rust
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
```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<Vec<Scalar>>,
}
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<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
- `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).
+40
View File
@@ -0,0 +1,40 @@
//! `cell` — el dato local que sabe fundirse y proyectarse (§6.1).
//!
//! M0: solo la *forma* del trait y los tipos. Los CRDTs concretos
//! (`GCounter`, `ResettableRegister`) y sus semánticas llegan en T2.
use crate::linalg::Scalar;
/// 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.
///
/// M0: forma únicamente; `impl Cell` llega en T2.
#[derive(Debug, Clone, PartialEq)]
pub struct GCounter {
/// Una casilla por réplica; fusionar = máximo casilla por casilla.
pub slots: Vec<u64>,
}
/// CRDT no monótono para el caso "difícil": permite reset/borrado.
/// Sirve para provocar `H¹ ≠ 0` deliberadamente (§8, corrida B).
///
/// M0: forma únicamente; `impl Cell` llega en T2.
#[derive(Debug, Clone, PartialEq)]
pub struct ResettableRegister {
/// Valor actual del registro.
pub value: i64,
/// Contador de generación; un reset lo incrementa (rompe la monotonicidad).
pub generation: u64,
}
+33
View File
@@ -0,0 +1,33 @@
//! `cohomology` — la única cuenta que importa (§6.4, §7).
//!
//! `rank δ`, `dim H⁰`, `dim H¹` y una base del cokernel para localizar el nudo.
//! M0: forma del resultado y firma de `compute`. La cuenta llega en T1.
use crate::error::{Result, SheafError};
use crate::linalg::Scalar;
use crate::sheaf::Sheaf;
/// Resultado del cálculo cohomológico sobre un haz.
#[derive(Debug, Clone, PartialEq)]
pub struct Cohomology {
/// `dim C⁰ = Σ dim F(v)`.
pub dim_c0: usize,
/// `dim C¹ = Σ dim F(e)`.
pub dim_c1: usize,
/// `rank δ`.
pub rank_delta: usize,
/// `dim H⁰ = dim_c0 rank_delta` (secciones globales coherentes).
pub dim_h0: usize,
/// `dim H¹ = dim_c1 rank_delta` (obstrucciones).
pub dim_h1: usize,
/// 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>>,
}
/// Calcula la cohomología del haz vía eliminación gaussiana (§7).
///
/// M0: no implementado; llega en T1.
pub fn compute(_sheaf: &Sheaf) -> Result<Cohomology> {
Err(SheafError::NotImplemented("cohomology::compute llega en T1"))
}
+22
View File
@@ -0,0 +1,22 @@
//! Errores tipados de la librería (§5.1). Nada de `unwrap()` en rutas públicas.
use thiserror::Error;
/// Error raíz de `sheafsync`.
#[derive(Debug, Error)]
pub enum SheafError {
/// Dimensiones incompatibles al ensamblar el coboundary o multiplicar matrices.
#[error("dimensiones incompatibles: {0}")]
DimensionMismatch(String),
/// El haz referencia una réplica o arista que no existe en el nervio.
#[error("entidad inexistente en el nervio: {0}")]
MissingEntity(String),
/// Marcador de funcionalidad aún no implementada (scaffolding M0).
#[error("no implementado todavía: {0}")]
NotImplemented(&'static str),
}
/// Alias de resultado usado en toda la librería.
pub type Result<T> = std::result::Result<T, SheafError>;
+38
View File
@@ -0,0 +1,38 @@
//! # sheafsync
//!
//! Motor de consistencia sin coordinación. Modela el estado distribuido como un
//! haz celular sobre el grafo de réplicas (el nervio) y calcula su cohomología
//! `H⁰`/`H¹` para decidir, con demostración, cuándo se puede fundir sin
//! coordinación y cuándo hay una obstrucción localizada.
//!
//! Ver `DESIGN.md` para el diseño completo. Este es el scaffold de M0: la forma
//! de todos los módulos, sin lógica de negocio todavía.
//!
//! Pipeline (§5):
//! `cell` → `nerve` → `sheaf` → `cohomology` → `verdict`, validado por `oracle`.
pub mod cell;
pub mod cohomology;
pub mod error;
pub mod linalg;
pub mod nerve;
pub mod oracle;
pub mod sheaf;
pub mod verdict;
pub use error::{Result, SheafError};
#[cfg(test)]
mod tests {
use crate::linalg::Matrix;
/// Test dummy de M0: el andamio compila y los tipos básicos existen.
/// La lógica real (cohomología, cordura topológica) llega en T1.
#[test]
fn scaffold_compila() {
let m = Matrix::zeros(2, 3);
assert_eq!(m.rows, 2);
assert_eq!(m.cols, 3);
assert_eq!(m.data.len(), 6);
}
}
+73
View File
@@ -0,0 +1,73 @@
//! Aislamiento del álgebra lineal tras un `trait` (§5.1, §7.1).
//!
//! Dos aritméticas, misma interfaz:
//! - `F64Backend`: rápido, con tolerancia epsilon. Para arrancar el MVP.
//! - `Gf2Backend` (M2): exacto, bit-packed sobre 𝔽₂. Aún no implementado.
//!
//! En M0 solo dejamos la *forma*: la impl `f64` existe pero sus operaciones
//! todavía no calculan nada. La lógica llega en T1.
/// Escalar del backend activo. Alias que resuelve la impl elegida (§6.1, §7).
pub type Scalar = f64;
/// Matriz densa mínima almacenada por filas. Placeholder de M0; en T1 puede
/// migrarse a `nalgebra::DMatrix` o mantenerse propia para el backend `GF(2)`.
#[derive(Debug, Clone, PartialEq)]
pub struct Matrix {
pub rows: usize,
pub cols: usize,
/// Datos row-major: `data[r * cols + c]`.
pub data: Vec<Scalar>,
}
impl Matrix {
/// Matriz de ceros de tamaño `rows × cols`.
pub fn zeros(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
data: vec![0.0; rows * cols],
}
}
}
/// Interfaz de álgebra lineal que el motor usa sin conocer la aritmética concreta.
///
/// Las dos operaciones que la cohomología necesita (§7): rango del coboundary y
/// una base del cokernel para localizar la obstrucción.
pub trait LinAlg {
/// `rank(m)` — número de filas/columnas independientes.
fn rank(&self, m: &Matrix) -> usize;
/// Base del cokernel `C¹ / im δ`: vectores de longitud `m.rows` que no
/// provienen de ninguna sección global. Vacío si `H¹ = 0`.
fn cokernel_basis(&self, m: &Matrix) -> Vec<Vec<Scalar>>;
}
/// Backend `f64` del MVP (§7.1). Rango con tolerancia epsilon.
///
/// M0: shell vacío. La eliminación gaussiana llega en T1.
#[derive(Debug, Default, Clone, Copy)]
pub struct F64Backend {
/// Tolerancia para considerar un pivote como cero.
pub epsilon: Scalar,
}
impl F64Backend {
/// Backend con la tolerancia por defecto.
pub fn new() -> Self {
Self { epsilon: 1e-9 }
}
}
impl LinAlg for F64Backend {
fn rank(&self, _m: &Matrix) -> usize {
// TODO(T1): eliminación gaussiana con pivoteo, contar pivotes > epsilon.
unimplemented!("F64Backend::rank llega en T1")
}
fn cokernel_basis(&self, _m: &Matrix) -> Vec<Vec<Scalar>> {
// TODO(T1): extraer base del cokernel a partir de la forma escalonada.
unimplemented!("F64Backend::cokernel_basis llega en T1")
}
}
+6
View File
@@ -0,0 +1,6 @@
//! Binario de `sheafsync`. En M0 solo saluda; las corridas A/B del hito M1
//! (§8) se conectan aquí en T2.
fn main() {
println!("hello sheaf");
}
+34
View File
@@ -0,0 +1,34 @@
//! `nerve` — el grafo de qué réplicas comparten datos con cuáles (§6.2).
//!
//! Vértices = réplicas, aristas = pares que comparten un dato.
use petgraph::graph::UnGraph;
/// Identificador de una réplica (vértice del nervio).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ReplicaId(pub usize);
/// Qué coordenadas del estado se comparten en una arista.
///
/// M0: forma únicamente; el detalle de la proyección se fija en `sheaf` (T1/T2).
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SharedLink {
/// Índices de las coordenadas compartidas en esta arista.
pub shared_coords: Vec<usize>,
}
/// El nervio: grafo no dirigido de réplicas y sus enlaces compartidos.
#[derive(Debug, Default)]
pub struct Nerve {
/// Grafo no dirigido: vértices = réplicas, aristas = pares que comparten datos.
pub graph: UnGraph<ReplicaId, SharedLink>,
}
impl Nerve {
/// Nervio vacío.
pub fn new() -> Self {
Self {
graph: UnGraph::new_undirected(),
}
}
}
+16
View File
@@ -0,0 +1,16 @@
//! `oracle` — evaluador CRDT/CALM de referencia, independiente del haz (§8 M3).
//!
//! Para cada caso de prueba dice, por un camino independiente (monotonicidad de
//! las operaciones + estructura del grafo), si es coordination-free. El test
//! maestro de M3 comprueba que el veredicto del haz coincide con este.
//!
//! M0: solo la forma. La batería de ≥20 casos y la lógica llegan en T4.
/// Predicción independiente del oráculo para una configuración.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OracleVerdict {
/// El oráculo cree que la configuración es libre de coordinación.
CoordinationFree,
/// El oráculo cree que hace falta coordinar.
NeedsCoordination,
}
+46
View File
@@ -0,0 +1,46 @@
//! `sheaf` — el haz celular y el ensamblado del coboundary `δ` (§6.3, §7).
//!
//! M0: forma de los tipos y firma de `coboundary`. El ensamblado real
//! (bloque por arista, `+R_{u⊴e}` / `R_{v⊴e}`) llega en T1.
use crate::error::{Result, SheafError};
use crate::linalg::Matrix;
/// Mapa de restricción `F(v ⊴ e): F(v) → F(e)`: proyección lineal del estado
/// de una réplica sobre el dato de una arista. Matriz `edge_dim × vertex_dim`.
pub type RestrictionMap = Matrix;
/// Un haz celular sobre el nervio.
#[derive(Debug, Default)]
pub struct Sheaf {
/// Para cada réplica: la dimensión de su stalk `F(v)`.
pub vertex_stalks: Vec<usize>,
/// Para cada arista: la dimensión de su stalk `F(e)`.
pub edge_stalks: Vec<usize>,
/// Mapas de restricción: por arista, dos matrices (una por extremo).
pub restrictions: Vec<(RestrictionMap, RestrictionMap)>,
/// Extremos `(u, v)` de cada arista, como índices en `vertex_stalks`.
pub edge_endpoints: Vec<(usize, usize)>,
}
impl Sheaf {
/// Dimensión de `C⁰ = ⊕_v F(v)` (espacio de cocadenas de vértice).
pub fn dim_c0(&self) -> usize {
self.vertex_stalks.iter().sum()
}
/// Dimensión de `C¹ = ⊕_e F(e)` (espacio de cocadenas de arista).
pub fn dim_c1(&self) -> usize {
self.edge_stalks.iter().sum()
}
/// Ensambla la matriz global de coboundary `δ: C⁰ → C¹`.
///
/// Convención (§6.3): para arista `e=(u,v)`,
/// `(δx)_e = R_{u⊴e}·x_u R_{v⊴e}·x_v`.
///
/// M0: no implementado; llega en T1.
pub fn coboundary(&self) -> Result<Matrix> {
Err(SheafError::NotImplemented("Sheaf::coboundary llega en T1"))
}
}
+43
View File
@@ -0,0 +1,43 @@
//! `verdict` — traducción del resultado topológico a lenguaje de sistemas (§6.5).
//!
//! `H¹ = 0 → corre libre` vs `H¹ ≠ 0 → coordina aquí`, con localización.
use crate::cohomology::Cohomology;
use crate::error::Result;
use crate::nerve::ReplicaId;
/// Un nudo: una obstrucción localizada entre réplicas/aristas concretas.
#[derive(Debug, Clone, PartialEq)]
pub struct Knot {
/// Réplicas implicadas en el nudo.
pub replicas: Vec<ReplicaId>,
/// Aristas del ciclo obstruido (índices en el nervio).
pub edges: Vec<usize>,
}
/// El veredicto final en lenguaje de sistemas.
#[derive(Debug, Clone, PartialEq)]
pub enum Verdict {
/// `H¹ = 0`: fusión libre de coordinación.
RunsFree,
/// `H¹ ≠ 0`: hace falta coordinar, con los nudos localizados.
NeedsCoordination {
/// Los nudos detectados.
knots: Vec<Knot>,
},
}
impl Verdict {
/// Deriva el veredicto a partir de la cohomología calculada.
///
/// M0: cuando `dim_h1 == 0` devuelve `RunsFree`; la localización de nudos
/// (poblar `Knot` desde `obstruction_basis`) llega en T2/T3.
pub fn from_cohomology(cohomology: &Cohomology) -> Result<Verdict> {
if cohomology.dim_h1 == 0 {
Ok(Verdict::RunsFree)
} else {
// TODO(T2/T3): mapear obstruction_basis → Knot con réplicas y aristas.
Ok(Verdict::NeedsCoordination { knots: Vec::new() })
}
}
}