diff --git a/Cargo.lock b/Cargo.lock
index 212bf9a..0269bff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -52,6 +52,17 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom 0.2.17",
+ "once_cell",
+ "version_check",
+]
+
[[package]]
name = "ahash"
version = "0.8.12"
@@ -86,7 +97,7 @@ dependencies = [
[[package]]
name = "aingle_ai"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"blake2",
"candle-core 0.9.2",
@@ -108,7 +119,7 @@ dependencies = [
[[package]]
name = "aingle_contracts"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"blake3",
"dashmap 6.1.0",
@@ -127,10 +138,12 @@ dependencies = [
[[package]]
name = "aingle_cortex"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"aingle_graph",
"aingle_logic",
+ "aingle_raft",
+ "aingle_wal",
"aingle_zk",
"argon2",
"async-graphql",
@@ -149,18 +162,22 @@ dependencies = [
"log",
"mdns-sd",
"once_cell",
+ "openraft",
"quinn",
"rand 0.9.2",
"rcgen",
"regex",
"reqwest",
"rustls",
+ "rustls-pemfile",
"serde",
"serde_json",
"spargebra",
+ "subtle",
"tempfile",
"thiserror 2.0.18",
"tokio",
+ "tokio-rustls",
"tokio-stream",
"tokio-test",
"tower",
@@ -173,7 +190,7 @@ dependencies = [
[[package]]
name = "aingle_graph"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"bincode",
"blake3",
@@ -190,11 +207,12 @@ dependencies = [
"sled",
"tempfile",
"thiserror 2.0.18",
+ "uuid",
]
[[package]]
name = "aingle_logic"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"aingle_graph",
"chrono",
@@ -210,7 +228,7 @@ dependencies = [
[[package]]
name = "aingle_minimal"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"async-io",
"async-tungstenite",
@@ -250,9 +268,30 @@ dependencies = [
"webrtc",
]
+[[package]]
+name = "aingle_raft"
+version = "0.5.0"
+dependencies = [
+ "aingle_graph",
+ "aingle_wal",
+ "anyerror",
+ "bincode",
+ "blake3",
+ "chrono",
+ "futures-util",
+ "ineru",
+ "openraft",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "tokio",
+ "tokio-test",
+ "tracing",
+]
+
[[package]]
name = "aingle_viz"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"aingle_graph",
"aingle_minimal",
@@ -272,9 +311,21 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "aingle_wal"
+version = "0.5.0"
+dependencies = [
+ "bincode",
+ "blake3",
+ "chrono",
+ "serde",
+ "serde_json",
+ "tempfile",
+]
+
[[package]]
name = "aingle_zk"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"blake3",
"bulletproofs",
@@ -372,6 +423,15 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "anyerror"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71add24cc141a1e8326f249b74c41cfd217aeb2a67c9c6cf9134d175469afd49"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "anyhow"
version = "1.0.102"
@@ -1016,6 +1076,18 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
+[[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
[[package]]
name = "blake2"
version = "0.10.6"
@@ -1117,6 +1189,29 @@ dependencies = [
"dbus",
]
+[[package]]
+name = "borsh"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "bstr"
version = "1.12.1"
@@ -1198,18 +1293,52 @@ dependencies = [
"allocator-api2",
]
+[[package]]
+name = "byte-unit"
+version = "5.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d"
+dependencies = [
+ "rust_decimal",
+ "schemars",
+ "serde",
+ "utf8-width",
+]
+
+[[package]]
+name = "bytecheck"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
+dependencies = [
+ "bytecheck_derive 0.6.12",
+ "ptr_meta 0.1.4",
+ "simdutf8",
+]
+
[[package]]
name = "bytecheck"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b"
dependencies = [
- "bytecheck_derive",
- "ptr_meta",
+ "bytecheck_derive 0.8.2",
+ "ptr_meta 0.3.1",
"rancor",
"simdutf8",
]
+[[package]]
+name = "bytecheck_derive"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "bytecheck_derive"
version = "0.8.2"
@@ -2399,7 +2528,7 @@ dependencies = [
"aes",
"aes-gcm",
"async-trait",
- "bytecheck",
+ "bytecheck 0.8.2",
"byteorder",
"cbc",
"ccm",
@@ -2414,7 +2543,7 @@ dependencies = [
"rand_core 0.6.4",
"rcgen",
"ring",
- "rkyv",
+ "rkyv 0.8.15",
"rustls",
"sec1",
"sha1",
@@ -2426,6 +2555,12 @@ dependencies = [
"x509-parser",
]
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
[[package]]
name = "dyn-stack"
version = "0.10.0"
@@ -3019,6 +3154,12 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
[[package]]
name = "futures"
version = "0.3.32"
@@ -3534,13 +3675,22 @@ dependencies = [
"byteorder",
]
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash 0.7.8",
+]
+
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [
- "ahash",
+ "ahash 0.8.12",
]
[[package]]
@@ -3977,7 +4127,7 @@ dependencies = [
[[package]]
name = "ineru"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"bincode",
"blake3",
@@ -4236,7 +4386,7 @@ dependencies = [
[[package]]
name = "kaneru"
-version = "0.4.2"
+version = "0.5.0"
dependencies = [
"chrono",
"criterion",
@@ -4499,6 +4649,12 @@ dependencies = [
"zerocopy-derive",
]
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+
[[package]]
name = "matchers"
version = "0.2.0"
@@ -5002,6 +5158,67 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+[[package]]
+name = "openraft"
+version = "0.10.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b9d8db10f834d517e4c2c45ab5c645bc5cafee9d07f7b150b8029a0b1ebdca"
+dependencies = [
+ "anyerror",
+ "byte-unit",
+ "chrono",
+ "clap",
+ "derive_more",
+ "futures-util",
+ "maplit",
+ "openraft-macros",
+ "openraft-rt",
+ "openraft-rt-tokio",
+ "peel-off",
+ "rand 0.9.2",
+ "serde",
+ "thiserror 2.0.18",
+ "tracing",
+ "validit",
+]
+
+[[package]]
+name = "openraft-macros"
+version = "0.10.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22b0bd215948ed47997a1d0447ea592e49220096360a833b118f329a08aa286"
+dependencies = [
+ "chrono",
+ "proc-macro2",
+ "quote",
+ "semver",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "openraft-rt"
+version = "0.10.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55b651e6e2f25d022e34549e605eb8875c78ebc26862b16b06143a551e53ec00"
+dependencies = [
+ "futures-channel",
+ "futures-util",
+ "openraft-macros",
+ "rand 0.9.2",
+]
+
+[[package]]
+name = "openraft-rt-tokio"
+version = "0.10.0-alpha.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478d5625fdeb13293e68549ba1d42b7a25085f3be04204412147637ad22e2827"
+dependencies = [
+ "futures-util",
+ "openraft-rt",
+ "rand 0.9.2",
+ "tokio",
+]
+
[[package]]
name = "openssl"
version = "0.10.75"
@@ -5174,6 +5391,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+[[package]]
+name = "peel-off"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3420ea4424090cbd75a688996f696a807c68d6744b4863591b86435dc3078e9"
+
[[package]]
name = "peg"
version = "0.8.5"
@@ -5455,13 +5678,33 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "ptr_meta"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
+dependencies = [
+ "ptr_meta_derive 0.1.4",
+]
+
[[package]]
name = "ptr_meta"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79"
dependencies = [
- "ptr_meta_derive",
+ "ptr_meta_derive 0.3.1",
+]
+
+[[package]]
+name = "ptr_meta_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
]
[[package]]
@@ -5588,13 +5831,19 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+[[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
[[package]]
name = "rancor"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee"
dependencies = [
- "ptr_meta",
+ "ptr_meta 0.3.1",
]
[[package]]
@@ -5784,6 +6033,26 @@ dependencies = [
"thiserror 2.0.18",
]
+[[package]]
+name = "ref-cast"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "regalloc2"
version = "0.13.5"
@@ -5853,13 +6122,22 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "rend"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
+dependencies = [
+ "bytecheck 0.6.12",
+]
+
[[package]]
name = "rend"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6"
dependencies = [
- "bytecheck",
+ "bytecheck 0.8.2",
]
[[package]]
@@ -5947,25 +6225,54 @@ dependencies = [
"rio_api",
]
+[[package]]
+name = "rkyv"
+version = "0.7.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1"
+dependencies = [
+ "bitvec",
+ "bytecheck 0.6.12",
+ "bytes",
+ "hashbrown 0.12.3",
+ "ptr_meta 0.1.4",
+ "rend 0.4.2",
+ "rkyv_derive 0.7.46",
+ "seahash",
+ "tinyvec",
+ "uuid",
+]
+
[[package]]
name = "rkyv"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70"
dependencies = [
- "bytecheck",
+ "bytecheck 0.8.2",
"bytes",
"hashbrown 0.16.1",
"indexmap",
"munge",
- "ptr_meta",
+ "ptr_meta 0.3.1",
"rancor",
- "rend",
- "rkyv_derive",
+ "rend 0.5.3",
+ "rkyv_derive 0.8.15",
"tinyvec",
"uuid",
]
+[[package]]
+name = "rkyv_derive"
+version = "0.7.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
[[package]]
name = "rkyv_derive"
version = "0.8.15"
@@ -6047,6 +6354,22 @@ dependencies = [
"smallvec",
]
+[[package]]
+name = "rust_decimal"
+version = "1.40.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0"
+dependencies = [
+ "arrayvec",
+ "borsh",
+ "bytes",
+ "num-traits",
+ "rand 0.8.5",
+ "rkyv 0.7.46",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.27"
@@ -6135,6 +6458,15 @@ dependencies = [
"security-framework",
]
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "rustls-pki-types"
version = "1.14.0"
@@ -6243,6 +6575,18 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "schemars"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -6261,6 +6605,12 @@ dependencies = [
"url",
]
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
[[package]]
name = "sec1"
version = "0.7.3"
@@ -6904,6 +7254,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
[[package]]
name = "tar"
version = "0.4.44"
@@ -7496,6 +7852,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+[[package]]
+name = "utf8-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091"
+
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -7550,6 +7912,15 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "validit"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4efba0434d5a0a62d4f22070b44ce055dc18cb64d4fa98276aa523dadfaba0e7"
+dependencies = [
+ "anyerror",
+]
+
[[package]]
name = "valuable"
version = "0.1.1"
@@ -7771,7 +8142,7 @@ dependencies = [
"object 0.38.1",
"rangemap",
"region",
- "rkyv",
+ "rkyv 0.8.15",
"self_cell",
"shared-buffer",
"smallvec",
@@ -7825,14 +8196,14 @@ version = "7.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7f91b0cb63705afa0843b46a0aeaeaedff7be2e5b05691176e9e58e2dbe921"
dependencies = [
- "bytecheck",
+ "bytecheck 0.8.2",
"enum-iterator",
"enumset",
"getrandom 0.2.17",
"hex",
"indexmap",
"more-asserts",
- "rkyv",
+ "rkyv 0.8.15",
"sha2 0.11.0-rc.5",
"target-lexicon",
"thiserror 2.0.18",
@@ -8641,6 +9012,15 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
[[package]]
name = "x25519-dalek"
version = "2.0.1"
diff --git a/Cargo.toml b/Cargo.toml
index 16f2619..672ec8e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,6 +12,8 @@ members = [
"crates/aingle_minimal", # IoT-optimized minimal node
"crates/aingle_contracts", # Smart Contracts (DSL + WASM Runtime)
"crates/aingle_viz", # DAG Visualization Server
+ "crates/aingle_wal", # Write-Ahead Log (clustering)
+ "crates/aingle_raft", # Raft consensus (clustering)
# ── Examples ────────────────────────────────────────────────────
"examples/iot_sensor_network",
diff --git a/README.md b/README.md
index 743871f..3941129 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,8 @@
-
+
+
@@ -156,6 +157,73 @@ Interactive D3.js dashboard. Watch your DAG evolve in real-time. Filter, search,
---
+## Clustering
+
+AIngle supports multi-node clustering via Raft consensus for high availability and horizontal scalability. Writes are replicated to all nodes; reads can be served from any node with optional quorum consistency.
+
+### Quick Start (3-node cluster)
+
+```bash
+# Node 1 — bootstrap leader
+aingle-cortex --port 8081 \
+ --cluster --cluster-node-id 1 \
+ --cluster-secret "your-secret-at-least-16-chars" \
+ --cluster-wal-dir ./data/node1/wal \
+ --db-path ./data/node1/graph.sled
+
+# Node 2 — joins via node 1
+aingle-cortex --port 8082 \
+ --cluster --cluster-node-id 2 \
+ --cluster-peers 127.0.0.1:8081 \
+ --cluster-secret "your-secret-at-least-16-chars" \
+ --cluster-wal-dir ./data/node2/wal \
+ --db-path ./data/node2/graph.sled
+
+# Node 3 — joins via node 1
+aingle-cortex --port 8083 \
+ --cluster --cluster-node-id 3 \
+ --cluster-peers 127.0.0.1:8081 \
+ --cluster-secret "your-secret-at-least-16-chars" \
+ --cluster-wal-dir ./data/node3/wal \
+ --db-path ./data/node3/graph.sled
+```
+
+### With TLS encryption
+
+```bash
+# Auto-generated self-signed certs (development)
+aingle-cortex --port 8081 --cluster --cluster-node-id 1 \
+ --cluster-secret "your-secret" --cluster-tls
+
+# Custom certificates (production)
+aingle-cortex --port 8081 --cluster --cluster-node-id 1 \
+ --cluster-secret "your-secret" --cluster-tls \
+ --cluster-tls-cert /path/to/cert.pem \
+ --cluster-tls-key /path/to/key.pem
+```
+
+### Cluster endpoints
+
+| Endpoint | Method | Description |
+|----------|--------|-------------|
+| `/api/v1/cluster/status` | GET | Node role, leader ID, current term |
+| `/api/v1/cluster/members` | GET | All cluster members and their state |
+| `/api/v1/cluster/join` | POST | Add a new node to the cluster |
+| `/api/v1/cluster/leave` | POST | Gracefully remove a node |
+| `/api/v1/cluster/wal/stats` | GET | WAL segment count and disk usage |
+| `/api/v1/cluster/wal/verify` | POST | Verify WAL integrity (checksums) |
+
+### Features
+
+- **Raft consensus** — automatic leader election, log replication, and membership changes
+- **Streaming snapshots** — 512KB chunked transfer with per-chunk ACK for large datasets
+- **Write-Ahead Log** — crash-safe durability with segment rotation and integrity verification
+- **TLS encryption** — optional TLS for inter-node communication (self-signed or custom certs)
+- **Constant-time auth** — cluster secret verified with timing-safe comparison
+- **Quorum reads** — optional strong consistency for read operations
+
+---
+
## Architecture
```
@@ -177,6 +245,12 @@ Interactive D3.js dashboard. Watch your DAG evolve in real-time. Filter, search,
│ │ Graph │ │ Engine │ │ (Privacy) │ │ Runtime │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ └───────────┘ │
├────────────────────────────────────────────────────────────────────────┤
+│ CONSENSUS LAYER │
+│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
+│ │ Raft │ │ WAL │ │ Streaming │ │ TLS │ │
+│ │ (openraft) │ │ (Durability) │ │ Snapshots │ │ (mTLS) │ │
+│ └──────────────┘ └──────────────┘ └──────────────┘ └───────────┘ │
+├────────────────────────────────────────────────────────────────────────┤
│ NETWORK LAYER │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Kitsune P2P │ │ CoAP │ │ Gossip │ │ mDNS │ │
@@ -199,6 +273,9 @@ cd aingle
# Build
cargo build --workspace --release
+# Build with clustering support
+cargo build -p aingle_cortex --features cluster --release
+
# Test
cargo test --workspace
@@ -208,7 +285,7 @@ cargo doc --workspace --no-deps --open
### Prerequisites
-- **Rust** 1.70 or later
+- **Rust** 1.83 or later
- **libsodium-dev** (cryptography)
- **libssl-dev** (TLS)
- **pkg-config**
@@ -264,6 +341,13 @@ cargo doc --workspace --no-deps --open
| `aingle_logic` | Prolog-style reasoning engine |
| `aingle_graph` | Semantic graph database |
+### Clustering & Consensus
+
+| Component | Purpose |
+|-----------|---------|
+| `aingle_raft` | Raft consensus (leader election, log replication, streaming snapshots) |
+| `aingle_wal` | Write-Ahead Log for crash-safe durability |
+
### Security & Privacy
| Component | Purpose |
diff --git a/crates/aingle_ai/Cargo.toml b/crates/aingle_ai/Cargo.toml
index 48095ae..3dd1334 100644
--- a/crates/aingle_ai/Cargo.toml
+++ b/crates/aingle_ai/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "aingle_ai"
-version = "0.4.2"
+version = "0.5.0"
description = "AI integration layer for AIngle - Ineru, Nested Learning, Kaneru"
license = "Apache-2.0 OR LicenseRef-Commercial"
repository = "https://github.com/ApiliumCode/aingle"
diff --git a/crates/aingle_contracts/Cargo.toml b/crates/aingle_contracts/Cargo.toml
index 3c9f735..1d7a53a 100644
--- a/crates/aingle_contracts/Cargo.toml
+++ b/crates/aingle_contracts/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "aingle_contracts"
-version = "0.4.2"
+version = "0.5.0"
description = "Smart Contracts DSL and WASM Runtime for AIngle"
license = "Apache-2.0 OR LicenseRef-Commercial"
repository = "https://github.com/ApiliumCode/aingle"
diff --git a/crates/aingle_cortex/Cargo.toml b/crates/aingle_cortex/Cargo.toml
index 03693b3..677c41f 100644
--- a/crates/aingle_cortex/Cargo.toml
+++ b/crates/aingle_cortex/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "aingle_cortex"
-version = "0.4.2"
+version = "0.5.0"
description = "Córtex API - REST/GraphQL/SPARQL interface for AIngle semantic graphs"
license = "Apache-2.0 OR LicenseRef-Commercial"
repository = "https://github.com/ApiliumCode/aingle"
@@ -20,6 +20,7 @@ sparql = ["dep:spargebra"]
auth = ["dep:jsonwebtoken", "dep:argon2"]
p2p = ["dep:quinn", "dep:rustls", "dep:rcgen", "dep:ed25519-dalek", "dep:hex"]
p2p-mdns = ["p2p", "dep:mdns-sd", "dep:if-addrs"]
+cluster = ["p2p", "dep:aingle_wal", "dep:aingle_raft", "dep:openraft", "dep:tokio-rustls", "dep:rustls-pemfile"]
full = ["rest", "graphql", "sparql", "auth"]
[[bin]]
@@ -28,10 +29,10 @@ path = "src/main.rs"
[dependencies]
# Core AIngle crates
-aingle_graph = { version = "0.4", path = "../aingle_graph", features = ["sled-backend"] }
-aingle_logic = { version = "0.4", path = "../aingle_logic" }
-aingle_zk = { version = "0.4", path = "../aingle_zk" }
-ineru = { version = "0.4", path = "../ineru" }
+aingle_graph = { version = "0.5", path = "../aingle_graph", features = ["sled-backend"] }
+aingle_logic = { version = "0.5", path = "../aingle_logic" }
+aingle_zk = { version = "0.5", path = "../aingle_zk" }
+ineru = { version = "0.5", path = "../ineru" }
# Web framework
axum = { version = "0.8", features = ["ws", "macros"] }
@@ -68,6 +69,7 @@ rand = "0.9"
# Hashing
blake3 = "1.8"
+subtle = "2.6"
# Streaming
tokio-stream = { version = "0.1", features = ["sync"] }
@@ -92,6 +94,13 @@ rustls = { version = "0.23", default-features = false, features = ["ring", "std"
rcgen = { version = "0.13", optional = true }
ed25519-dalek = { version = "2", features = ["rand_core"], optional = true }
hex = { version = "0.4", optional = true }
+# Clustering (optional)
+aingle_wal = { version = "0.5", path = "../aingle_wal", optional = true }
+aingle_raft = { version = "0.5", path = "../aingle_raft", optional = true }
+openraft = { version = "0.10.0-alpha.17", features = ["serde", "type-alias"], optional = true }
+tokio-rustls = { version = "0.26", default-features = false, features = ["ring"], optional = true }
+rustls-pemfile = { version = "2", optional = true }
+
dirs = "6"
mdns-sd = { version = "0.18", optional = true }
if-addrs = { version = "0.13", optional = true }
diff --git a/crates/aingle_cortex/src/cluster_init.rs b/crates/aingle_cortex/src/cluster_init.rs
new file mode 100644
index 0000000..17834aa
--- /dev/null
+++ b/crates/aingle_cortex/src/cluster_init.rs
@@ -0,0 +1,534 @@
+// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0 OR Commercial
+
+//! Cluster initialization — public API for setting up Raft consensus.
+//!
+//! This module extracts the cluster setup logic from `main.rs` into a
+//! reusable API so it can be called both from the binary and from
+//! integration tests.
+
+#[cfg(feature = "cluster")]
+use crate::error::Error;
+#[cfg(feature = "cluster")]
+use crate::server::CortexServer;
+
+/// Configuration for cluster mode.
+#[cfg(feature = "cluster")]
+#[derive(Debug, Clone)]
+pub struct ClusterConfig {
+ /// Whether cluster mode is enabled.
+ pub enabled: bool,
+ /// Unique Raft node ID (must be > 0).
+ pub node_id: u64,
+ /// Peer REST addresses to join (empty = bootstrap single-node).
+ pub peers: Vec,
+ /// Directory for the Write-Ahead Log.
+ pub wal_dir: Option,
+ /// Shared secret for authenticating internal cluster RPCs.
+ pub secret: Option,
+ /// Whether to use TLS for inter-node communication.
+ pub tls: bool,
+ /// Path to TLS certificate PEM file (optional; auto-generated if absent).
+ pub tls_cert: Option,
+ /// Path to TLS private key PEM file (optional; auto-generated if absent).
+ pub tls_key: Option,
+}
+
+#[cfg(feature = "cluster")]
+impl ClusterConfig {
+ /// Parse cluster config from CLI arguments.
+ pub fn from_args(args: &[String]) -> Self {
+ let mut cfg = Self {
+ enabled: false,
+ node_id: 0,
+ peers: Vec::new(),
+ wal_dir: None,
+ secret: None,
+ tls: false,
+ tls_cert: None,
+ tls_key: None,
+ };
+ let mut i = 1;
+ while i < args.len() {
+ match args[i].as_str() {
+ "--cluster" => cfg.enabled = true,
+ "--cluster-node-id" => {
+ if i + 1 < args.len() {
+ cfg.node_id = args[i + 1].parse().unwrap_or(0);
+ i += 1;
+ }
+ }
+ "--cluster-peers" => {
+ if i + 1 < args.len() {
+ cfg.peers =
+ args[i + 1].split(',').map(|s| s.trim().to_string()).collect();
+ i += 1;
+ }
+ }
+ "--cluster-wal-dir" => {
+ if i + 1 < args.len() {
+ cfg.wal_dir = Some(args[i + 1].clone());
+ i += 1;
+ }
+ }
+ "--cluster-secret" => {
+ if i + 1 < args.len() {
+ cfg.secret = Some(args[i + 1].clone());
+ i += 1;
+ }
+ }
+ "--cluster-tls" => cfg.tls = true,
+ "--cluster-tls-cert" => {
+ if i + 1 < args.len() {
+ cfg.tls_cert = Some(args[i + 1].clone());
+ i += 1;
+ }
+ }
+ "--cluster-tls-key" => {
+ if i + 1 < args.len() {
+ cfg.tls_key = Some(args[i + 1].clone());
+ i += 1;
+ }
+ }
+ _ => {}
+ }
+ i += 1;
+ }
+ cfg
+ }
+
+ /// Validate the cluster configuration. Returns an error message on failure.
+ pub fn validate(&self) -> Result<(), String> {
+ if self.node_id == 0 {
+ return Err("--cluster-node-id must be > 0".into());
+ }
+ if let Some(ref secret) = self.secret {
+ if secret.len() < 16 {
+ return Err("--cluster-secret must be at least 16 bytes".into());
+ }
+ }
+ Ok(())
+ }
+}
+
+/// HTTP-based Raft RPC sender with exponential backoff.
+///
+/// Routes Raft protocol messages to target nodes via their internal HTTP
+/// endpoints (`/internal/raft/{append-entries,vote,snapshot}`).
+#[cfg(feature = "cluster")]
+pub struct HttpRaftRpcSender {
+ client: reqwest::Client,
+ cluster_secret: Option,
+ use_tls: bool,
+}
+
+#[cfg(feature = "cluster")]
+impl HttpRaftRpcSender {
+ /// Create a new sender.
+ ///
+ /// When `use_tls` is true, URLs will use `https://` and the reqwest
+ /// client will accept self-signed certificates (TOFU model, matching
+ /// the P2P transport).
+ pub fn new(cluster_secret: Option, use_tls: bool) -> Self {
+ let client = if use_tls {
+ reqwest::Client::builder()
+ .timeout(std::time::Duration::from_secs(10))
+ .danger_accept_invalid_certs(true) // TOFU — same as P2P layer
+ .build()
+ .expect("Failed to create HTTPS client for Raft RPC")
+ } else {
+ reqwest::Client::builder()
+ .timeout(std::time::Duration::from_secs(10))
+ .build()
+ .expect("Failed to create HTTP client for Raft RPC")
+ };
+ Self {
+ client,
+ cluster_secret,
+ use_tls,
+ }
+ }
+
+ fn scheme(&self) -> &str {
+ if self.use_tls {
+ "https"
+ } else {
+ "http"
+ }
+ }
+}
+
+#[cfg(feature = "cluster")]
+impl aingle_raft::network::RaftRpcSender for HttpRaftRpcSender {
+ fn send_rpc(
+ &self,
+ addr: std::net::SocketAddr,
+ msg: aingle_raft::network::RaftMessage,
+ ) -> std::pin::Pin<
+ Box<
+ dyn std::future::Future