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:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+612
@@ -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
@@ -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"
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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"))
|
||||
}
|
||||
}
|
||||
@@ -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() })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user