diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 00000000..14ac3045 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,25 @@ +# Cargo audit configuration for AIngle +# https://rustsec.org/ + +[advisories] +# Advisories to ignore in product crates +ignore = [ + # rsa 0.9.10 — Marvin Attack timing sidechannel (no upstream fix) + # Pulled in by jsonwebtoken 10.x; JWT auth uses HMAC/EdDSA, not RSA decryption + "RUSTSEC-2023-0071", + + # bincode 2.0.1 — unmaintained advisory, still functional (aingle_graph) + "RUSTSEC-2025-0141", + + # async-std — unmaintained (transitive via async-tungstenite in aingle_minimal) + "RUSTSEC-2025-0052", + + # fxhash — unmaintained (transitive via sled in aingle_graph) + "RUSTSEC-2025-0057", + + # instant — unmaintained (transitive via sled → parking_lot 0.11) + "RUSTSEC-2024-0384", + + # paste — unmaintained (transitive via wasmer and candle-core/gemm) + "RUSTSEC-2024-0436", +] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e23ab1b1..da8bd432 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,7 +195,7 @@ jobs: - name: Test aingle_minimal with all transports run: cargo test -p aingle_minimal --features "sqlite,coap,quic,webrtc,rest" - # Note: Other crates (aingle_graph, hope_agents, titans_memory, etc.) + # Note: Other crates (aingle_graph, kaneru, ineru, etc.) # will be added to CI once they pass all checks independently # Security audit for dependencies @@ -252,7 +252,7 @@ jobs: continue-on-error: true - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v7 with: files: lcov.info fail_ci_if_error: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3a621df..911f2b94 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,18 +28,23 @@ jobs: - target: x86_64-unknown-linux-gnu os: ubuntu-latest name: aingle-minimal-linux-x86_64 + cortex_name: aingle-cortex-linux-x86_64 - target: aarch64-unknown-linux-gnu os: ubuntu-latest name: aingle-minimal-linux-aarch64 + cortex_name: aingle-cortex-linux-aarch64 - target: x86_64-apple-darwin os: macos-latest name: aingle-minimal-macos-x86_64 + cortex_name: aingle-cortex-macos-x86_64 - target: aarch64-apple-darwin os: macos-latest name: aingle-minimal-macos-aarch64 + cortex_name: aingle-cortex-macos-aarch64 - target: x86_64-pc-windows-msvc os: windows-latest name: aingle-minimal-windows-x86_64.exe + cortex_name: aingle-cortex-windows-x86_64.exe steps: - uses: actions/checkout@v6 @@ -74,31 +79,48 @@ jobs: restore-keys: | ${{ runner.os }}-${{ matrix.target }}-cargo-release- - - name: Build release binary + - name: Build aingle-minimal run: cargo build --release --target ${{ matrix.target }} -p aingle_minimal --features rest env: CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc - - name: Create archive (Unix) + - name: Build aingle-cortex + run: cargo build --release --target ${{ matrix.target }} -p aingle_cortex + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + + - name: Create archives (Unix) if: runner.os != 'Windows' run: | mkdir -p dist cp target/${{ matrix.target }}/release/aingle-minimal dist/${{ matrix.name }} - cd dist && tar -czvf ${{ matrix.name }}.tar.gz ${{ matrix.name }} + cp target/${{ matrix.target }}/release/aingle-cortex dist/${{ matrix.cortex_name }} + cd dist + tar -czvf ${{ matrix.name }}.tar.gz ${{ matrix.name }} + tar -czvf ${{ matrix.cortex_name }}.tar.gz ${{ matrix.cortex_name }} - - name: Create archive (Windows) + - name: Create archives (Windows) if: runner.os == 'Windows' run: | mkdir dist copy target\${{ matrix.target }}\release\aingle-minimal.exe dist\${{ matrix.name }} - cd dist && 7z a -tzip ${{ matrix.name }}.zip ${{ matrix.name }} + copy target\${{ matrix.target }}\release\aingle-cortex.exe dist\${{ matrix.cortex_name }} + cd dist + 7z a -tzip ${{ matrix.name }}.zip ${{ matrix.name }} + 7z a -tzip ${{ matrix.cortex_name }}.zip ${{ matrix.cortex_name }} - - name: Upload artifact - uses: actions/upload-artifact@v6 + - name: Upload aingle-minimal artifact + uses: actions/upload-artifact@v7 with: name: ${{ matrix.name }} path: dist/${{ matrix.name }}.* + - name: Upload aingle-cortex artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ matrix.cortex_name }} + path: dist/${{ matrix.cortex_name }}.* + release: name: Create Release needs: build @@ -107,12 +129,12 @@ jobs: - uses: actions/checkout@v6 - name: Download all artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: path: artifacts - name: Display structure of downloaded files - run: ls -la artifacts/ + run: ls -laR artifacts/ - name: Generate changelog id: changelog @@ -148,6 +170,51 @@ jobs: sudo apt-get update sudo apt-get install -y libsodium-dev libssl-dev pkg-config + # Phase 1: Crates with no internal dependencies + - name: Publish aingle_graph + run: cargo publish -p aingle_graph --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Publish aingle_zk + run: cargo publish -p aingle_zk --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Publish ineru + run: cargo publish -p ineru --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Publish aingle_ai + run: cargo publish -p aingle_ai --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Publish ai_hash + run: cargo publish -p ai_hash --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Wait for crates.io index update + run: sleep 30 + + # Phase 2: Crates depending on Phase 1 + - name: Publish aingle_zome_types + run: cargo publish -p aingle_zome_types --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Publish aingle_logic + run: cargo publish -p aingle_logic --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Publish kaneru + run: cargo publish -p kaneru --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + + - name: Wait for crates.io index update + run: sleep 30 + + # Phase 3: Crates depending on Phase 2 + - name: Publish aingle_cortex + run: cargo publish -p aingle_cortex --token ${{ secrets.CARGO_REGISTRY_TOKEN }} + continue-on-error: true + - name: Publish aingle_minimal run: cargo publish -p aingle_minimal --token ${{ secrets.CARGO_REGISTRY_TOKEN }} - continue-on-error: true # May fail if version already published + continue-on-error: true diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 202b2bd7..bd585f82 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -46,7 +46,7 @@ jobs: echo "No critical vulnerabilities found" - name: Upload audit report - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: security-audit-report path: audit-report.json diff --git a/.gitignore b/.gitignore index 653625d9..bb7ee1d0 100644 --- a/.gitignore +++ b/.gitignore @@ -112,6 +112,7 @@ llm-instructions.md # =========================================== *.profraw *.profdata +aingle_iot.db # Logs *.log diff --git a/CHANGELOG.md b/CHANGELOG.md index c1223606..c8ef1734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.0] - 2026-03-09 + +### Changed +- Rename `titans_memory` crate to `ineru` — Ineru neural-inspired memory system +- Rename `hope_agents` crate to `kaneru` — Kaneru multi-agent execution system +- Rename `TitansMemory` → `IneruMemory`, `TitansConfig` → `IneruConfig` +- Rename `HopeAgent` → `KaneruAgent`, `HopeConfig` → `KaneruConfig` +- Move `crate::titans` module to `crate::ineru` in `aingle_ai` +- Move `crate::hope` module to `crate::kaneru` in `aingle_ai` +- Bump all main crate versions to 0.4.0 (unified version scheme) +- Update all internal dependency version specs to match +- Standardize copyright headers and license metadata across all crates + ## [0.1.0] - 2024-12-17 ### Technical Requirements @@ -43,9 +56,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Anonymous credentials #### AI & Machine Learning -- `aingle_ai` - AI integration layer (Titans Memory architecture) -- `titans_memory` - Neural-inspired memory system (STM/LTM) -- `hope_agents` - HOPE Agent framework +- `aingle_ai` - AI integration layer (Ineru architecture) +- `ineru` - Ineru neural-inspired memory system (STM/LTM) +- `kaneru` - Kaneru agent framework - Q-Learning - DQN (Deep Q-Network) - PPO (Proximal Policy Optimization) diff --git a/Cargo.lock b/Cargo.lock index 077fa8f3..c711557b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,54 +2,13 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" -dependencies = [ - "lazy_static", - "regex", -] - [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ - "gimli 0.32.3", -] - -[[package]] -name = "adk" -version = "0.0.1" -dependencies = [ - "adk_derive", - "ai_fixt", - "ai_hash", - "aingle_middleware_bytes", - "aingle_wasmer_guest", - "aingle_zome_types", - "mockall", - "paste", - "serde", - "serde_bytes", - "thiserror 2.0.17", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "adk_derive" -version = "0.0.1" -dependencies = [ - "aingle_zome_types", - "paste", - "proc-macro2", - "quote", - "syn 1.0.109", + "gimli", ] [[package]] @@ -58,28 +17,13 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aead" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" -dependencies = [ - "generic-array", -] - [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -89,8 +33,8 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "cfg-if 1.0.4", - "cipher 0.4.4", + "cfg-if", + "cipher", "cpufeatures", ] @@ -100,9 +44,9 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ - "aead 0.5.2", + "aead", "aes", - "cipher 0.4.4", + "cipher", "ctr", "ghash", "subtle", @@ -114,7 +58,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] @@ -125,8 +69,7 @@ version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ - "cfg-if 1.0.4", - "getrandom 0.3.4", + "cfg-if", "once_cell", "version_check", "zerocopy", @@ -145,163 +88,19 @@ dependencies = [ name = "ai_autonomous_agent" version = "0.1.0" dependencies = [ - "hope_agents 0.2.0", + "ineru", + "kaneru", "rand 0.9.2", "serde", "serde_json", - "titans_memory 0.2.0", -] - -[[package]] -name = "ai_fixt" -version = "0.0.1" -dependencies = [ - "aingle_middleware_bytes", - "lazy_static", - "parking_lot 0.12.5", - "paste", - "rand 0.9.2", - "rand_core 0.6.4", - "serde", - "strum 0.26.3", - "strum_macros 0.26.4", -] - -[[package]] -name = "ai_fixt_test" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "paste", -] - -[[package]] -name = "ai_hash" -version = "0.0.2" -dependencies = [ - "ai_fixt", - "aingle_middleware_bytes", - "arbitrary", - "base64 0.21.7", - "blake2b_simd", - "derive_more 0.99.20", - "rand 0.9.2", - "rusqlite", - "serde", - "serde_bytes", - "serde_json", - "thiserror 2.0.17", - "tracing", -] - -[[package]] -name = "aingle" -version = "0.0.100" -dependencies = [ - "adk", - "ai_fixt", - "ai_hash", - "aingle-observability", - "aingle_ai", - "aingle_cascade", - "aingle_conductor_api", - "aingle_cortex", - "aingle_keystore", - "aingle_middleware_bytes", - "aingle_p2p", - "aingle_sqlite", - "aingle_state", - "aingle_test_wasm_common", - "aingle_types", - "aingle_util", - "aingle_wasm_test_utils", - "aingle_wasmer_host", - "aingle_websocket", - "aingle_zome_types", - "anyhow", - "assert_cmd", - "async-trait", - "base64 0.21.7", - "byteorder", - "cfg-if 0.1.10", - "chrono", - "criterion", - "crypto_secretbox", - "derive_more 0.99.20", - "directories 2.0.2", - "either", - "fallible-iterator 0.2.0", - "futures", - "ghost_actor", - "human-panic", - "itertools 0.10.5", - "kitsune_p2p", - "kitsune_p2p_types", - "lazy_static", - "maplit", - "matches", - "mockall", - "mr_bundle", - "must_future", - "nanoid 0.4.0", - "num_cpus", - "once_cell", - "parking_lot 0.12.5", - "predicates 1.0.8", - "pretty_assertions", - "rand 0.9.2", - "ring 0.17.14", - "rusqlite", - "rustls 0.23.35", - "sd-notify", - "serde", - "serde_json", - "serde_yml", - "serial_test", - "shrinkwraprs", - "structopt", - "strum 0.26.3", - "tempdir", - "test-case", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "toml 0.5.11", - "tracing", - "tracing-futures", - "tracing-subscriber", - "unwrap_to", - "url 1.7.2", - "url2", - "url_serde", - "uuid", - "wasmer", -] - -[[package]] -name = "aingle-observability" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08c3edaef0d75240b73fb33d8509b01fa7dd5043424c6b07e550a2b26d39fa3" -dependencies = [ - "chrono", - "derive_more 0.99.20", - "inferno", - "once_cell", - "serde_json", - "thiserror 1.0.69", - "tracing", - "tracing-core", - "tracing-serde 0.1.3", - "tracing-subscriber", ] [[package]] name = "aingle_ai" -version = "0.1.0" +version = "0.6.3" dependencies = [ "blake2", - "candle-core", + "candle-core 0.9.2", "candle-nn", "chrono", "criterion", @@ -312,142 +111,25 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", - "toml 0.8.23", + "toml", "tracing", ] -[[package]] -name = "aingle_cascade" -version = "0.0.1" -dependencies = [ - "adk", - "adk_derive", - "ai_fixt", - "ai_hash", - "aingle-observability", - "aingle_middleware_bytes", - "aingle_p2p", - "aingle_sqlite", - "aingle_state", - "aingle_types", - "aingle_zome_types", - "async-trait", - "derive_more 0.99.20", - "either", - "fallible-iterator 0.2.0", - "futures", - "ghost_actor", - "kitsune_p2p", - "matches", - "mockall", - "pretty_assertions", - "serde", - "serde_derive", - "thiserror 2.0.17", - "tokio", - "tracing", - "tracing-futures", -] - -[[package]] -name = "aingle_cli" -version = "0.0.1" -dependencies = [ - "aingle-observability", - "aingle_cli_bundle", - "aingle_cli_sandbox", - "anyhow", - "futures", - "structopt", - "tokio", -] - -[[package]] -name = "aingle_cli_bundle" -version = "0.0.1" -dependencies = [ - "aingle_middleware_bytes", - "aingle_types", - "aingle_util", - "anyhow", - "assert_cmd", - "matches", - "mr_bundle", - "predicates 1.0.8", - "serde", - "serde_bytes", - "serde_yml", - "structopt", - "tempdir", - "thiserror 2.0.17", - "tokio", -] - -[[package]] -name = "aingle_cli_sandbox" -version = "0.0.1" -dependencies = [ - "aingle-observability", - "aingle_cli_bundle", - "aingle_conductor_api", - "aingle_p2p", - "aingle_types", - "aingle_websocket", - "ansi_term", - "anyhow", - "assert_cmd", - "chrono", - "futures", - "lazy_static", - "matches", - "nanoid 0.4.0", - "portpicker", - "serde_yml", - "structopt", - "tokio", - "tracing", - "url2", - "walkdir", -] - -[[package]] -name = "aingle_conductor_api" -version = "0.0.1" -dependencies = [ - "ai_hash", - "aingle-observability", - "aingle_middleware_bytes", - "aingle_p2p", - "aingle_state", - "aingle_types", - "aingle_zome_types", - "derive_more 0.99.20", - "directories 2.0.2", - "kitsune_p2p", - "matches", - "serde", - "serde_derive", - "serde_yml", - "structopt", - "thiserror 2.0.17", - "tracing", - "url2", -] - [[package]] name = "aingle_contracts" -version = "0.2.0" +version = "0.6.3" dependencies = [ "blake3", "dashmap 6.1.0", "hex", + "rand 0.9.2", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-test", "tracing", @@ -456,33 +138,47 @@ dependencies = [ [[package]] name = "aingle_cortex" -version = "0.2.1" +version = "0.6.3" dependencies = [ - "aingle_graph 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "aingle_logic 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "aingle_zk 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "aingle_zome_types", + "aingle_graph", + "aingle_logic", + "aingle_raft", + "aingle_wal", + "aingle_zk", "argon2", "async-graphql", "async-graphql-axum", - "axum 0.7.9", - "axum-client-ip", + "axum", "blake3", "chrono", "dashmap 6.1.0", + "dirs", + "ed25519-dalek", "futures", + "hex", + "if-addrs 0.13.4", + "ineru", "jsonwebtoken", "log", + "mdns-sd", + "once_cell", + "openraft", + "quinn", "rand 0.9.2", + "rcgen", "regex", "reqwest", + "rustls", + "rustls-pemfile", "serde", "serde_json", + "sled", "spargebra", + "subtle", "tempfile", - "thiserror 2.0.17", - "titans_memory 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 2.0.18", "tokio", + "tokio-rustls", "tokio-stream", "tokio-test", "tower", @@ -495,14 +191,16 @@ dependencies = [ [[package]] name = "aingle_graph" -version = "0.2.0" +version = "0.6.3" dependencies = [ "bincode", "blake3", "chrono", "criterion", - "indexmap 2.12.1", + "ed25519-dalek", + "indexmap", "log", + "rand 0.9.2", "rio_api", "rio_turtle", "rocksdb", @@ -511,105 +209,29 @@ dependencies = [ "serde_json", "sled", "tempfile", - "thiserror 2.0.17", -] - -[[package]] -name = "aingle_graph" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac66b77dcf2f24346faf2e513ed5bf15e8073371b041131b9f2a84d3d222d8e7" -dependencies = [ - "bincode", - "blake3", - "chrono", - "indexmap 2.12.1", - "log", - "serde", - "serde_json", - "sled", - "thiserror 2.0.17", -] - -[[package]] -name = "aingle_keystore" -version = "0.0.1" -dependencies = [ - "ai_hash", - "aingle_middleware_bytes", - "aingle_zome_types", - "ghost_actor", - "lair_keystore_api", - "lair_keystore_client", - "serde", - "serde_bytes", - "sodoken", - "thiserror 2.0.17", - "tokio", - "tracing", + "thiserror 2.0.18", + "uuid", ] [[package]] name = "aingle_logic" -version = "0.2.0" +version = "0.6.3" dependencies = [ - "aingle_graph 0.2.0", + "aingle_graph", "chrono", "hex", - "indexmap 2.12.1", + "indexmap", "log", "regex", "serde", "serde_json", "tempfile", - "thiserror 2.0.17", -] - -[[package]] -name = "aingle_logic" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939805a8c3feb94384ffcb32ba2982c7474753edcc5201e71b98caaa85134195" -dependencies = [ - "aingle_graph 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "chrono", - "hex", - "indexmap 2.12.1", - "log", - "regex", - "serde", - "serde_json", - "thiserror 2.0.17", -] - -[[package]] -name = "aingle_middleware_bytes" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c03f4a0a5203dc8fb86097bc33c02a54bd5813c8d3d67595fe3e9d0a2272ff0" -dependencies = [ - "aingle_middleware_bytes_derive", - "rmp-serde 0.14.4", - "serde", - "serde-transcode", - "serde_bytes", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "aingle_middleware_bytes_derive" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ab1e16ded0d7d76ccf32e8e194bedf284237c24d8a5fa46fe60a01f3ba4cba" -dependencies = [ - "quote", - "syn 1.0.109", + "thiserror 2.0.18", ] [[package]] name = "aingle_minimal" -version = "0.3.0" +version = "0.6.3" dependencies = [ "async-io", "async-tungstenite", @@ -617,231 +239,74 @@ dependencies = [ "btleplug", "bytes", "chrono", - "clap 4.5.53", + "clap", "coap-lite", "criterion", + "ed25519-dalek", "embedded-hal 1.0.0", "embedded-hal-async", "env_logger", "esp32-nimble", "futures-util", - "hope_agents 0.1.0", + "hex", "if-addrs 0.13.4", + "ineru", + "kaneru", "ledger-transport", "ledger-transport-hid", "log", "mdns-sd", "quinn", "rand 0.9.2", - "rcgen 0.13.2", + "rcgen", "rocksdb", "rusqlite", - "rustls 0.23.35", + "rustls", "semver", "serde", "serde_json", "smol", "tiny_http", - "titans_memory 0.1.0", "uuid", "webrtc", ] [[package]] -name = "aingle_p2p" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "ai_hash", - "aingle-observability", - "aingle_keystore", - "aingle_middleware_bytes", - "aingle_types", - "aingle_util", - "aingle_zome_types", - "async-trait", - "futures", - "ghost_actor", - "kitsune_p2p", - "kitsune_p2p_types", - "mockall", - "serde", - "serde_bytes", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "aingle_sqlite" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "ai_hash", - "aingle-observability", - "aingle_middleware_bytes", - "aingle_util", - "aingle_zome_types", - "anyhow", - "byteorder", - "cfg-if 0.1.10", - "chashmap", - "chrono", - "derive_more 0.99.20", - "either", - "failure", - "fallible-iterator 0.2.0", - "futures", - "kitsune_p2p", - "lazy_static", - "must_future", - "nanoid 0.4.0", - "num_cpus", - "once_cell", - "page_size", - "parking_lot 0.12.5", - "r2d2", - "r2d2_sqlite", - "rand 0.9.2", - "rmp-serde 0.15.5", - "rusqlite", - "scheduled-thread-pool", - "serde", - "serde_derive", - "shrinkwraprs", - "tempdir", - "thiserror 2.0.17", - "tokio", - "tracing", - "tracing-futures", -] - -[[package]] -name = "aingle_state" -version = "0.0.1" -dependencies = [ - "adk", - "ai_fixt", - "ai_hash", - "aingle-observability", - "aingle_keystore", - "aingle_middleware_bytes", - "aingle_p2p", - "aingle_sqlite", - "aingle_types", - "aingle_util", - "aingle_wasm_test_utils", - "aingle_zome_types", - "anyhow", - "base64 0.21.7", - "byteorder", - "chrono", - "derive_more 0.99.20", - "either", - "fallible-iterator 0.2.0", - "kitsune_p2p", - "matches", - "mockall", - "parking_lot 0.12.5", - "pretty_assertions", - "serde", - "serde_json", - "shrinkwraprs", - "tempdir", - "thiserror 2.0.17", - "tokio", - "tracing", - "tracing-futures", -] - -[[package]] -name = "aingle_test_wasm_common" -version = "0.0.1" -dependencies = [ - "adk", - "serde", -] - -[[package]] -name = "aingle_types" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "ai_hash", - "aingle-observability", - "aingle_keystore", - "aingle_middleware_bytes", - "aingle_sqlite", - "aingle_util", - "aingle_zome_types", - "anyhow", - "arbitrary", - "async-trait", - "automap", - "backtrace", - "base64 0.21.7", - "cfg-if 0.1.10", - "chrono", - "derive_builder", - "derive_more 0.99.20", - "either", - "flate2", - "futures", - "itertools 0.10.5", - "lazy_static", - "maplit", - "matches", - "mockall", - "mr_bundle", - "must_future", - "nanoid 0.4.0", - "rand 0.9.2", - "regex", - "rusqlite", - "serde", - "serde_bytes", - "serde_derive", - "serde_json", - "serde_yml", - "shrinkwraprs", - "strum 0.26.3", - "strum_macros 0.26.4", - "tempdir", - "thiserror 2.0.17", - "tokio", - "tracing", -] - -[[package]] -name = "aingle_util" -version = "0.0.1" +name = "aingle_raft" +version = "0.6.3" dependencies = [ - "backtrace", - "cfg-if 0.1.10", - "derive_more 0.99.20", - "dunce", - "futures", - "num_cpus", - "once_cell", + "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.2.0" +version = "0.6.3" dependencies = [ - "aingle_graph 0.2.0", + "aingle_graph", "aingle_minimal", - "axum 0.7.9", + "axum", "chrono", - "clap 4.5.53", + "clap", "env_logger", "futures", "log", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-test", "tower", @@ -850,163 +315,35 @@ dependencies = [ ] [[package]] -name = "aingle_wasm_test_utils" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "ai_hash", - "aingle_types", - "aingle_util", - "aingle_zome_types", - "rand 0.9.2", - "strum 0.26.3", - "strum_macros 0.26.4", - "toml 0.5.11", - "walkdir", -] - -[[package]] -name = "aingle_wasmer_codec" -version = "0.0.1" -source = "git+https://github.com/ApiliumCode/aingle-wasmer.git?branch=main#d1406b8fc6d8240722095b5223659bc330c2eb6b" -dependencies = [ - "aingle_wasmer_common", - "bytes", - "crc32fast", -] - -[[package]] -name = "aingle_wasmer_common" -version = "0.0.1" -source = "git+https://github.com/ApiliumCode/aingle-wasmer.git?branch=main#d1406b8fc6d8240722095b5223659bc330c2eb6b" -dependencies = [ - "aingle_middleware_bytes", - "serde", -] - -[[package]] -name = "aingle_wasmer_guest" -version = "0.0.1" -source = "git+https://github.com/ApiliumCode/aingle-wasmer.git?branch=main#d1406b8fc6d8240722095b5223659bc330c2eb6b" -dependencies = [ - "aingle_middleware_bytes", - "aingle_wasmer_codec", - "aingle_wasmer_common", - "bumpalo", - "rmp-serde 1.3.0", - "serde", - "serde_bytes", -] - -[[package]] -name = "aingle_wasmer_host" -version = "0.0.1" -source = "git+https://github.com/ApiliumCode/aingle-wasmer.git?branch=main#d1406b8fc6d8240722095b5223659bc330c2eb6b" -dependencies = [ - "aingle_middleware_bytes", - "aingle_wasmer_codec", - "aingle_wasmer_common", - "bytes", - "parking_lot 0.12.5", - "rmp-serde 1.3.0", - "serde", - "serde_bytes", - "thiserror 2.0.17", - "tracing", - "wasmer", - "wasmer-middlewares", -] - -[[package]] -name = "aingle_websocket" -version = "0.0.1" +name = "aingle_wal" +version = "0.6.3" dependencies = [ - "aingle-observability", - "aingle_middleware_bytes", - "aingle_types", - "criterion", - "futures", - "ghost_actor", - "linefeed", - "must_future", - "nanoid 0.4.0", - "net2", - "parking_lot 0.12.5", + "bincode", + "blake3", + "chrono", "serde", - "serde_bytes", - "stream-cancel", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tokio-tungstenite 0.24.0", - "tracing", - "tracing-futures", - "tungstenite 0.24.0", - "unwrap_to", - "url2", + "serde_json", + "tempfile", ] [[package]] name = "aingle_zk" -version = "0.2.0" +version = "0.6.3" dependencies = [ - "bincode", "blake3", "bulletproofs", "criterion", - "curve25519-dalek 4.1.3", + "curve25519-dalek", "curve25519-dalek-ng", "hex", "merlin", "rand 0.8.5", - "rand_core 0.6.4", - "rayon", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.17", -] - -[[package]] -name = "aingle_zk" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e863f022f4906f106053c2fbf16cac7687c1d3318a173003b8ca1e435a8a41f8" -dependencies = [ - "bincode", - "blake3", - "curve25519-dalek 4.1.3", - "hex", - "rand 0.8.5", - "rand_core 0.6.4", "rayon", "serde", "serde_json", - "sha2", - "thiserror 2.0.17", -] - -[[package]] -name = "aingle_zome_types" -version = "0.0.2" -dependencies = [ - "ai_fixt", - "ai_hash", - "aingle_middleware_bytes", - "chrono", - "derive_builder", - "nanoid 0.4.0", - "num_enum", - "paste", - "rand 0.9.2", - "rusqlite", - "serde", - "serde_bytes", - "shrinkwraprs", - "strum 0.26.3", + "sha2 0.10.9", "subtle", - "thiserror 2.0.17", - "tracing", + "thiserror 2.0.18", ] [[package]] @@ -1039,15 +376,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anstream" version = "0.6.21" @@ -1098,26 +426,35 @@ 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.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" @@ -1137,12 +474,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "arrayvec" version = "0.7.6" @@ -1165,10 +496,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] -name = "ascii_utils" -version = "0.9.3" +name = "askama" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08e1676b346cadfec169374f949d7490fd80a24193d37d2afce0c047cf695e57" +dependencies = [ + "askama_macros", + "itoa", + "percent-encoding", + "serde", + "serde_json", +] + +[[package]] +name = "askama_derive" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7661ff56517787343f376f75db037426facd7c8d3049cef8911f1e75016f3a37" +dependencies = [ + "askama_parser", + "memchr", + "proc-macro2", + "quote", + "rustc-hash 2.1.1", + "syn 2.0.117", +] + +[[package]] +name = "askama_macros" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713ee4dbfd1eb719c2dab859465b01fa1d21cb566684614a713a6b7a99a4e47b" +dependencies = [ + "askama_derive", +] + +[[package]] +name = "askama_parser" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +checksum = "1d62d674238a526418b30c0def480d5beadb9d8964e7f38d635b03bf639c704c" +dependencies = [ + "rustc-hash 2.1.1", + "unicode-ident", + "winnow", +] [[package]] name = "asn1-rs" @@ -1194,8 +566,8 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -1206,32 +578,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "assert_cmd" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcbb6924530aa9e0432442af08bbcafdad182db80d2e560da42a6d442535bf85" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates 3.1.3", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -1259,22 +606,21 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -1312,47 +658,47 @@ dependencies = [ [[package]] name = "async-graphql" -version = "7.0.17" +version = "8.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "036618f842229ba0b89652ffe425f96c7c16a49f7e3cb23b56fca7f61fd74980" +checksum = "992eec3a71c482790dc346a311a3cdcb1e9006b652a0b87e154b42adb0918b4c" dependencies = [ + "askama", "async-graphql-derive", "async-graphql-parser", "async-graphql-value", - "async-stream 0.3.6", "async-trait", - "base64 0.22.1", + "asynk-strim", + "base64", + "blocking", "bytes", "chrono", - "fast_chemail", - "fnv", - "futures-timer", "futures-util", - "handlebars", - "http 1.4.0", - "indexmap 2.12.1", + "http", + "indexmap", "mime", - "multer 3.1.0", + "multer", "num-traits", "pin-project-lite", "regex", + "rustc-hash 2.1.1", "serde", "serde_json", "serde_urlencoded", "static_assertions_next", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.18", + "tokio", "uuid", ] [[package]] name = "async-graphql-axum" -version = "7.0.17" +version = "8.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8725874ecfbf399e071150b8619c4071d7b2b7a2f117e173dddef53c6bdb6bb1" +checksum = "908f39c915dfdb310d87db08aac6ffc9704ddd210a7f7e0af8348822cff61744" dependencies = [ "async-graphql", - "axum 0.8.7", + "axum", "bytes", "futures-util", "serde_json", @@ -1364,26 +710,26 @@ dependencies = [ [[package]] name = "async-graphql-derive" -version = "7.0.17" +version = "8.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd45deb3dbe5da5cdb8d6a670a7736d735ba65b455328440f236dfb113727a3d" +checksum = "0739ca16bca33d4071c1b45f19d902d6894696f997b9b4c9c0b316c509b4f83f" dependencies = [ - "Inflector", "async-graphql-parser", - "darling 0.20.11", + "darling 0.23.0", + "heck 0.5.0", "proc-macro-crate", "proc-macro2", "quote", - "strum 0.26.3", - "syn 2.0.111", - "thiserror 1.0.69", + "strum 0.28.0", + "syn 2.0.117", + "thiserror 2.0.18", ] [[package]] name = "async-graphql-parser" -version = "7.0.17" +version = "8.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b7607e59424a35dadbc085b0d513aa54ec28160ee640cf79ec3b634eba66d3" +checksum = "d85b5e90b08d72410b40c71a1878d78308a190f3bbb45a82ea000d0dbc4133a8" dependencies = [ "async-graphql-value", "pest", @@ -1393,12 +739,12 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "7.0.17" +version = "8.0.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ecdaff7c9cffa3614a9f9999bf9ee4c3078fe3ce4d6a6e161736b56febf2de" +checksum = "fa3b3ae38aad471922da0c5be8318b58dcf0b9e0cf570aa5b57d7df4ef5e6c9e" dependencies = [ "bytes", - "indexmap 2.12.1", + "indexmap", "serde", "serde_json", ] @@ -1409,23 +755,23 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ - "autocfg 1.5.0", - "cfg-if 1.0.4", + "autocfg", + "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", - "polling 3.11.0", - "rustix 1.1.2", + "polling", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -1455,10 +801,10 @@ dependencies = [ "async-signal", "async-task", "blocking", - "cfg-if 1.0.4", + "cfg-if", "event-listener 5.4.1", "futures-lite", - "rustix 1.1.2", + "rustix 1.1.4", ] [[package]] @@ -1470,10 +816,10 @@ dependencies = [ "async-io", "async-lock", "atomic-waker", - "cfg-if 1.0.4", + "cfg-if", "futures-core", "futures-io", - "rustix 1.1.2", + "rustix 1.1.4", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -1485,12 +831,10 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" dependencies = [ - "async-attributes", "async-channel 1.9.0", "async-global-executor", "async-io", "async-lock", - "async-process", "crossbeam-utils", "futures-channel", "futures-core", @@ -1507,49 +851,6 @@ dependencies = [ "wasm-bindgen-futures", ] -[[package]] -name = "async-stream" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" -dependencies = [ - "async-stream-impl 0.2.1", - "futures-core", -] - -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl 0.3.6", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "async-task" version = "4.7.1" @@ -1564,7 +865,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -1582,108 +883,49 @@ dependencies = [ ] [[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" -dependencies = [ - "autocfg 1.5.0", -] - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "automap" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99d887f4066f8a1b4a713a8121fab07ff543863ac86177ebdee6b5cb18acf12" -dependencies = [ - "cfg-if 1.0.4", - "derive_more 0.99.20", - "serde", - "shrinkwraprs", -] - -[[package]] -name = "axum" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" -dependencies = [ - "async-trait", - "axum-core 0.4.5", - "axum-macros", - "base64 0.22.1", - "bytes", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.8.1", - "hyper-util", - "itoa", - "matchit 0.7.3", - "memchr", - "mime", - "percent-encoding 2.3.2", - "pin-project-lite", - "rustversion", - "serde", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite 0.24.0", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] +name = "asynk-strim" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52697735bdaac441a29391a9e97102c74c6ef0f9b60a40cf109b1b404e29d2f6" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "axum-core 0.5.5", - "base64 0.22.1", + "axum-core", + "axum-macros", + "base64", "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "itoa", - "matchit 0.8.4", + "matchit", "memchr", "mime", - "percent-encoding 2.3.2", + "percent-encoding", "pin-project-lite", "serde_core", "serde_json", @@ -1692,55 +934,23 @@ dependencies = [ "sha1", "sync_wrapper", "tokio", - "tokio-tungstenite 0.28.0", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", "tracing", ] -[[package]] -name = "axum-client-ip" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff8ee1869817523c8f91c20bf17fd932707f66c2e7e0b0f811b29a227289562" -dependencies = [ - "axum 0.8.7", - "forwarded-header-value", - "serde", -] - -[[package]] -name = "axum-core" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" -dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "mime", - "pin-project-lite", - "rustversion", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -1752,13 +962,13 @@ dependencies = [ [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -1768,7 +978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", - "cfg-if 1.0.4", + "cfg-if", "libc", "miniz_oxide", "object 0.37.3", @@ -1782,18 +992,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1802,52 +1000,42 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" -version = "1.3.3" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" dependencies = [ + "bincode_derive", "serde", + "unty", ] [[package]] -name = "bindgen" -version = "0.69.5" +name = "bincode_derive" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" dependencies = [ - "bitflags 2.10.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 1.1.0", - "shlex", - "syn 2.0.111", - "which", + "virtue", ] [[package]] name = "bindgen" -version = "0.70.1" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.12.1", + "lazy_static", + "lazycell", "log", "prettyplease", "proc-macro2", @@ -1855,7 +1043,8 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.111", + "syn 2.0.117", + "which 4.4.2", ] [[package]] @@ -1864,25 +1053,18 @@ version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cexpr", "clang-sys", "itertools 0.13.0", + "log", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.111", -] - -[[package]] -name = "bit-vec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" -dependencies = [ - "serde", + "syn 2.0.117", ] [[package]] @@ -1893,68 +1075,63 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] -name = "blake2" -version = "0.10.6" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "digest 0.10.7", + "funty", + "radium", + "tap", + "wyz", ] [[package]] -name = "blake2b_simd" -version = "0.5.11" +name = "blake2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq 0.1.5", + "digest 0.10.7", ] [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", - "arrayvec 0.7.6", + "arrayvec", "cc", - "cfg-if 1.0.4", - "constant_time_eq 0.3.1", + "cfg-if", + "constant_time_eq", + "cpufeatures", ] [[package]] name = "block-buffer" -version = "0.9.0" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "block-padding 0.2.1", "generic-array", ] [[package]] name = "block-buffer" -version = "0.10.4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ - "generic-array", + "hybrid-array", ] -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - [[package]] name = "block-padding" version = "0.3.3" @@ -1970,16 +1147,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2 0.6.3", + "objc2", ] [[package]] @@ -1995,24 +1163,13 @@ dependencies = [ "piper", ] -[[package]] -name = "bloomfilter" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c541c70a910b485670304fd420f0eab8f7bde68439db6a8d98819c3d2774d7e2" -dependencies = [ - "bit-vec", - "getrandom 0.2.16", - "siphasher", -] - [[package]] name = "bluez-async" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84ae4213cc2a8dc663acecac67bbdad05142be4d8ef372b6903abf878b0c690a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bluez-generated", "dbus", "dbus-tokio", @@ -2021,7 +1178,7 @@ dependencies = [ "log", "serde", "serde-xml-rs", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "uuid", ] @@ -2035,6 +1192,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" @@ -2042,7 +1222,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", - "regex-automata", "serde", ] @@ -2053,7 +1232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a11621cb2c8c024e444734292482b1ad86fb50ded066cf46252e46643c8748" dependencies = [ "async-trait", - "bitflags 2.10.0", + "bitflags 2.11.0", "bluez-async", "dashmap 6.1.0", "dbus", @@ -2061,12 +1240,12 @@ dependencies = [ "jni 0.19.0", "jni-utils", "log", - "objc2 0.5.2", + "objc2", "objc2-core-bluetooth", - "objc2-foundation 0.2.2", + "objc2-foundation", "once_cell", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "uuid", @@ -2084,34 +1263,50 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "bulletproofs" -version = "4.0.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e698f1df446cc6246afd823afbe2d121134d089c9102c1dd26d1264991ba32" +checksum = "012e2e5f88332083bd4235d445ae78081c00b2558443821a9ca5adfe1070073d" dependencies = [ "byteorder", "clear_on_drop", - "curve25519-dalek-ng", - "digest 0.9.0", + "curve25519-dalek", + "digest 0.10.7", + "group", "merlin", "rand 0.8.5", "rand_core 0.6.4", "serde", "serde_derive", "sha3", - "subtle-ng", + "subtle", "thiserror 1.0.69", ] [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "byte-unit" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "8c6d47a4e2961fb8721bcfc54feae6455f2f64e7054f9bc67e875f0e77f4c58d" +dependencies = [ + "rust_decimal", + "schemars", + "serde", + "utf8-width", +] [[package]] name = "bytecheck" @@ -2155,14 +1350,14 @@ checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -2175,7 +1370,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -2186,9 +1381,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -2219,31 +1414,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f1b20174c1707e20f4cb364a355b449803c03e9b0c9193324623cf9787a4e00" dependencies = [ "byteorder", - "gemm", + "gemm 0.17.1", "half", - "memmap2 0.9.9", + "memmap2 0.9.10", "num-traits", "num_cpus", "rand 0.8.5", "rand_distr 0.4.3", "rayon", - "safetensors", + "safetensors 0.4.5", "thiserror 1.0.69", "yoke 0.7.5", "zip 0.6.6", ] +[[package]] +name = "candle-core" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c15b675b80d994b2eadb20a4bbe434eabeb454eac3ee5e2b4cf6f147ee9be091" +dependencies = [ + "byteorder", + "float8", + "gemm 0.19.0", + "half", + "libm", + "memmap2 0.9.10", + "num-traits", + "num_cpus", + "rand 0.9.2", + "rand_distr 0.5.1", + "rayon", + "safetensors 0.7.0", + "thiserror 2.0.18", + "yoke 0.8.1", + "zip 7.2.0", +] + [[package]] name = "candle-nn" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66a27533c8edfc915a6459f9850641ef523a829fa1a181c670766c1f752d873a" dependencies = [ - "candle-core", + "candle-core 0.4.1", "half", "num-traits", "rayon", - "safetensors", + "safetensors 0.4.5", "serde", "thiserror 1.0.69", ] @@ -2283,14 +1501,14 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ - "cipher 0.4.4", + "cipher", ] [[package]] name = "cc" -version = "1.2.49" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -2304,8 +1522,8 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" dependencies = [ - "aead 0.5.2", - "cipher 0.4.4", + "aead", + "cipher", "ctr", "subtle", ] @@ -2325,12 +1543,6 @@ dependencies = [ "nom", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.4" @@ -2343,68 +1555,35 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed8738f14471a99f0e316c327e68fc82a3611cc2895fcb604b89eedaf8f39d95" -dependencies = [ - "cipher 0.2.5", - "zeroize", -] - [[package]] name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ - "cfg-if 1.0.4", - "cipher 0.4.4", + "cfg-if", + "cipher", "cpufeatures", ] -[[package]] -name = "chacha20poly1305" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1fc18e6d90c40164bf6c317476f2a98f04661e310e79830366b7e914c58a8e" -dependencies = [ - "aead 0.3.2", - "chacha20 0.6.0", - "cipher 0.2.5", - "poly1305 0.6.2", - "zeroize", -] - [[package]] name = "chacha20poly1305" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead 0.5.2", - "chacha20 0.9.1", - "cipher 0.4.4", - "poly1305 0.8.0", + "aead", + "chacha20", + "cipher", + "poly1305", "zeroize", ] -[[package]] -name = "chashmap" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" -dependencies = [ - "owning_ref", - "parking_lot 0.4.8", -] - [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", @@ -2447,22 +1626,13 @@ dependencies = [ "half", ] -[[package]] -name = "cipher" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" -dependencies = [ - "generic-array", -] - [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -2480,24 +1650,9 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap", - "unicode-width 0.1.14", - "vec_map", -] - -[[package]] -name = "clap" -version = "4.5.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -2505,33 +1660,33 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.1", + "strsim", ] [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "clear_on_drop" @@ -2542,15 +1697,6 @@ dependencies = [ "cc", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "cmake" version = "0.1.57" @@ -2604,9 +1750,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" dependencies = [ "compression-core", "flate2", @@ -2634,6 +1780,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const_format" version = "0.2.35" @@ -2656,21 +1808,18 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" -version = "0.4.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "core-foundation" @@ -2698,23 +1847,14 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" -dependencies = [ - "memchr", -] - [[package]] name = "corosensei" -version = "0.2.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1ea1c2a2f898d2a6ff149587b8a04f41ee708d248c723f01ac2f0f01edc0b3" +checksum = "2c54787b605c7df106ceccf798df23da4f2e09918defad66705d1cedf3bb914f" dependencies = [ - "autocfg 1.5.0", - "cfg-if 1.0.4", + "autocfg", + "cfg-if", "libc", "scopeguard", "windows-sys 0.59.0", @@ -2730,33 +1870,46 @@ dependencies = [ ] [[package]] -name = "cpuid-bool" -version = "0.2.0" +name = "cranelift-assembler-x64" +version = "0.128.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a04121a197fde2fe896f8e7cac9812fc41ed6ee9c63e1906090f9f497845f6" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.128.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" +checksum = "a09e699a94f477303820fb2167024f091543d6240783a2d3b01a3f21c42bc744" +dependencies = [ + "cranelift-srcgen", +] [[package]] name = "cranelift-bforest" -version = "0.110.2" +version = "0.128.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305d51c180ebdc46ef61bc60c54ae6512db3bc9a05842a1f1e762e45977019ab" +checksum = "aea7351476d0eb196e89150e7a6235ecd37c97848243faea7746c29676abeeac" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.110.3" +version = "0.128.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "690d8ae6c73748e5ce3d8fe59034dceadb8823e6c8994ba324141c5eae909b0e" +checksum = "18391da761cf362a06def7a7cf11474d79e55801dd34c2e9ba105b33dc0aef88" [[package]] name = "cranelift-codegen" -version = "0.110.2" +version = "0.128.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7ca95e831c18d1356da783765c344207cbdffea91e13e47fa9327dbb2e0719" +checksum = "fa9e80ceb5153bb9dd0d048e685ec4df6fa20ce92d4ffffcb5d691623e1d8693" dependencies = [ "bumpalo", + "cranelift-assembler-x64", "cranelift-bforest", "cranelift-bitset", "cranelift-codegen-meta", @@ -2764,65 +1917,76 @@ dependencies = [ "cranelift-control", "cranelift-entity", "cranelift-isle", - "gimli 0.28.1", - "hashbrown 0.14.5", + "gimli", + "hashbrown 0.15.5", "log", "regalloc2", - "rustc-hash 1.1.0", - "smallvec 1.15.1", + "rustc-hash 2.1.1", + "serde", + "smallvec", "target-lexicon", + "wasmtime-internal-math", ] [[package]] name = "cranelift-codegen-meta" -version = "0.110.3" +version = "0.128.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a2d2ab65e6cbf91f81781d8da65ec2005510f18300eff21a99526ed6785863" +checksum = "75817926ec812241889208d1b190cadb7fedded4592a4bb01b8524babb9e4849" dependencies = [ + "cranelift-assembler-x64-meta", "cranelift-codegen-shared", + "cranelift-srcgen", + "heck 0.5.0", ] [[package]] name = "cranelift-codegen-shared" -version = "0.110.3" +version = "0.128.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efcff860573cf3db9ae98fbd949240d78b319df686cc306872e7fab60e9c84d7" +checksum = "859158f87a59476476eda3884d883c32e08a143cf3d315095533b362a3250a63" [[package]] name = "cranelift-control" -version = "0.110.3" +version = "0.128.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d70e5b75c2d5541ef80a99966ccd97aaa54d2a6af19ea31759a28538e1685a" +checksum = "03b65a9aec442d715cbf54d14548b8f395476c09cef7abe03e104a378291ab88" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.110.2" +version = "0.128.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a48cb0a194c9ba82fec35a1e492055388d89b2e3c03dee9dcf2488892be8004d" +checksum = "3e57c6f29da407f6ee9956197d011aedf4fd39bd03781ab5b44b85d45a448a27" dependencies = [ "cranelift-bitset", ] [[package]] name = "cranelift-frontend" -version = "0.110.2" +version = "0.128.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8327afc6c1c05f4be62fefce5b439fa83521c65363a322e86ea32c85e7ceaf64" +checksum = "add3991ccfeb20022443bae60b8adc56081f27caab0213b0ff26288954e44fe5" dependencies = [ "cranelift-codegen", "log", - "smallvec 1.15.1", + "smallvec", "target-lexicon", ] [[package]] name = "cranelift-isle" -version = "0.110.2" +version = "0.128.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc02707039d43c0e132526f1d3ac319b45468331b823a1749625825010f644e4" + +[[package]] +name = "cranelift-srcgen" +version = "0.128.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b08621c00321efcfa3eee6a3179adc009e21ea8d24ca7adc3c326184bc3f48" +checksum = "903adeaf4938e60209a97b53a2e4326cd2d356aab9764a1934630204bae381c9" [[package]] name = "crc" @@ -2845,7 +2009,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] @@ -2857,7 +2021,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.53", + "clap", "criterion-plot", "is-terminal", "itertools 0.10.5", @@ -2890,15 +2054,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2933,31 +2088,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crossterm" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" -dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "lazy_static", - "libc", - "mio 0.7.14", - "parking_lot 0.11.2", - "signal-hook", - "winapi", -] - -[[package]] -name = "crossterm_winapi" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" -dependencies = [ - "winapi", -] - [[package]] name = "crunchy" version = "0.2.4" @@ -2988,42 +2118,12 @@ dependencies = [ ] [[package]] -name = "crypto_box" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42152c7961cd7af77bfe71c2cb0cf893b0a8939e9510d0c4db9b8d9027fea7e4" -dependencies = [ - "chacha20poly1305 0.7.1", - "rand_core 0.5.1", - "salsa20 0.7.2", - "x25519-dalek 1.1.1", - "xsalsa20poly1305", - "zeroize", -] - -[[package]] -name = "crypto_secretbox" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" -dependencies = [ - "aead 0.5.2", - "cipher 0.4.4", - "generic-array", - "poly1305 0.8.0", - "salsa20 0.10.2", - "subtle", - "zeroize", -] - -[[package]] -name = "ctor" -version = "0.1.26" +name = "crypto-common" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ - "quote", - "syn 1.0.109", + "hybrid-array", ] [[package]] @@ -3032,20 +2132,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.4.4", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", + "cipher", ] [[package]] @@ -3054,10 +2141,12 @@ version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "curve25519-dalek-derive", + "digest 0.10.7", "fiat-crypto", + "group", "rand_core 0.6.4", "rustc_version", "serde", @@ -3073,7 +2162,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -3085,7 +2174,6 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.6.4", - "serde", "subtle-ng", "zeroize", ] @@ -3096,7 +2184,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ae9bf77fbf2d39ef573205d554d87e86c12f1994e9ea335b0651b9b278bcf1" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] @@ -3112,16 +2200,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "darling" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" -dependencies = [ - "darling_core 0.10.2", - "darling_macro 0.10.2", -] - [[package]] name = "darling" version = "0.20.11" @@ -3143,17 +2221,13 @@ dependencies = [ ] [[package]] -name = "darling_core" -version = "0.10.2" +name = "darling" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.9.3", - "syn 1.0.109", + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -3166,8 +2240,8 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", - "syn 2.0.111", + "strsim", + "syn 2.0.117", ] [[package]] @@ -3180,18 +2254,20 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] -name = "darling_macro" -version = "0.10.2" +name = "darling_core" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ - "darling_core 0.10.2", + "ident_case", + "proc-macro2", "quote", - "syn 1.0.109", + "strsim", + "syn 2.0.117", ] [[package]] @@ -3202,7 +2278,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -3213,14 +2289,19 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] -name = "dary_heap" -version = "0.3.8" +name = "darling_macro" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06d2e3287df1c007e74221c49ca10a95d557349e54b3a75dc2fb14712c751f04" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] [[package]] name = "dashmap" @@ -3228,7 +2309,7 @@ version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "hashbrown 0.14.5", "lock_api", "once_cell", @@ -3241,7 +2322,7 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "crossbeam-utils", "hashbrown 0.14.5", "lock_api", @@ -3251,9 +2332,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "dbus" @@ -3308,7 +2389,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -3317,7 +2398,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3326,7 +2407,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -3347,101 +2428,36 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "derive_builder" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" -dependencies = [ - "darling 0.10.2", - "derive_builder_core", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_builder_core" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" -dependencies = [ - "darling 0.10.2", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.111", -] - [[package]] name = "derive_more" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ + "convert_case", "proc-macro2", "quote", - "syn 2.0.111", + "rustc_version", + "syn 2.0.117", "unicode-xid", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - [[package]] name = "digest" version = "0.9.0" @@ -3458,69 +2474,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", - "crypto-common", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] [[package]] -name = "directories" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" -dependencies = [ - "cfg-if 0.1.10", - "dirs-sys", -] - -[[package]] -name = "directories" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69600ff1703123957937708eb27f7a564e48885c537782722ed0ba3189ce1d7" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs" -version = "1.0.5" +name = "digest" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +checksum = "285743a676ccb6b3e116bc14cc69319b957867930ae9c4822f8e0f54509d7243" dependencies = [ - "libc", - "redox_users 0.3.5", - "winapi", + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", ] [[package]] name = "dirs" -version = "4.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", - "redox_users 0.4.6", - "winapi", -] - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", + "option-ext", + "redox_users", + "windows-sys 0.61.2", ] [[package]] @@ -3531,39 +2519,23 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "dns-parser" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" -dependencies = [ - "byteorder", - "quick-error", + "syn 2.0.117", ] -[[package]] -name = "downcast" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" - [[package]] name = "dtls" -version = "0.13.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f531dd7c181beaf3cebab3716afa4d0d41ab888be85232583f56bbaf07ca208a" +checksum = "2f016db07b91e9d79cc60a152c163d3f0ce2d4c0173cb3964de3526aab6e07fa" dependencies = [ "aes", "aes-gcm", "async-trait", - "bincode", + "bytecheck 0.8.2", "byteorder", "cbc", "ccm", - "chacha20poly1305 0.10.1", + "chacha20poly1305", "der-parser", "hmac", "log", @@ -3572,25 +2544,25 @@ dependencies = [ "portable-atomic", "rand 0.9.2", "rand_core 0.6.4", - "rcgen 0.13.2", - "ring 0.17.14", - "rustls 0.23.35", + "rcgen", + "ring", + "rkyv 0.8.15", + "rustls", "sec1", - "serde", "sha1", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "tokio", "webrtc-util", - "x25519-dalek 2.0.1", + "x25519-dalek", "x509-parser", ] [[package]] -name = "dunce" -version = "1.0.5" +name = "dyn-clone" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "dyn-stack" @@ -3602,6 +2574,22 @@ dependencies = [ "reborrow", ] +[[package]] +name = "dyn-stack" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c4713e43e2886ba72b8271aa66c93d722116acf7a75555cce11dcde84388fe8" +dependencies = [ + "bytemuck", + "dyn-stack-macros", +] + +[[package]] +name = "dyn-stack-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d926b4d407d372f141f93bb444696142c29d32962ccbd3531117cf3aa0bfa9" + [[package]] name = "ecdsa" version = "0.16.9" @@ -3616,6 +2604,31 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -3655,7 +2668,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "critical-section", "embedded-io-async", "futures-sink", @@ -3753,14 +2766,14 @@ dependencies = [ "home", "log", "regex", - "remove_dir_all 0.8.4", + "remove_dir_all", "serde", "serde_json", "shlex", "strum 0.24.1", "tempfile", "thiserror 1.0.69", - "which", + "which 4.4.2", ] [[package]] @@ -3769,7 +2782,7 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] @@ -3781,27 +2794,27 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "enum-iterator" -version = "0.7.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" dependencies = [ "enum-iterator-derive", ] [[package]] name = "enum-iterator-derive" -version = "0.7.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -3822,14 +2835,14 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "env_filter" -version = "0.1.4" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" dependencies = [ "log", "regex", @@ -3837,9 +2850,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" dependencies = [ "anstream", "anstyle", @@ -3863,20 +2876,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "err-derive" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "errno" version = "0.3.14" @@ -3946,7 +2945,7 @@ dependencies = [ "regex", "serde", "strum 0.24.1", - "which", + "which 4.4.2", ] [[package]] @@ -3956,9 +2955,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53ee06613867453660128b3d772c31a5d38b02c4fa66952d29022b3f35bdd920" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "bstr", - "cfg-if 1.0.4", + "cfg-if", "embassy-sync", "embuild", "esp-idf-svc", @@ -3996,34 +2995,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure 0.12.6", -] - -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fallible-iterator" version = "0.3.0" @@ -4036,20 +3007,11 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" -[[package]] -name = "fast_chemail" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4" -dependencies = [ - "ascii_utils", -] - [[package]] name = "fastbloom" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c1ddb9231d8554c2d6bdf4cfaabf0c59251658c68b6c95cd52dd0c513a912a" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" dependencies = [ "getrandom 0.3.4", "libm", @@ -4081,49 +3043,41 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", ] [[package]] -name = "float-cmp" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" -dependencies = [ - "num-traits", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" +name = "float8" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "719a903cc23e4a89e87962c2a80fdb45cdaad0983a89bd150bb57b4c8571a7d5" dependencies = [ + "half", "num-traits", + "rand 0.9.2", + "rand_distr 0.5.1", ] [[package]] @@ -4134,7 +3088,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -4143,6 +3097,12 @@ 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 = "foldhash" version = "0.2.0" @@ -4170,25 +3130,9 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ - "percent-encoding 2.3.2", -] - -[[package]] -name = "forwarded-header-value" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" -dependencies = [ - "nonempty", - "thiserror 1.0.69", + "percent-encoding", ] -[[package]] -name = "fragile" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" - [[package]] name = "fs2" version = "0.4.3" @@ -4206,7 +3150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14af6c9694ea25db25baa2a1788703b9e7c6648dcaeeebeb98f7561b5384c036" dependencies = [ "aligned", - "cfg-if 1.0.4", + "cfg-if", "cvt", "libc", "nix 0.29.0", @@ -4214,16 +3158,16 @@ dependencies = [ ] [[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -4236,9 +3180,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -4246,15 +3190,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -4263,9 +3207,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -4282,38 +3226,32 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-timer" -version = "3.0.3" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -4323,7 +3261,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -4342,17 +3279,37 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ab24cc62135b40090e31a76a9b2766a501979f3070fa27f689c27ec04377d32" dependencies = [ - "dyn-stack", - "gemm-c32", - "gemm-c64", - "gemm-common", - "gemm-f16", - "gemm-f32", - "gemm-f64", + "dyn-stack 0.10.0", + "gemm-c32 0.17.1", + "gemm-c64 0.17.1", + "gemm-common 0.17.1", + "gemm-f16 0.17.1", + "gemm-f32 0.17.1", + "gemm-f64 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa0673db364b12263d103b68337a68fbecc541d6f6b61ba72fe438654709eacb" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-c32 0.19.0", + "gemm-c64 0.19.0", + "gemm-common 0.19.0", + "gemm-f16 0.19.0", + "gemm-f32 0.19.0", + "gemm-f64 0.19.0", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 11.6.0", "seq-macro", ] @@ -4362,12 +3319,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9c030d0b983d1e34a546b86e08f600c11696fde16199f971cd46c12e67512c0" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086936dbdcb99e37aad81d320f98f670e53c1e55a98bee70573e83f95beb128c" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.19.0", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 11.6.0", "seq-macro", ] @@ -4377,12 +3349,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb5f2e79fefb9693d18e1066a557b4546cd334b226beadc68b11a8f9431852a" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-c64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c8aeeeec425959bda4d9827664029ba1501a90a0d1e6228e48bef741db3a3f" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.19.0", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 11.6.0", "seq-macro", ] @@ -4393,17 +3380,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2e7ea062c987abcd8db95db917b4ffb4ecdfd0668471d8dc54734fdff2354e8" dependencies = [ "bytemuck", - "dyn-stack", + "dyn-stack 0.10.0", + "half", + "num-complex", + "num-traits", + "once_cell", + "paste", + "pulp 0.18.22", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", + "sysctl 0.5.5", +] + +[[package]] +name = "gemm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88027625910cc9b1085aaaa1c4bc46bb3a36aad323452b33c25b5e4e7c8e2a3e" +dependencies = [ + "bytemuck", + "dyn-stack 0.13.2", "half", + "libm", "num-complex", "num-traits", "once_cell", "paste", - "pulp", - "raw-cpuid", + "pulp 0.22.2", + "raw-cpuid 11.6.0", "rayon", "seq-macro", - "sysctl", + "sysctl 0.6.0", ] [[package]] @@ -4412,14 +3420,32 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ca4c06b9b11952071d317604acb332e924e817bd891bec8dfb494168c7cedd4" dependencies = [ - "dyn-stack", - "gemm-common", - "gemm-f32", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "gemm-f32 0.17.1", + "half", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "rayon", + "seq-macro", +] + +[[package]] +name = "gemm-f16" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3df7a55202e6cd6739d82ae3399c8e0c7e1402859b30e4cb780e61525d9486e" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.19.0", + "gemm-f32 0.19.0", "half", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 11.6.0", "rayon", "seq-macro", ] @@ -4430,12 +3456,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9a69f51aaefbd9cf12d18faf273d3e982d9d711f60775645ed5c8047b4ae113" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f32" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e0b8c9da1fbec6e3e3ab2ce6bc259ef18eb5f6f0d3e4edf54b75f9fd41a81c" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.19.0", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 11.6.0", "seq-macro", ] @@ -4445,12 +3486,27 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa397a48544fadf0b81ec8741e5c0fba0043008113f71f2034def1935645d2b0" dependencies = [ - "dyn-stack", - "gemm-common", + "dyn-stack 0.10.0", + "gemm-common 0.17.1", + "num-complex", + "num-traits", + "paste", + "raw-cpuid 10.7.0", + "seq-macro", +] + +[[package]] +name = "gemm-f64" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "056131e8f2a521bfab322f804ccd652520c79700d81209e9d9275bbdecaadc6a" +dependencies = [ + "dyn-stack 0.13.2", + "gemm-common 0.19.0", "num-complex", "num-traits", "paste", - "raw-cpuid", + "raw-cpuid 11.6.0", "seq-macro", ] @@ -4467,40 +3523,42 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", + "js-sys", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "r-efi 5.3.0", + "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.4" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ - "cfg-if 1.0.4", - "js-sys", + "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", - "wasm-bindgen", + "wasip3", ] [[package]] @@ -4513,39 +3571,17 @@ dependencies = [ "polyval", ] -[[package]] -name = "ghost_actor" -version = "0.3.0-alpha.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a34addaffa7d2c80637807444f171c646cad7549fcdac8019544034678f76d5" -dependencies = [ - "futures", - "mockall", - "must_future", - "paste", - "thiserror 1.0.69", - "tokio", - "tracing", - "tracing-futures", -] - [[package]] name = "gimli" -version = "0.28.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ - "fallible-iterator 0.3.0", - "indexmap 2.12.1", + "fallible-iterator", + "indexmap", "stable_deref_trait", ] -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" - [[package]] name = "glob" version = "0.3.3" @@ -4601,36 +3637,17 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap 2.12.1", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", - "indexmap 2.12.1", + "http", + "indexmap", "slab", "tokio", "tokio-util", @@ -4644,7 +3661,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "bytemuck", - "cfg-if 1.0.4", + "cfg-if", "crunchy", "num-traits", "rand 0.9.2", @@ -4652,20 +3669,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "handlebars" -version = "5.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" -dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "hash32" version = "0.3.1" @@ -4675,28 +3678,13 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.12", + "ahash 0.7.8", ] [[package]] @@ -4713,6 +3701,9 @@ name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] [[package]] name = "hashbrown" @@ -4722,40 +3713,18 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] name = "hashlink" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" -dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.7", - "bytes", - "headers-core", - "http 0.2.12", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "http 0.2.12", + "hashbrown 0.14.5", ] [[package]] @@ -4768,15 +3737,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.1" @@ -4789,15 +3749,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - [[package]] name = "hermit-abi" version = "0.5.2" @@ -4812,15 +3763,15 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hidapi" -version = "2.6.4" +version = "2.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565dd4c730b8f8b2c0fb36df6be12e5470ae10895ddcc4e9dcfbfb495de202b0" +checksum = "d1b71e1f4791fb9e93b9d7ee03d70b501ab48f6151432fbcadeabc30fe15396e" dependencies = [ "cc", - "cfg-if 1.0.4", + "cfg-if", "libc", "pkg-config", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -4850,54 +3801,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "hope_agents" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fecd5b7c6f06a594c2530d6ce8b3a6f88cce272b163cc8eec5edb4cdbac21c2" -dependencies = [ - "chrono", - "log", - "rand 0.8.5", - "serde", - "serde_json", -] - -[[package]] -name = "hope_agents" -version = "0.2.0" -dependencies = [ - "chrono", - "criterion", - "log", - "rand 0.9.2", - "serde", - "serde_json", - "titans_memory 0.2.0", -] - -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -4908,17 +3811,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -4926,7 +3818,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -4937,8 +3829,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -4961,43 +3853,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "human-panic" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a07a0957cd4a3cad4a1e4ca7cd5ea07fcacef6ebe2e5d0c7935bfc95120d8" -dependencies = [ - "anstream", - "anstyle", - "backtrace", - "os_info", - "serde", - "serde_derive", - "toml 0.9.10+spec-1.1.0", - "uuid", -] - -[[package]] -name = "hyper" -version = "0.14.32" +name = "hybrid-array" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2 0.3.27", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2 0.5.10", - "tokio", - "tower-service", - "tracing", - "want", + "typenum", ] [[package]] @@ -5010,15 +3871,15 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.12", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "pin-utils", - "smallvec 1.15.1", + "smallvec", "tokio", "want", ] @@ -5029,14 +3890,15 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", - "hyper 1.8.1", + "http", + "hyper", "hyper-util", - "rustls 0.23.35", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.4", + "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -5047,7 +3909,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-util", "native-tls", "tokio", @@ -5057,23 +3919,22 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", - "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "hyper 1.8.1", + "http", + "http-body", + "hyper", "ipnet", "libc", - "percent-encoding 2.3.2", + "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -5083,9 +3944,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -5141,7 +4002,7 @@ dependencies = [ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.1", + "smallvec", "zerovec", ] @@ -5187,21 +4048,16 @@ dependencies = [ ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] -name = "idna" -version = "0.1.5" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" @@ -5210,7 +4066,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", - "smallvec 1.15.1", + "smallvec", "utf8_iter", ] @@ -5224,17 +4080,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if-addrs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2273e421f7c4f0fc99e1934fe4776f59d8df2972f4199d703fc0da9f2a9f73de" -dependencies = [ - "if-addrs-sys", - "libc", - "winapi", -] - [[package]] name = "if-addrs" version = "0.13.4" @@ -5246,13 +4091,13 @@ dependencies = [ ] [[package]] -name = "if-addrs-sys" -version = "0.3.2" +name = "if-addrs" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" dependencies = [ - "cc", "libc", + "windows-sys 0.61.2", ] [[package]] @@ -5273,19 +4118,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg 1.5.0", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -5294,25 +4129,17 @@ dependencies = [ ] [[package]] -name = "inferno" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d35223c50fdd26419a4ccea2c73be68bd2b29a3d7d6123ffe101c17f4c20a52a" +name = "ineru" +version = "0.6.3" dependencies = [ - "ahash 0.8.12", - "clap 4.5.53", - "crossbeam-channel", - "crossbeam-utils", - "dashmap 6.1.0", - "env_logger", - "indexmap 2.12.1", - "itoa", + "bincode", + "blake3", + "chrono", + "criterion", "log", - "num-format", - "once_cell", - "quick-xml", - "rgb", - "str_stack", + "rusqlite", + "serde", + "serde_json", ] [[package]] @@ -5321,7 +4148,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding 0.3.3", + "block-padding", "generic-array", ] @@ -5331,14 +4158,14 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] name = "interceptor" -version = "0.15.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea51375727680dc15f06e8ad90fa31df75d79dd030100e8ad60eef1c27fe2c98" +checksum = "7f73f4fdb971cab2d599cbdc2ccf0c6ea8fb27347b871ed14c65ce2353dbe75b" dependencies = [ "async-trait", "bytes", @@ -5369,15 +4196,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -5389,7 +4216,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi 0.5.2", + "hermit-abi", "libc", "windows-sys 0.61.2", ] @@ -5400,15 +4227,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.10.5" @@ -5447,15 +4265,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jiff" -version = "0.2.16" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -5466,13 +4284,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.16" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -5496,7 +4314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if 1.0.4", + "cfg-if", "combine", "jni-sys", "log", @@ -5515,305 +4333,89 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" name = "jni-utils" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "259e9f2c3ead61de911f147000660511f07ab00adeed1d84f5ac4d0386e7a6c4" -dependencies = [ - "dashmap 5.5.3", - "futures", - "jni 0.19.0", - "log", - "once_cell", - "static_assertions", - "uuid", -] - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "9.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" -dependencies = [ - "base64 0.22.1", - "js-sys", - "pem 3.0.6", - "ring 0.17.14", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "kitsune_bootstrap" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "criterion", - "kitsune_p2p", - "kitsune_p2p_types", - "once_cell", - "parking_lot 0.12.5", - "rand 0.9.2", - "reqwest", - "rmp-serde 0.15.5", - "serde", - "serde_bytes", - "serde_json", - "tokio", - "warp", -] - -[[package]] -name = "kitsune_p2p" -version = "0.0.1" -dependencies = [ - "ai_fixt", - "aingle-observability", - "arrayref", - "base64 0.21.7", - "bloomfilter", - "derive_more 0.99.20", - "futures", - "ghost_actor", - "kitsune_p2p_mdns", - "kitsune_p2p_proxy", - "kitsune_p2p_transport_quic", - "kitsune_p2p_types", - "lair_keystore_api", - "matches", - "once_cell", - "rand 0.9.2", - "reqwest", - "serde", - "serde_bytes", - "shrinkwraprs", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tracing", - "tracing-subscriber", - "url2", -] - -[[package]] -name = "kitsune_p2p_direct" -version = "0.0.1" -dependencies = [ - "arrayref", - "base64 0.21.7", - "derive_more 0.99.20", - "futures", - "hyper 0.14.32", - "kitsune_p2p", - "kitsune_p2p_direct_api", - "kitsune_p2p_proxy", - "kitsune_p2p_transport_quic", - "kitsune_p2p_types", - "rand 0.9.2", - "serde", - "serde_json", - "sodoken", - "structopt", - "tokio", - "tokio-tungstenite 0.20.1", - "tungstenite 0.20.1", -] - -[[package]] -name = "kitsune_p2p_direct_api" -version = "0.0.1" -dependencies = [ - "arrayref", - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "kitsune_p2p_direct_test" -version = "0.0.1" -dependencies = [ - "kitsune_p2p_direct", - "kitsune_p2p_proxy", - "kitsune_p2p_transport_quic", - "tokio", -] - -[[package]] -name = "kitsune_p2p_mdns" -version = "0.0.1" -dependencies = [ - "async-stream 0.3.6", - "base64 0.21.7", - "futures-core", - "futures-util", - "libmdns", - "mdns", - "thiserror 2.0.17", - "tokio", - "tokio-stream", +checksum = "259e9f2c3ead61de911f147000660511f07ab00adeed1d84f5ac4d0386e7a6c4" +dependencies = [ + "dashmap 5.5.3", + "futures", + "jni 0.19.0", + "log", + "once_cell", + "static_assertions", + "uuid", ] [[package]] -name = "kitsune_p2p_proxy" -version = "0.0.1" +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "aingle-observability", - "base64 0.21.7", - "blake2b_simd", - "criterion", - "crossterm", - "derive_more 0.99.20", - "futures", - "kitsune_p2p_transport_quic", - "kitsune_p2p_types", - "lair_keystore_api", - "nanoid 0.4.0", - "parking_lot 0.12.5", - "rmp-serde 0.15.5", - "rustls 0.23.35", - "rustls-pki-types", - "serde", - "serde_bytes", - "structopt", - "tokio", - "tracing", - "tracing-subscriber", + "getrandom 0.3.4", + "libc", ] [[package]] -name = "kitsune_p2p_transport_quic" -version = "0.0.1" +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ - "blake2b_simd", - "futures", - "if-addrs 0.6.7", - "kitsune_p2p_types", - "lair_keystore_api", - "nanoid 0.4.0", "once_cell", - "quinn", - "rcgen 0.13.2", - "rustls 0.23.35", - "rustls-pki-types", - "serde", - "tokio", - "tokio-util", + "wasm-bindgen", ] [[package]] -name = "kitsune_p2p_types" -version = "0.0.1" +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ - "aingle-observability", - "base64 0.21.7", - "criterion", - "derive_more 0.99.20", - "futures", - "ghost_actor", - "lair_keystore_api", - "lru", - "mockall", - "nanoid 0.4.0", - "once_cell", - "parking_lot 0.12.5", - "paste", - "rmp-serde 0.15.5", - "rustls 0.23.35", - "rustls-pki-types", + "base64", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256", + "p384", + "pem", + "rand 0.8.5", + "rsa", "serde", - "serde_bytes", "serde_json", - "shrinkwraprs", - "sysinfo", - "thiserror 2.0.17", - "tokio", - "tokio-stream", - "tracing-subscriber", - "url 2.5.7", - "url2", + "sha2 0.10.9", + "signature", + "simple_asn1", ] [[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +name = "kaneru" +version = "0.6.3" dependencies = [ + "chrono", + "criterion", + "ineru", "log", + "rand 0.9.2", + "serde", + "serde_json", ] [[package]] -name = "lair_keystore_api" -version = "0.0.11" +name = "keccak" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3f2fb64691f81c33a26276c8f11ccc6ab10ade5afca45df8629331b9f6a7a0" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "blake2b_simd", - "block-padding 0.2.1", - "byteorder", - "crypto_box", - "derive_more 0.99.20", - "directories 3.0.2", - "futures", - "ghost_actor", - "nanoid 0.3.0", - "num_cpus", - "once_cell", - "one_err", - "parking_lot 0.11.2", - "rand 0.7.3", - "rayon", - "rcgen 0.8.13", - "ring 0.16.20", - "sodoken", - "subtle", - "thiserror 1.0.69", - "tokio", - "toml 0.5.11", - "winapi", + "cpufeatures", ] [[package]] -name = "lair_keystore_client" -version = "0.0.11" +name = "kv-log-macro" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06b1591a47f5db62e30b11a5b83fd9d8194a6c3ef3ab6a160d9333092319b2a2" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "futures", - "ghost_actor", - "lair_keystore_api", - "sodoken", - "tempfile", - "tokio", - "tracing", + "log", ] [[package]] @@ -5821,6 +4423,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "lazycell" @@ -5868,7 +4473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e34341e2708fbf805a9ada44ef6182170c6464c4fc068ab801abb7562fd5e8" dependencies = [ "byteorder", - "cfg-if 1.0.4", + "cfg-if", "hex", "hidapi", "ledger-transport", @@ -5879,9 +4484,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libdbus-sys" @@ -5892,113 +4497,54 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "libflate" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3248b8d211bd23a104a42d81b4fa8bb8ac4a3b75e7a43d85d2c9ccb6179cd74" -dependencies = [ - "adler32", - "core2", - "crc32fast", - "dary_heap", - "libflate_lz77", -] - -[[package]] -name = "libflate_lz77" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a599cb10a9cd92b1300debcef28da8f70b935ec937f44fcd1b70a7c986a11c5c" -dependencies = [ - "core2", - "hashbrown 0.16.1", - "rle-decode-fast", -] - [[package]] name = "libloading" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "windows-link 0.2.1", ] [[package]] name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - -[[package]] -name = "libmdns" -version = "0.6.0" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b276920bfc6c9285e16ffd30ed410487f0185f383483f45a3446afc0554fded" -dependencies = [ - "byteorder", - "futures-util", - "hostname", - "if-addrs 0.6.7", - "log", - "multimap", - "quick-error", - "rand 0.8.5", - "socket2 0.3.19", - "tokio", -] +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", - "redox_syscall 0.6.0", + "plain", + "redox_syscall 0.7.3", ] [[package]] name = "librocksdb-sys" -version = "0.16.0+8.10.0" +version = "0.17.3+10.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" +checksum = "cef2a00ee60fe526157c9023edab23943fae1ce2ab6f4abb2a807c1746835de9" dependencies = [ - "bindgen 0.69.5", + "bindgen 0.72.1", "bzip2-sys", "cc", - "glob", "libc", "libz-sys", "lz4-sys", "zstd-sys", ] -[[package]] -name = "libsodium-sys-stable" -version = "1.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186bf786351b393a91025e9fc2f1058b8b8e1c7ecfde142ed829c209ab95daff" -dependencies = [ - "cc", - "libc", - "libflate", - "minisign-verify", - "pkg-config", - "tar", - "ureq 3.1.4", - "vcpkg", - "zip 6.0.0", -] - [[package]] name = "libsqlite3-sys" -version = "0.22.2" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290b64917f8b0cb885d9de0f9959fe1f775d7fa12f1da2db9001c1c8ab60f89d" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -6011,47 +4557,17 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6639b70a7ce854b79c70d7e83f16b5dc0137cc914f3d7d03803b513ecc67ac" -[[package]] -name = "libyml" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" -dependencies = [ - "anyhow", - "version_check", -] - -[[package]] -name = "libz-rs-sys" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" -dependencies = [ - "zlib-rs", -] - [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "4735e9cbde5aac84a5ce588f6b23a90b9b0b528f6c5a8db8a4aff300463a0839" dependencies = [ "cc", "pkg-config", "vcpkg", ] -[[package]] -name = "linefeed" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28715d08e35c6c074f9ae6b2e6a2420bac75d050c66ecd669d7d5b98e2caa036" -dependencies = [ - "dirs 1.0.5", - "mortal", - "winapi", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" @@ -6060,9 +4576,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -6088,15 +4604,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "lru" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" -dependencies = [ - "hashbrown 0.11.2", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -6128,13 +4635,19 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "macho-unwind-info" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb4bdc8b0ce69932332cf76d24af69c3a155242af95c226b2ab6c2e371ed1149" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", "zerocopy", "zerocopy-derive", ] @@ -6145,12 +4658,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.2.0" @@ -6160,18 +4667,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "matchit" version = "0.8.4" @@ -6184,60 +4679,40 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ - "autocfg 1.5.0", + "autocfg", "rawpointer", ] -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "digest 0.10.7", ] -[[package]] -name = "mdns" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c769962ac75a6ea437f0922b27834bcccd4c013d591383a16ae5731e3ef0f3f3" -dependencies = [ - "async-std", - "async-stream 0.2.1", - "dns-parser", - "err-derive", - "futures-core", - "futures-util", - "log", - "net2", -] - [[package]] name = "mdns-sd" -version = "0.11.5" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe7c11a1eb3cfbfcf702d1601c1f5f4c102cdc8665b8a557783ef634741676e" +checksum = "e8a3efd5e35bde8fe2bf6f698a0158c198eade41805bb89788ee59853baeb877" dependencies = [ + "fastrand", "flume", - "if-addrs 0.13.4", + "if-addrs 0.15.0", "log", - "polling 2.8.0", - "socket2 0.5.10", + "mio", + "socket-pktinfo", + "socket2 0.6.3", ] [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" @@ -6250,9 +4725,9 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", "stable_deref_trait", @@ -6264,7 +4739,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ - "autocfg 1.5.0", + "autocfg", ] [[package]] @@ -6273,7 +4748,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ - "autocfg 1.5.0", + "autocfg", ] [[package]] @@ -6299,157 +4774,44 @@ name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "minisign-verify" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log", - "miow", - "ntapi 0.3.7", - "winapi", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "mockall" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" -dependencies = [ - "cfg-if 1.0.4", - "downcast", - "fragile", - "lazy_static", - "mockall_derive", - "predicates 2.1.5", - "predicates-tree", -] - -[[package]] -name = "mockall_derive" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" -dependencies = [ - "cfg-if 1.0.4", - "proc-macro2", - "quote", - "syn 1.0.109", +dependencies = [ + "mime", + "unicase", ] [[package]] -name = "more-asserts" -version = "0.2.2" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "mortal" -version = "0.2.4" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c624fa1b7aab6bd2aff6e9b18565cc0363b6d45cbcd7465c9ed5e3740ebf097" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "bitflags 2.10.0", - "libc", - "nix 0.26.4", - "smallstr", - "terminfo", - "unicode-normalization", - "unicode-width 0.1.14", - "winapi", + "adler2", + "simd-adler32", ] [[package]] -name = "mr_bundle" -version = "0.0.1" +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ - "aingle_util", - "anyhow", - "arbitrary", - "bytes", - "derive_more 0.99.20", - "either", - "flate2", - "futures", - "maplit", - "matches", - "reqwest", - "rmp-serde 0.15.5", - "serde", - "serde_bytes", - "serde_derive", - "serde_yml", - "tempdir", - "thiserror 2.0.17", - "tokio", + "libc", + "log", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "multer" -version = "2.1.0" +name = "more-asserts" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 0.2.12", - "httparse", - "log", - "memchr", - "mime", - "spin 0.9.8", - "version_check", -] +checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" [[package]] name = "multer" @@ -6460,23 +4822,14 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http", "httparse", "memchr", "mime", - "spin 0.9.8", + "spin", "version_check", ] -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -dependencies = [ - "serde", -] - [[package]] name = "munge" version = "0.4.7" @@ -6494,42 +4847,14 @@ checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "must_future" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a160ffed3c2f98d2906c67a9b6e4e1f09cca7e17e3f780286a349061459eeebe" -dependencies = [ - "futures", - "pin-utils", -] - -[[package]] -name = "nanoid" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6226bc4e142124cb44e309a37a04cd9bb10e740d8642855441d3b14808f635e" -dependencies = [ - "rand 0.6.5", -] - -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", + "syn 2.0.117", ] [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -6537,7 +4862,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -6559,9 +4884,9 @@ checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" [[package]] name = "ndarray" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7c9125e8f6f10c9da3aad044cc918cf8784fa34de857b1aa68038eb05a50a9" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" dependencies = [ "matrixmultiply", "num-complex", @@ -6572,17 +4897,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "net2" -version = "0.2.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi", -] - [[package]] name = "nix" version = "0.26.4" @@ -6590,7 +4904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", - "cfg-if 1.0.4", + "cfg-if", "libc", "memoffset 0.7.1", "pin-utils", @@ -6602,20 +4916,8 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", - "cfg-if 1.0.4", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.10.0", - "cfg-if 1.0.4", + "bitflags 2.11.0", + "cfg-if", "cfg_aliases", "libc", ] @@ -6636,18 +4938,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "nonempty" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" - -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - [[package]] name = "normpath" version = "1.5.0" @@ -6657,24 +4947,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -6694,6 +4966,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.6" @@ -6706,26 +4994,27 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] -name = "num-format" -version = "0.4.4" +name = "num-integer" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "arrayvec 0.7.6", - "itoa", + "num-traits", ] [[package]] -name = "num-integer" -version = "0.1.46" +name = "num-iter" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ + "autocfg", + "num-integer", "num-traits", ] @@ -6735,7 +5024,7 @@ version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "autocfg 1.5.0", + "autocfg", "libm", ] @@ -6745,7 +5034,7 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.5.2", + "hermit-abi", "libc", ] @@ -6768,7 +5057,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -6787,101 +5076,15 @@ dependencies = [ "objc2-encode", ] -[[package]] -name = "objc2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - [[package]] name = "objc2-core-bluetooth" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a644b62ffb826a5277f536cf0f701493de420b13d40e700c452c36567771111" dependencies = [ - "bitflags 2.10.0", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.10.0", - "dispatch2", - "objc2 0.6.3", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.10.0", - "dispatch2", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2 0.6.3", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-core-graphics", + "bitflags 2.11.0", + "objc2", + "objc2-foundation", ] [[package]] @@ -6896,112 +5099,35 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.10.0", - "block2 0.5.1", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.10.0", - "block2 0.6.2", - "libc", - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" -dependencies = [ + "bitflags 2.11.0", + "block2", "libc", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.10.0", - "objc2 0.6.3", - "objc2-core-foundation", - "objc2-foundation 0.3.2", + "objc2", ] [[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.10.0", - "block2 0.6.2", - "objc2 0.6.3", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation 0.3.2", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" +name = "object" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ - "objc2 0.6.3", - "objc2-foundation 0.3.2", + "memchr", ] [[package]] name = "object" -version = "0.32.2" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "271638cd5fa9cca89c4c304675ca658efc4e64a66c716b7cfe1afb4b9611dbbc" dependencies = [ "crc32fast", "flate2", - "hashbrown 0.14.5", - "indexmap 2.12.1", + "hashbrown 0.16.1", + "indexmap", "memchr", "ruzstd", ] -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "oid-registry" version = "0.7.1" @@ -7015,37 +5141,86 @@ dependencies = [ name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] -name = "once_cell_polyfill" -version = "1.70.2" +name = "openraft" +version = "0.10.0-alpha.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +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 = "one_err" -version = "0.0.5" +name = "openraft-macros" +version = "0.10.0-alpha.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42901c1956c1ef1de5bce4428695aa03505a8e27bd2262173395c804834f44ec" +checksum = "e22b0bd215948ed47997a1d0447ea592e49220096360a833b118f329a08aa286" dependencies = [ - "indexmap 1.9.3", - "libc", - "serde", - "serde_json", + "chrono", + "proc-macro2", + "quote", + "semver", + "syn 2.0.117", ] [[package]] -name = "oorandom" -version = "11.1.5" +name = "openraft-rt" +version = "0.10.0-alpha.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +checksum = "55b651e6e2f25d022e34549e605eb8875c78ebc26862b16b06143a551e53ec00" +dependencies = [ + "futures-channel", + "futures-util", + "openraft-macros", + "rand 0.9.2", +] [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "openraft-rt-tokio" +version = "0.10.0-alpha.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "478d5625fdeb13293e68549ba1d42b7a25085f3be04204412147637ad22e2827" +dependencies = [ + "futures-util", + "openraft-rt", + "rand 0.9.2", + "tokio", +] [[package]] name = "openssl" @@ -7053,8 +5228,8 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", - "cfg-if 1.0.4", + "bitflags 2.11.0", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -7070,14 +5245,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -7092,38 +5267,10 @@ dependencies = [ ] [[package]] -name = "os_info" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" -dependencies = [ - "android_system_properties", - "log", - "nix 0.30.1", - "objc2 0.6.3", - "objc2-foundation 0.3.2", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", -] - -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - -[[package]] -name = "owning_ref" -version = "0.3.3" +name = "option-ext" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" -dependencies = [ - "stable_deref_trait", -] +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "oxilangtag" @@ -7142,14 +5289,14 @@ checksum = "54b4ed3a7192fa19f5f48f99871f2755047fabefd7f222f12a1df1773796a102" [[package]] name = "oxrdf" -version = "0.2.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a04761319ef84de1f59782f189d072cbfc3a9a40c4e8bded8667202fbd35b02a" +checksum = "0afd5c28e4a399c57ee2bc3accd40c7b671fdc7b6537499f14e95b265af7d7e0" dependencies = [ "oxilangtag", "oxiri", - "rand 0.8.5", - "thiserror 2.0.17", + "rand 0.9.2", + "thiserror 2.0.18", ] [[package]] @@ -7161,7 +5308,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -7173,17 +5320,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", -] - -[[package]] -name = "page_size" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" -dependencies = [ - "libc", - "winapi", + "sha2 0.10.9", ] [[package]] @@ -7192,16 +5329,6 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" -[[package]] -name = "parking_lot" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" -dependencies = [ - "owning_ref", - "parking_lot_core 0.2.14", -] - [[package]] name = "parking_lot" version = "0.11.2" @@ -7223,29 +5350,17 @@ dependencies = [ "parking_lot_core 0.9.12", ] -[[package]] -name = "parking_lot_core" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" -dependencies = [ - "libc", - "rand 0.4.6", - "smallvec 0.6.14", - "winapi", -] - [[package]] name = "parking_lot_core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "instant", "libc", "redox_syscall 0.2.16", - "smallvec 1.15.1", + "smallvec", "winapi", ] @@ -7255,10 +5370,10 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "libc", "redox_syscall 0.5.18", - "smallvec 1.15.1", + "smallvec", "windows-link 0.2.1", ] @@ -7279,6 +5394,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" @@ -7306,24 +5427,13 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64 0.13.1", - "once_cell", - "regex", -] - [[package]] name = "pem" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] @@ -7336,12 +5446,6 @@ dependencies = [ "base64ct", ] -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.3.2" @@ -7350,110 +5454,19 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", ] -[[package]] -name = "pest_derive" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "pest_meta" -version = "2.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" -dependencies = [ - "pest", - "sha2", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared", - "rand 0.8.5", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -7463,15 +5476,26 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -7488,6 +5512,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "plotters" version = "0.3.7" @@ -7516,46 +5546,20 @@ dependencies = [ "plotters-backend", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg 1.5.0", - "bitflags 1.3.2", - "cfg-if 1.0.4", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.61.2", ] -[[package]] -name = "poly1305" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b7456bc1ad2d4cf82b3a016be4c2ac48daf11bf990c1603ebd447fe6f30fca8" -dependencies = [ - "cpuid-bool", - "universal-hash 0.4.0", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -7564,7 +5568,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug", - "universal-hash 0.5.1", + "universal-hash", ] [[package]] @@ -7573,36 +5577,27 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "opaque-debug", - "universal-hash 0.5.1", + "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] -[[package]] -name = "portpicker" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be97d76faf1bfab666e1375477b23fde79eccf0276e9b63b92a39d676a889ba9" -dependencies = [ - "rand 0.8.5", -] - [[package]] name = "potential_utf" version = "0.1.4" @@ -7627,122 +5622,32 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "predicates" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" -dependencies = [ - "difference", - "float-cmp 0.8.0", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" -dependencies = [ - "difflib", - "float-cmp 0.9.0", - "itertools 0.10.5", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" -dependencies = [ - "anstyle", - "difflib", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" - -[[package]] -name = "predicates-tree" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "pretty_assertions" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" -dependencies = [ - "ansi_term", - "ctor", - "diff", - "output_vt100", -] - [[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.111", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", + "proc-macro2", + "syn 2.0.117", ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "primeorder" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", + "elliptic-curve", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-crate" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "toml_edit", ] [[package]] @@ -7764,14 +5669,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -7813,7 +5718,7 @@ checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -7829,19 +5734,27 @@ dependencies = [ ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "pulp" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "2e205bb30d5b916c55e584c22201771bcf2bad9aabd5d4127f38387140c38632" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid 11.6.0", + "reborrow", + "version_check", +] [[package]] -name = "quick-xml" -version = "0.38.4" +name = "pulp-wasm-simd-flag" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] +checksum = "40e24eee682d89fb193496edf918a7f407d30175b2e785fe057e4392dfd182e0" [[package]] name = "quinn" @@ -7855,9 +5768,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.1.1", - "rustls 0.23.35", - "socket2 0.6.1", - "thiserror 2.0.17", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -7865,22 +5778,22 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", - "ring 0.17.14", + "ring", "rustc-hash 2.1.1", - "rustls 0.23.35", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -7895,16 +5808,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -7916,25 +5829,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "r2d2" -version = "0.8.10" +name = "r-efi" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" -dependencies = [ - "log", - "parking_lot 0.12.5", - "scheduled-thread-pool", -] +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] -name = "r2d2_sqlite" -version = "0.18.0" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d24607049214c5e42d3df53ac1d8a23c34cc6a5eefe3122acb2c72174719959" -dependencies = [ - "r2d2", - "rusqlite", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rancor" @@ -7945,51 +5849,6 @@ dependencies = [ "ptr_meta 0.3.1", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.8", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc 0.2.0", -] - [[package]] name = "rand" version = "0.8.5" @@ -8008,27 +5867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.3.1", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "rand_core 0.9.5", ] [[package]] @@ -8048,31 +5887,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", + "rand_core 0.9.5", ] [[package]] @@ -8081,14 +5896,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -8114,83 +5929,27 @@ dependencies = [ ] [[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - -[[package]] -name = "rand_pcg" -version = "0.1.2" +name = "rangemap" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" -dependencies = [ - "autocfg 0.1.8", - "rand_core 0.4.2", -] +checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" [[package]] -name = "rand_xorshift" -version = "0.1.1" +name = "raw-cpuid" +version = "10.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" dependencies = [ - "rand_core 0.3.1", + "bitflags 1.3.2", ] [[package]] name = "raw-cpuid" -version = "10.7.0" +version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.0", ] [[package]] @@ -8219,39 +5978,18 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rcgen" -version = "0.8.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2351cbef4bf91837f5ff7face6091cb277ba960d1638d2c5ae2327859912fbba" -dependencies = [ - "chrono", - "pem 0.8.3", - "ring 0.16.20", - "yasna 0.4.0", -] - [[package]] name = "rcgen" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" dependencies = [ - "pem 3.0.6", - "ring 0.17.14", + "pem", + "ring", "rustls-pki-types", "time", "x509-parser", - "yasna 0.5.2", -] - -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", + "yasna", ] [[package]] @@ -8260,12 +5998,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - [[package]] name = "redox_syscall" version = "0.2.16" @@ -8281,58 +6013,68 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_users" -version = "0.3.5" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.1.16", - "redox_syscall 0.1.57", - "rust-argon2", + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", ] [[package]] -name = "redox_users" -version = "0.4.6" +name = "ref-cast" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 1.0.69", + "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.9.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" dependencies = [ - "hashbrown 0.13.2", + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", "log", - "rustc-hash 1.1.0", - "slice-group-by", - "smallvec 1.15.1", + "rustc-hash 2.1.1", + "smallvec", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -8342,9 +6084,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -8353,9 +6095,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "region" @@ -8365,26 +6107,17 @@ checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" dependencies = [ "bitflags 1.3.2", "libc", - "mach2", + "mach2 0.4.3", "windows-sys 0.52.0", ] -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - [[package]] name = "remove_dir_all" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a694f9e0eb3104451127f6cc1e5de55f59d3b1fc8c5ddfaeb6f1e716479ceb4a" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cvt", "fs_at", "libc", @@ -8392,6 +6125,15 @@ 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" @@ -8403,19 +6145,19 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.26" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", - "h2 0.4.12", - "http 1.4.0", - "http-body 1.0.1", + "h2", + "http", + "http-body", "http-body-util", - "hyper 1.8.1", + "hyper", "hyper-rustls", "hyper-tls", "hyper-util", @@ -8423,8 +6165,10 @@ dependencies = [ "log", "mime", "native-tls", - "percent-encoding 2.3.2", + "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", "serde", "serde_json", @@ -8432,13 +6176,15 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", - "url 2.5.7", + "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", ] [[package]] @@ -8451,30 +6197,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "rgb" -version = "0.8.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.14" @@ -8482,10 +6204,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "cfg-if 1.0.4", - "getrandom 0.2.16", + "cfg-if", + "getrandom 0.2.17", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -8508,99 +6230,98 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.8.12" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a640b26f007713818e9a9b65d34da1cf58538207b052916a83d80e43f3ffa4" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ - "bytecheck 0.8.2", + "bitvec", + "bytecheck 0.6.12", "bytes", - "hashbrown 0.15.5", - "indexmap 2.12.1", - "munge", - "ptr_meta 0.3.1", - "rancor", - "rend", - "rkyv_derive", + "hashbrown 0.12.3", + "ptr_meta 0.1.4", + "rend 0.4.2", + "rkyv_derive 0.7.46", + "seahash", "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd83f5f173ff41e00337d97f6572e416d022ef8a19f371817259ae960324c482" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "rle-decode-fast" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" - -[[package]] -name = "rmp" -version = "0.8.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" -dependencies = [ - "byteorder", - "num-traits", - "paste", + "uuid", ] [[package]] -name = "rmp-serde" -version = "0.14.4" +name = "rkyv" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce7d70c926fe472aed493b902010bccc17fa9f7284145cb8772fd22fdb052d8" +checksum = "1a30e631b7f4a03dee9056b8ef6982e8ba371dd5bedb74d3ec86df4499132c70" dependencies = [ - "byteorder", - "rmp", - "serde", + "bytecheck 0.8.2", + "bytes", + "hashbrown 0.16.1", + "indexmap", + "munge", + "ptr_meta 0.3.1", + "rancor", + "rend 0.5.3", + "rkyv_derive 0.8.15", + "tinyvec", + "uuid", ] [[package]] -name = "rmp-serde" -version = "0.15.5" +name = "rkyv_derive" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723ecff9ad04f4ad92fe1c8ca6c20d2196d9286e9c60727c4cb5511629260e9d" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" dependencies = [ - "byteorder", - "rmp", - "serde", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "rmp-serde" -version = "1.3.0" +name = "rkyv_derive" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +checksum = "8100bb34c0a1d0f907143db3149e6b4eea3c33b9ee8b189720168e818303986f" dependencies = [ - "byteorder", - "rmp", - "serde", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "rocksdb" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" +checksum = "ddb7af00d2b17dbd07d82c0063e25411959748ff03e8d4f96134c2ff41fce34f" dependencies = [ "libc", "librocksdb-sys", ] +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid 0.9.6", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rtcp" -version = "0.14.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81d30d1c4091644431c22acf9f8be6191b56805e0e977f15ca7104b4a6d6eaec" +checksum = "cb22f1cc99aea8152fdae6a4bc52a9caddf4bd1ff083d897c1f9f279956177e8" dependencies = [ "bytes", "thiserror 1.0.69", @@ -8609,9 +6330,9 @@ dependencies = [ [[package]] name = "rtp" -version = "0.14.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f126f38ea84c02480e32e547c1459a939052f74fb92117ac3eef23fdac6b023" +checksum = "f8d41f3565d9add11caabe7c61745517f4ef511c168a6aa2b59ce4c701802cde" dependencies = [ "bytes", "memchr", @@ -8624,36 +6345,39 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.25.4" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4b1eaf239b47034fb450ee9cdedd7d0226571689d8823030c4b6c2cb407152" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 1.3.2", - "fallible-iterator 0.2.0", + "bitflags 2.11.0", + "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", - "memchr", - "smallvec 1.15.1", + "smallvec", ] [[package]] -name = "rust-argon2" -version = "0.8.3" +name = "rust_decimal" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" dependencies = [ - "base64 0.13.1", - "blake2b_simd", - "constant_time_eq 0.1.5", - "crossbeam-utils", + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv 0.7.46", + "serde", + "serde_json", ] [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" @@ -8691,7 +6415,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -8700,61 +6424,57 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring 0.17.14", - "rustls-webpki 0.101.7", - "sct", -] - -[[package]] -name = "rustls" -version = "0.23.35" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ - "log", "once_cell", - "ring 0.17.14", + "ring", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "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.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -8771,11 +6491,11 @@ dependencies = [ "jni 0.21.1", "log", "once_cell", - "rustls 0.23.35", + "rustls", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.8", - "security-framework 3.5.1", + "rustls-webpki", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -8789,23 +6509,13 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ - "ring 0.17.14", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -8816,20 +6526,18 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ruzstd" -version = "0.5.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c4eb8a81997cf040a091d1f7e1938aeab6749d3a0dfa73af43cdc32393483d" +checksum = "e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01" dependencies = [ - "byteorder", - "derive_more 0.99.20", "twox-hash", ] [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safetensors" @@ -8842,22 +6550,14 @@ dependencies = [ ] [[package]] -name = "salsa20" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399f290ffc409596022fce5ea5d4138184be4784f2b28c62c59f0d8389059a15" -dependencies = [ - "cipher 0.2.5", - "zeroize", -] - -[[package]] -name = "salsa20" -version = "0.10.2" +name = "safetensors" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +checksum = "675656c1eabb620b921efea4f9199f97fc86e36dd6ffd1fbbe48d0f59a4987f5" dependencies = [ - "cipher 0.4.4", + "hashbrown 0.16.1", + "serde", + "serde_json", ] [[package]] @@ -8869,15 +6569,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scc" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" -dependencies = [ - "sdd", -] - [[package]] name = "schannel" version = "0.1.28" @@ -8888,60 +6579,41 @@ dependencies = [ ] [[package]] -name = "scheduled-thread-pool" -version = "0.2.7" +name = "schemars" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ - "parking_lot 0.12.5", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.14", - "untrusted 0.9.0", -] - -[[package]] -name = "sd-notify" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cd08a21f852bd2fe42e3b2a6c76a0db6a95a5b5bd29c0521dd0b30fa1712ec8" - -[[package]] -name = "sdd" -version = "3.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" - [[package]] name = "sdp" -version = "0.10.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32c374dceda16965d541c8800ce9cc4e1c14acfd661ddf7952feeedc3411e5c6" +checksum = "22c3b0257608d7de4de4c4ea650ccc2e6e3e45e3cd80039fcdee768bcb449253" dependencies = [ "rand 0.9.2", "substring", "thiserror 1.0.69", - "url 2.5.7", + "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" @@ -8958,24 +6630,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -8984,9 +6643,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -8994,15 +6653,15 @@ dependencies = [ [[package]] name = "self_cell" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33" +checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" [[package]] name = "semantic_queries" version = "0.1.0" dependencies = [ - "aingle_graph 0.2.0", + "aingle_graph", "serde", "serde_json", ] @@ -9033,15 +6692,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-transcode" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" -dependencies = [ - "serde", -] - [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -9061,20 +6711,10 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "xml", ] -[[package]] -name = "serde_bytes" -version = "0.11.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" -dependencies = [ - "serde", - "serde_core", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -9092,21 +6732,20 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ - "indexmap 2.12.1", "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -9120,15 +6759,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "serde_spanned" version = "1.0.4" @@ -9150,53 +6780,13 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yml" -version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" -dependencies = [ - "indexmap 2.12.1", - "itoa", - "libyml", - "memchr", - "ryu", - "serde", - "version_check", -] - -[[package]] -name = "serial_test" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" -dependencies = [ - "futures", - "log", - "once_cell", - "parking_lot 0.12.5", - "scc", - "serial_test_derive", -] - -[[package]] -name = "serial_test_derive" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "digest 0.10.7", ] @@ -9207,21 +6797,30 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.11.1", +] + [[package]] name = "sha3" -version = "0.9.1" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", + "digest 0.10.7", "keccak", - "opaque-debug", ] [[package]] @@ -9249,36 +6848,13 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "shrinkwraprs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63e6744142336dfb606fe2b068afa2e1cca1ee6a5d8377277a92945d81fa331" -dependencies = [ - "bitflags 1.3.2", - "itertools 0.8.2", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "signal-hook" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" -dependencies = [ - "libc", - "mio 0.7.14", - "signal-hook-registry", -] - [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -9306,69 +6882,42 @@ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "simple_asn1" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", ] [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -dependencies = [ - "serde", -] +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "sled" -version = "0.34.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" -dependencies = [ - "crc32fast", - "crossbeam-epoch", - "crossbeam-utils", - "fs2", - "fxhash", - "libc", - "log", - "parking_lot 0.11.2", -] - -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - -[[package]] -name = "smallstr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e922794d168678729ffc7e07182721a14219c65814e66e91b839a272fe5ae4f" -dependencies = [ - "smallvec 1.15.1", -] +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "smallvec" -version = "0.6.14" +name = "sled" +version = "0.34.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" dependencies = [ - "maybe-uninit", + "crc32fast", + "crossbeam-epoch", + "crossbeam-utils", + "fs2", + "fxhash", + "libc", + "log", + "parking_lot 0.11.2", ] [[package]] @@ -9418,21 +6967,21 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] -name = "socket2" -version = "0.3.19" +name = "socket-pktinfo" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" dependencies = [ - "cfg-if 1.0.4", "libc", - "winapi", + "socket2 0.6.3", + "windows-sys 0.60.2", ] [[package]] @@ -9447,49 +6996,28 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "sodoken" -version = "0.0.4" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a56bf1094466dc909027680d3974e4a73c1fe5aa7fa116a43c71c37decb699" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "libsodium-sys-stable", - "num_cpus", - "once_cell", - "one_err", - "parking_lot 0.11.2", - "tokio", + "windows-sys 0.61.2", ] [[package]] name = "spargebra" -version = "0.3.5" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8907e262be4b4b363218f4688f5654d423a958aa4b8d7c7a7f898be591fa474e" +checksum = "e9a6bc0ae1da1bf2516fb872d2ad0cf5efa3c7a7dbea58909b39eed321dc5117" dependencies = [ "oxilangtag", "oxiri", "oxrdf", "peg", - "rand 0.8.5", - "thiserror 2.0.17", + "rand 0.9.2", + "thiserror 2.0.18", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -9527,65 +7055,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" -[[package]] -name = "str_stack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" - -[[package]] -name = "stream-cancel" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9fbf9bd71e4cf18d68a8a0951c0e5b7255920c0cd992c4ff51cddd6ef514a3" -dependencies = [ - "futures-core", - "pin-project", - "tokio", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "strum" version = "0.24.1" @@ -9606,11 +7081,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.26.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" dependencies = [ - "strum_macros 0.26.4", + "strum_macros 0.28.0", ] [[package]] @@ -9636,38 +7111,37 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "rustversion", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "stun" -version = "0.9.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a512c5d501e3e3b5a4bb3e8e31462d56d54a66b95a28b8596e14422bf21c32b" +checksum = "2e0fd33c04d4617df42c9c84c698511c59f59869629fb7a193067eec41bce347" dependencies = [ - "base64 0.22.1", + "base64", "crc", "lazy_static", "md-5", "rand 0.9.2", - "ring 0.17.14", + "ring", "subtle", "thiserror 1.0.69", "tokio", - "url 2.5.7", + "url", "webrtc-util", ] @@ -9677,7 +7151,7 @@ version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" dependencies = [ - "autocfg 1.5.0", + "autocfg", ] [[package]] @@ -9705,9 +7179,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -9723,18 +7197,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -9743,7 +7205,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -9752,7 +7214,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7dddc5f0fee506baf8b9fdb989e242f17e4b11c61dfbb0635b705217199eea" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "enum-as-inner", "libc", @@ -9761,26 +7223,26 @@ dependencies = [ ] [[package]] -name = "sysinfo" -version = "0.37.2" +name = "sysctl" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +checksum = "01198a2debb237c62b6826ec7081082d951f46dbb64b0e8c7649a452230d1dfc" dependencies = [ + "bitflags 2.11.0", + "byteorder", + "enum-as-inner", "libc", - "memchr", - "ntapi 0.4.1", - "objc2-core-foundation", - "objc2-io-kit", - "windows", + "thiserror 1.0.69", + "walkdir", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -9795,6 +7257,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" @@ -9808,94 +7276,23 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tempdir" -version = "0.3.7" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all 0.5.3", -] +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", - "rustix 1.1.2", + "rustix 1.1.4", "windows-sys 0.61.2", ] -[[package]] -name = "terminfo" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" -dependencies = [ - "dirs 4.0.0", - "fnv", - "nom", - "phf", - "phf_codegen", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "test-case" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" -dependencies = [ - "test-case-macros", -] - -[[package]] -name = "test-case-core" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" -dependencies = [ - "cfg-if 1.0.4", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "test-case-macros" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", - "test-case-core", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width 0.1.14", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -9907,11 +7304,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -9922,18 +7319,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -9942,35 +7339,35 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -10023,71 +7420,32 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "titans_memory" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0305f54897e871182c1cc5c8b52ad9cad46ee30bc8490a8927c90af080204001" -dependencies = [ - "blake3", - "chrono", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "titans_memory" -version = "0.2.0" -dependencies = [ - "blake3", - "chrono", - "criterion", - "log", - "rusqlite", - "serde", - "serde_json", -] - -[[package]] -name = "titans_memory" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c99211aa167be140c940883c120fd68e5eb25ec31c20b2b33c14047c5abf61" -dependencies = [ - "blake3", - "chrono", - "log", - "serde", - "serde_json", -] - [[package]] name = "tokio" -version = "1.48.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", - "mio 1.1.1", + "mio", "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -10100,31 +7458,21 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -10134,60 +7482,15 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" dependencies = [ - "async-stream 0.3.6", - "bytes", "futures-core", "tokio", "tokio-stream", ] -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "rustls 0.21.12", - "tokio", - "tokio-rustls 0.24.1", - "tungstenite 0.20.1", - "webpki-roots 0.25.4", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.21.0", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "rustls 0.23.35", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.4", - "tungstenite 0.24.0", - "webpki-roots 0.26.11", -] - [[package]] name = "tokio-tungstenite" version = "0.28.0" @@ -10202,9 +7505,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -10215,45 +7518,18 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_edit 0.22.27", -] - -[[package]] -name = "toml" -version = "0.9.10+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" -dependencies = [ - "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_writer", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" +name = "toml" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "serde", + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] @@ -10266,46 +7542,35 @@ dependencies = [ ] [[package]] -name = "toml_edit" -version = "0.22.27" +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" dependencies = [ - "indexmap 2.12.1", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", - "toml_write", - "winnow", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ - "indexmap 2.12.1", - "toml_datetime 0.7.5+spec-1.1.0", + "indexmap", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - [[package]] name = "toml_writer" version = "1.0.6+spec-1.1.0" @@ -10314,9 +7579,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -10335,19 +7600,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "async-compression", - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "http-range-header", "httpdate", "iri-string", "mime", "mime_guess", - "percent-encoding 2.3.2", + "percent-encoding", "pin-project-lite", "tokio", "tokio-util", @@ -10389,7 +7654,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -10402,16 +7667,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -10423,26 +7678,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.22" @@ -10453,15 +7688,12 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", - "serde", - "serde_json", "sharded-slab", - "smallvec 1.15.1", + "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde 0.2.0", ] [[package]] @@ -10470,45 +7702,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 0.2.12", - "httparse", - "log", - "rand 0.8.5", - "rustls 0.21.12", - "sha1", - "thiserror 1.0.69", - "url 2.5.7", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.4.0", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "url 2.5.7", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.23.0" @@ -10518,30 +7711,10 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.0", - "httparse", - "log", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.8.5", - "rustls 0.23.35", - "rustls-pki-types", "sha1", "thiserror 1.0.69", "utf-8", @@ -10555,29 +7728,29 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.9.2", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] [[package]] name = "turn" -version = "0.11.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed995882f66ab94238de77c62e5e778389698ab700afa4696f4754da8f457cb" +checksum = "b6a8b8ac3543b2a8eb0b28c7ac3d5f2db6221e057f3b3ae47cf7637b1333a5c3" dependencies = [ "async-trait", - "base64 0.22.1", + "base64", "futures", "log", "md-5", "portable-atomic", "rand 0.9.2", - "ring 0.17.14", + "ring", "stun", "thiserror 1.0.69", "tokio", @@ -10587,13 +7760,15 @@ dependencies = [ [[package]] name = "twox-hash" -version = "1.6.3" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if 0.1.10", - "static_assertions", -] +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" [[package]] name = "typenum" @@ -10618,30 +7793,15 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -10649,50 +7809,22 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "universal-hash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -10700,93 +7832,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "unwrap_to" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad414b2eed757c1b6f810f8abc814e298a9c89176b21fae092c7a87756fb839" - -[[package]] -name = "ureq" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" -dependencies = [ - "base64 0.22.1", - "flate2", - "log", - "once_cell", - "rustls 0.23.35", - "rustls-pki-types", - "url 2.5.7", - "webpki-roots 0.26.11", -] - -[[package]] -name = "ureq" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" -dependencies = [ - "base64 0.22.1", - "log", - "percent-encoding 2.3.2", - "ureq-proto", - "utf-8", -] - -[[package]] -name = "ureq-proto" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" -dependencies = [ - "base64 0.22.1", - "http 1.4.0", - "httparse", - "log", -] - -[[package]] -name = "url" -version = "1.7.2" +name = "unty" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", - "idna 1.1.0", - "percent-encoding 2.3.2", - "serde", -] - -[[package]] -name = "url2" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89cd13f1de9862d363308f5ffdadcd2b64b2a4a812fb296a80b7d3e80011b1e" -dependencies = [ - "serde", - "url 2.5.7", -] - -[[package]] -name = "url_serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e7d099f1ee52f823d4bdd60c93c3602043c728f5db3b97bdb548467f7bddea" -dependencies = [ + "idna", + "percent-encoding", "serde", - "url 1.7.2", ] [[package]] @@ -10795,6 +7855,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" @@ -10809,41 +7875,29 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.2", "js-sys", "serde_core", - "uuid-macro-internal", "wasm-bindgen", ] -[[package]] -name = "uuid-macro-internal" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39d11901c36b3650df7acb0f9ebe624f35b5ac4e1922ecd3c57f444648429594" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "validator" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" dependencies = [ - "idna 1.1.0", + "idna", "once_cell", "regex", "serde", "serde_derive", "serde_json", - "url 2.5.7", + "url", "validator_derive", ] @@ -10858,7 +7912,16 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "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]] @@ -10879,12 +7942,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.5" @@ -10892,82 +7949,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "void" -version = "1.0.2" +name = "virtue" +version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" [[package]] -name = "wait-timeout" -version = "0.2.1" +name = "void" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "waitgroup" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" -dependencies = [ - "atomic-waker", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "warp" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http 0.2.12", - "hyper 0.14.32", - "log", - "mime", - "mime_guess", - "multer 2.1.0", - "percent-encoding 2.3.2", - "pin-project", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-tungstenite 0.21.0", - "tokio-util", - "tower-service", - "tracing", +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", ] [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] [[package]] name = "wasi" @@ -10977,20 +7996,29 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -10999,11 +8027,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -11012,9 +8041,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11022,48 +8051,60 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" -version = "0.243.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser 0.243.0", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", ] [[package]] name = "wasmer" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b8204e4eb959d89b41d4a536e61ce73f5416bccc81c7d3b7fa993995538ee97" +checksum = "b76134972161fb9ae6d956d3e79177a51c6c968d4f95fdba060a95897b2fe81c" dependencies = [ - "bindgen 0.70.1", + "bindgen 0.72.1", "bytes", - "cfg-if 1.0.4", + "cfg-if", "cmake", - "derive_more 1.0.0", - "indexmap 2.12.1", + "derive_more", + "indexmap", "js-sys", "more-asserts", "paste", @@ -11073,65 +8114,67 @@ dependencies = [ "shared-buffer", "tar", "target-lexicon", - "thiserror 1.0.69", + "thiserror 2.0.18", "tracing", - "ureq 2.12.1", "wasm-bindgen", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-derive", "wasmer-types", "wasmer-vm", - "wasmparser 0.224.1", - "wat", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "wasmer-compiler" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "690827b8ec4f3858d8b001d96ddfc25c28a255cbfa984ba5bd1ed173f29ffc2a" +checksum = "c21c166e89212d5bc31d08dffdb189fe689f4ca58756ccd924f9fc3ec2ee89da" dependencies = [ "backtrace", "bytes", - "cfg-if 1.0.4", + "cfg-if", "enum-iterator", "enumset", + "itertools 0.14.0", "leb128", "libc", "macho-unwind-info", - "memmap2 0.6.2", + "memmap2 0.9.10", "more-asserts", - "object 0.32.2", + "object 0.38.1", + "rangemap", "region", - "rkyv", + "rkyv 0.8.15", "self_cell", "shared-buffer", - "smallvec 1.15.1", + "smallvec", "target-lexicon", - "thiserror 1.0.69", + "tempfile", + "thiserror 2.0.18", "wasmer-types", "wasmer-vm", - "wasmparser 0.224.1", - "windows-sys 0.59.0", - "xxhash-rust", + "wasmparser", + "which 8.0.2", + "windows-sys 0.61.2", ] [[package]] name = "wasmer-compiler-cranelift" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a46a83b498a2f0dcdc2e97d611db9eae92a38f20fc5dac4709d645bdfd8d2d6" +checksum = "2a3174d3c78dd581d590860195420305581e2708cb1082ce8bea05cf6ed0e432" dependencies = [ "cranelift-codegen", "cranelift-entity", "cranelift-frontend", - "gimli 0.28.1", - "itertools 0.12.1", + "gimli", + "indexmap", + "itertools 0.14.0", + "leb128", "more-asserts", "rayon", - "smallvec 1.15.1", + "smallvec", "target-lexicon", "tracing", "wasmer-compiler", @@ -11140,121 +8183,91 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaedaf20c22736904ad842127cdbe46432998dbcdd840b024dda856a8b52265" +checksum = "20424fca4c6a757d7115a39ad5c6a5d369f5f864a5c64263d6de14b40f060cc8" dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "wasmer-middlewares" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dbba8c5d347898b8c5a7181da66fd95f62ee56c26fc8d9a40875fb4c0b8c39b" -dependencies = [ - "wasmer", - "wasmer-types", - "wasmer-vm", + "syn 2.0.117", ] [[package]] name = "wasmer-types" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b45fd1274b21365d3232732afe53c220ecbcdb78946405087e7016e7b2369a0" +checksum = "9a7f91b0cb63705afa0843b46a0aeaeaedff7be2e5b05691176e9e58e2dbe921" dependencies = [ - "bytecheck 0.6.12", + "bytecheck 0.8.2", "enum-iterator", "enumset", - "getrandom 0.2.16", + "getrandom 0.2.17", "hex", - "indexmap 2.12.1", + "indexmap", "more-asserts", - "rkyv", - "sha2", + "rkyv 0.8.15", + "sha2 0.11.0-rc.5", "target-lexicon", - "thiserror 1.0.69", - "xxhash-rust", + "thiserror 2.0.18", ] [[package]] name = "wasmer-vm" -version = "6.0.0" +version = "7.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4e7cec7b509e70664773f03907e6122d1633c100cb28009da770786806e6db" +checksum = "12da92bb2c2abd09628d28d112970880a9e671b7ccb55172e5f6db01b5d6add4" dependencies = [ "backtrace", "cc", - "cfg-if 1.0.4", + "cfg-if", "corosensei", "crossbeam-queue", "dashmap 6.1.0", "enum-iterator", "fnv", - "indexmap 2.12.1", + "gimli", + "indexmap", "libc", "libunwind", - "mach2", + "mach2 0.6.0", "memoffset 0.9.1", "more-asserts", + "parking_lot 0.12.5", "region", + "rustversion", "scopeguard", - "thiserror 1.0.69", + "thiserror 2.0.18", "wasmer-types", - "windows-sys 0.59.0", -] - -[[package]] -name = "wasmparser" -version = "0.224.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f17a5917c2ddd3819e84c661fae0d6ba29d7b9c1f0e96c708c65a9c4188e11" -dependencies = [ - "bitflags 2.10.0", + "windows-sys 0.61.2", ] [[package]] name = "wasmparser" -version = "0.243.0" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", - "indexmap 2.12.1", + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", "semver", ] [[package]] -name = "wast" -version = "243.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df21d01c2d91e46cb7a221d79e58a2d210ea02020d57c092e79255cc2999ca7f" -dependencies = [ - "bumpalo", - "leb128fmt", - "memchr", - "unicode-width 0.2.2", - "wasm-encoder", -] - -[[package]] -name = "wat" -version = "1.243.0" +name = "wasmtime-internal-math" +version = "41.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226a9a91cd80a50449312fef0c75c23478fcecfcc4092bdebe1dc8e760ef521b" +checksum = "d61fe7cfca53d0ce01dc480ce1db93ad48b6fa1f354d8ff0680ac6a76ef354a3" dependencies = [ - "wast", + "libm", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -11272,42 +8285,27 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" dependencies = [ "rustls-pki-types", ] [[package]] name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.4", -] - -[[package]] -name = "webpki-roots" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] [[package]] name = "webrtc" -version = "0.14.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08fd686c0920ac08f3a57eacc48e31f0e4ca1ffefba4478784606f78c14e83ad" +checksum = "8ba06986c4fcfbbb4b490abe4b88887b0ac9de0d3eb0aae36f3254e38d6ecdd1" dependencies = [ "arc-swap", "async-trait", @@ -11319,22 +8317,22 @@ dependencies = [ "log", "portable-atomic", "rand 0.9.2", - "rcgen 0.13.2", + "rcgen", "regex", - "ring 0.17.14", + "ring", "rtcp", "rtp", "sdp", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smol_str", "stun", "thiserror 1.0.69", "tokio", "turn", "unicase", - "url 2.5.7", + "url", "waitgroup", "webrtc-data", "webrtc-ice", @@ -11347,9 +8345,9 @@ dependencies = [ [[package]] name = "webrtc-data" -version = "0.12.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062a5438d63bb0756a221693d76cc0dd6119affee1dfdfe57abe3a2a8c8b3eea" +checksum = "a7ca42127ee64bcb71da36d151e6f87b12488c5f14c4f379e73d2d52a8e54aa0" dependencies = [ "bytes", "log", @@ -11362,9 +8360,9 @@ dependencies = [ [[package]] name = "webrtc-ice" -version = "0.14.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cb13fd1a373e68addc4bba0c8ca058627518e54342583d024bdcbb8ae5d97d" +checksum = "23ede72a36e5dda685814c389b2b34ac60b3ed000a81789e93626e27180eb785" dependencies = [ "arc-swap", "async-trait", @@ -11378,7 +8376,7 @@ dependencies = [ "thiserror 1.0.69", "tokio", "turn", - "url 2.5.7", + "url", "uuid", "waitgroup", "webrtc-mdns", @@ -11387,9 +8385,9 @@ dependencies = [ [[package]] name = "webrtc-mdns" -version = "0.10.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17279a067e75df72ce923fdeb7f04cd808f6f5aa4910dc6bcb4fbe66b396ace" +checksum = "1b62bc8d3fb5024bc6ffde5f4aad2127ce17f8359dbc6f70208a324a12e44677" dependencies = [ "log", "socket2 0.5.10", @@ -11400,9 +8398,9 @@ dependencies = [ [[package]] name = "webrtc-media" -version = "0.11.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a84c910fec0848fd5a0d8a5651e0ddbdedaf25a7d3ae3f0b15f71ac73a1773" +checksum = "a9a28290bd0cdda196f8bf5c6f7dddaef18cc913ee0702cd1ea237bad203e337" dependencies = [ "byteorder", "bytes", @@ -11413,9 +8411,9 @@ dependencies = [ [[package]] name = "webrtc-sctp" -version = "0.13.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f985465467d8910c1f8ac4382cd64f83b1f6a1a75021a82b221546f6fb3b856f" +checksum = "2ea6633bf951b3fd71eba3244731c8d0814f6a80300620a4370cad2983a5a42f" dependencies = [ "arc-swap", "async-trait", @@ -11431,11 +8429,11 @@ dependencies = [ [[package]] name = "webrtc-srtp" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d8cdc33413f1d0192670a80ce93d17cb78d57fe3a2414be30d6f6dff121123" +checksum = "9e437f74b04f42049192e25cbb33c21c86be308875e6afe14cf8e28d1ffa35ac" dependencies = [ - "aead 0.5.2", + "aead", "aes", "aes-gcm", "byteorder", @@ -11454,9 +8452,9 @@ dependencies = [ [[package]] name = "webrtc-util" -version = "0.12.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c0c7e0c8f280f2bbfae442701465777ac07adaf46ce0c5863cd58e13fe472a" +checksum = "b65c1e0143a43d40f69e1d8c2ffc8734e379b49c06d45892ea4104c388bf9ead" dependencies = [ "async-trait", "bitflags 1.3.2", @@ -11484,6 +8482,15 @@ dependencies = [ "rustix 0.38.44", ] +[[package]] +name = "which" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" +dependencies = [ + "libc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -11582,7 +8589,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -11593,7 +8600,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -11674,15 +8681,6 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -11734,21 +8732,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -11797,12 +8780,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -11821,12 +8798,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -11845,12 +8816,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -11881,12 +8846,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -11905,12 +8864,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -11929,12 +8882,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -11953,12 +8900,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -11973,18 +8914,100 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -11993,14 +9016,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] -name = "x25519-dalek" -version = "1.1.1" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "curve25519-dalek 3.2.0", - "rand_core 0.5.1", - "zeroize", + "tap", ] [[package]] @@ -12009,7 +9030,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek 4.1.3", + "curve25519-dalek", "rand_core 0.6.4", "serde", "zeroize", @@ -12027,7 +9048,7 @@ dependencies = [ "lazy_static", "nom", "oid-registry", - "ring 0.17.14", + "ring", "rusticata-macros", "thiserror 1.0.69", "time", @@ -12040,43 +9061,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.1.2", + "rustix 1.1.4", ] [[package]] name = "xml" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df5825faced2427b2da74d9100f1e2e93c533fff063506a81ede1cf517b2e7e" - -[[package]] -name = "xsalsa20poly1305" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0304c336e98d753428f7b3d8899d60b8a87a961ef50bdfc44af0c1bea2651ce5" -dependencies = [ - "aead 0.3.2", - "poly1305 0.6.2", - "rand_core 0.5.1", - "salsa20 0.7.2", - "subtle", - "zeroize", -] - -[[package]] -name = "xxhash-rust" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" - -[[package]] -name = "yasna" -version = "0.4.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" -dependencies = [ - "chrono", -] +checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" [[package]] name = "yasna" @@ -12118,8 +9110,8 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -12130,28 +9122,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "96e13bc581734df6250836c59a5f44f3c57db9f9acb9dc8e3eaabdaf6170254d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "3545ea9e86d12ab9bba9fcd99b54c1556fd3199007def5a03c375623d05fac1c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -12171,8 +9163,8 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", - "synstructure 0.13.2", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -12186,13 +9178,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -12225,7 +9217,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -12241,35 +9233,21 @@ dependencies = [ [[package]] name = "zip" -version = "6.0.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ - "arbitrary", "crc32fast", - "flate2", - "indexmap 2.12.1", + "indexmap", "memchr", - "zopfli", + "typed-path", ] [[package]] -name = "zlib-rs" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" - -[[package]] -name = "zopfli" -version = "0.8.3" +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zstd-sys" @@ -12277,7 +9255,6 @@ version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ - "bindgen 0.72.1", "cc", "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index 766b4ff2..672ec8e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,45 +1,38 @@ [workspace] resolver = "2" members = [ + # ── Product crates ────────────────────────────────────────────── + "crates/aingle_graph", # Native Semantic GraphDB + "crates/aingle_zk", # Zero-Knowledge Proofs (Privacy) + "crates/ineru", # Ineru Memory System + "crates/aingle_ai", # AI Integration Layer + "crates/aingle_logic", # Proof-of-Logic Validation Engine + "crates/kaneru", # Kaneru — Unified Multi-Agent Execution System + "crates/aingle_cortex", # Córtex API (REST/GraphQL/SPARQL) + "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", + "examples/ai_autonomous_agent", + "examples/dag_visualization", + "examples/semantic_queries", +] + +exclude = [ + # Legacy Holochain-derived crates (not part of the product build) "crates/ai_fixt", "crates/ai_fixt/test", "crates/adk", "crates/adk_derive", "crates/ai_hash", "crates/mr_bundle", - "crates/ai", "crates/ai_bundle", "crates/ai_sandbox", - - # IoT-optimized minimal node - "crates/aingle_minimal", - - # AI Integration Layer (Titans Memory, Nested Learning, HOPE Agents) - "crates/aingle_ai", - - # DAG Visualization Server - "crates/aingle_viz", - - # Native Semantic GraphDB - "crates/aingle_graph", - - # Proof-of-Logic Validation Engine - "crates/aingle_logic", - - # Córtex API (REST/GraphQL/SPARQL) - "crates/aingle_cortex", - - # Zero-Knowledge Proofs (Privacy) - "crates/aingle_zk", - - # Smart Contracts (DSL + WASM Runtime) - "crates/aingle_contracts", - - # AI Memory Systems - "crates/titans_memory", - "crates/hope_agents", - "crates/aingle", "crates/aingle_cascade", "crates/aingle_conductor_api", @@ -47,12 +40,10 @@ members = [ "crates/aingle_keystore", "crates/aingle_sqlite", "crates/aingle_state", - "crates/aingle_sqlite", "crates/aingle_types", "crates/aingle_websocket", "crates/aingle_util", "crates/aingle_zome_types", - "crates/kitsune_p2p/bootstrap", "crates/kitsune_p2p/direct", "crates/kitsune_p2p/direct_api", @@ -62,18 +53,8 @@ members = [ "crates/kitsune_p2p/proxy", "crates/kitsune_p2p/transport_quic", "crates/kitsune_p2p/types", - "crates/test_utils/wasm", "crates/test_utils/wasm_common", - - # Examples - "examples/iot_sensor_network", - "examples/ai_autonomous_agent", - "examples/dag_visualization", - "examples/semantic_queries", -] - -exclude = [ "crates/diagnostics", "examples/semantic_compliance", "examples/deep_context", @@ -90,10 +71,6 @@ incremental = false codegen-units = 16 # Workspace-wide lints configuration -[workspace.lints.rust] -# Allow ghost_actor's test_utils cfg -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test_utils"))'] } - [workspace.lints.rustdoc] bare_urls = "allow" invalid_html_tags = "allow" @@ -104,13 +81,3 @@ redundant_explicit_links = "allow" # Workspace dependencies - ensure rustls uses ring crypto provider [workspace.dependencies] rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } - -[patch.crates-io] -# Using Git refs until new versions are published with flexible serde version -# Published v0.0.1 has serde = "=1.0.123" which conflicts with other deps -aingle_wasmer_common = { git = "https://github.com/ApiliumCode/aingle-wasmer.git", branch = "main" } -aingle_wasmer_guest = { git = "https://github.com/ApiliumCode/aingle-wasmer.git", branch = "main" } -aingle_wasmer_host = { git = "https://github.com/ApiliumCode/aingle-wasmer.git", branch = "main" } -# Local path overrides for renamed crates -ai_fixt = { path = "crates/ai_fixt" } -ai_hash = { path = "crates/ai_hash" } diff --git a/LICENSE b/LICENSE index 931aba35..41762893 100644 --- a/LICENSE +++ b/LICENSE @@ -1,190 +1,56 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright - owner or by an individual or Legal Entity authorized to submit on behalf - of the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - Copyright 2019-2025 Apilium Technologies - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +AIngle — Dual License + +Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. + +AIngle is available under a dual license model: + +1. APACHE LICENSE 2.0 (Non-Commercial Use) + + You may use, modify, and distribute AIngle under the Apache License, + Version 2.0 for non-commercial purposes. This includes: + + - Personal projects and experiments + - Academic research and education + - Evaluation and prototyping + - Use by organizations with annual gross revenue below USD $1,000,000 + + See LICENSE-APACHE for the full Apache 2.0 license text. + +2. COMMERCIAL LICENSE (Commercial Use) + + A separate commercial license is REQUIRED if: + + - You integrate AIngle into a product or service that is sold or + commercially distributed + - You offer AIngle (or a modified version) as a hosted service + (SaaS, PaaS, or similar) + - Your organization's annual gross revenue exceeds USD $1,000,000 + + See LICENSE-COMMERCIAL for terms and contact information. + +For commercial licensing inquiries: + + Email: partners@apilium.com + Web: https://apilium.com/en/contact + +================================================================================ + +TRADEMARKS + +"AIngle", "AIngle Cortex", "Ineru", and "Kaneru" are trademarks +of Apilium Technologies OÜ. See NOTICE for trademark policy. + +PATENTS + +See the PATENTS file for protected technologies and innovations. + +SECURITY + +See SECURITY.md for vulnerability reporting. + +================================================================================ + +Apilium Technologies OÜ +Registration Number: 17409213 +Tallinn, Estonia +https://apilium.com diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..8f40ee41 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to the Licensor for inclusion in the Work by the copyright + owner or by an individual or Legal Entity authorized to submit on behalf + of the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2019-2026 Apilium Technologies + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-COMMERCIAL b/LICENSE-COMMERCIAL new file mode 100644 index 00000000..a6b71a4e --- /dev/null +++ b/LICENSE-COMMERCIAL @@ -0,0 +1,141 @@ +AINGLE COMMERCIAL LICENSE AGREEMENT +Version 1.0, March 2026 + +Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. + +================================================================================ + +IMPORTANT: READ CAREFULLY BEFORE USING THIS SOFTWARE UNDER COMMERCIAL TERMS. + +This Commercial License Agreement ("Agreement") is between Apilium Technologies +OÜ, a company registered in Tallinn, Estonia ("Licensor"), and the entity or +individual obtaining a commercial license ("Licensee"). + +================================================================================ + +1. DEFINITIONS + +"Software" means the AIngle source code, binaries, documentation, and any +associated materials available at https://github.com/ApiliumCode/aingle. + +"Commercial Use" means any use of the Software that meets one or more of the +following criteria: + (a) Integration, embedding, or bundling of the Software (in whole or in part) + into a product or service that is sold, licensed, or otherwise + commercially distributed; + (b) Offering the Software, or a modified version thereof, as a hosted or + managed service (SaaS, PaaS, or similar) to third parties; + (c) Use of the Software by an organization with annual gross revenue + exceeding one million United States dollars (USD $1,000,000). + +"Non-Commercial Use" means any use that does not constitute Commercial Use, +including personal projects, academic research, education, evaluation, and +use by organizations with annual gross revenue below USD $1,000,000. + +2. DUAL LICENSING + +The Software is available under two licenses: + + (a) Apache License, Version 2.0 ("Apache-2.0") — for Non-Commercial Use. + The full text is available in the file LICENSE-APACHE. + + (b) This Commercial License — for Commercial Use. A separate commercial + license must be obtained from the Licensor before any Commercial Use. + +If your use qualifies as Non-Commercial Use, you may use the Software under +the terms of Apache-2.0 without obtaining a commercial license. + +If your use qualifies as Commercial Use, you MUST obtain a commercial license +from the Licensor. Using the Software for Commercial Use without a valid +commercial license is a violation of the Licensor's intellectual property rights. + +3. COMMERCIAL LICENSE GRANT + +Upon execution of a Commercial License Agreement and payment of applicable +fees, the Licensor grants the Licensee a non-exclusive, non-transferable, +worldwide license to: + + (a) Use, modify, and create derivative works of the Software; + (b) Integrate the Software into Licensee's commercial products and services; + (c) Distribute the Software as part of Licensee's commercial offerings; + (d) Use the Software internally without limitation. + +The specific terms, scope, duration, and fees of each commercial license are +negotiated individually and documented in a separate Order Form or Statement +of Work. + +4. RESTRICTIONS + +Under this Commercial License, the Licensee may NOT: + + (a) Sublicense or resell the Software as a standalone product; + (b) Remove or alter copyright notices, trademark notices, or attribution + from the Software; + (c) Use the trademarks "AIngle", "AIngle Cortex", "Ineru", + or "Kaneru" except as permitted in writing by the Licensor; + (d) Represent that the Licensee's product IS AIngle or is endorsed by + Apilium Technologies; + (e) Use the Software to create a product that directly competes with the + Software itself (i.e., a semantic DAG database or knowledge graph + engine offered as a standalone product). + +5. INTELLECTUAL PROPERTY + +All intellectual property rights in the Software, including patents, copyrights, +trademarks, and trade secrets, remain with the Licensor. This Agreement does +not transfer any ownership rights to the Licensee. + +The protected technologies described in the PATENTS file are the proprietary +innovations of the Licensor. The commercial license grants usage rights only; +it does not grant patent rights beyond those necessary to use the Software as +licensed. + +6. SUPPORT AND MAINTENANCE + +Commercial license holders are entitled to: + + (a) Priority bug fixes and security patches; + (b) Direct communication with the engineering team; + (c) Service Level Agreements (SLAs) as negotiated in the Order Form; + (d) Early access to new releases and features. + +7. WARRANTY AND LIABILITY + +THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. THE LICENSOR +SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR +PUNITIVE DAMAGES ARISING FROM THE USE OF THE SOFTWARE. + +The Licensor's total liability under this Agreement shall not exceed the total +fees paid by the Licensee in the twelve (12) months preceding the claim. + +8. TERM AND TERMINATION + + (a) This Agreement is effective upon execution and continues for the term + specified in the Order Form. + (b) Either party may terminate this Agreement for material breach with + thirty (30) days written notice. + (c) Upon termination, the Licensee must cease all Commercial Use of the + Software within sixty (60) days. + (d) Sections 4, 5, 7, and 9 survive termination. + +9. GOVERNING LAW + +This Agreement is governed by the laws of the Republic of Estonia, without +regard to conflict of law principles. Any disputes shall be resolved in the +courts of Tallinn, Estonia, or by arbitration under the rules of the Estonian +Chamber of Commerce and Industry. + +10. HOW TO OBTAIN A COMMERCIAL LICENSE + +Contact Apilium Technologies OÜ: + + Email: partners@apilium.com + Web: https://apilium.com/en/contact + Legal: legal@apilium.com + +================================================================================ + +Apilium Technologies OÜ +Registration Number: 17409213 +Tallinn, Estonia +https://apilium.com diff --git a/NOTICE b/NOTICE index 07568bec..ea80c0d0 100644 --- a/NOTICE +++ b/NOTICE @@ -1,7 +1,71 @@ -AIngle -Copyright 2019-2025 Apilium Technologies +AIngle (TM) +Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. This product includes software developed at Apilium Technologies (https://apilium.com/). Licensed under the Apache License, Version 2.0. + +================================================================================ +TRADEMARKS +================================================================================ + +"AIngle", "AIngle Cortex", "Ineru", "Kaneru", and the AIngle logo +are trademarks of Apilium Technologies OÜ. These trademarks may not be used +in connection with any product or service that is not provided by Apilium +Technologies, in any manner that is likely to cause confusion, or in any +manner that disparages or discredits Apilium Technologies. + +Use of these trademarks to describe the origin of this software, as required +by the Apache License, is permitted. Any other use requires prior written +permission from Apilium Technologies OÜ. + +================================================================================ +PROPRIETARY TECHNOLOGIES +================================================================================ + +The following technologies, architectures, and methodologies embodied in this +software are original works of Apilium Technologies OÜ and are protected under +applicable copyright and intellectual property laws: + + - Semantic DAG Architecture: Content-addressed directed acyclic graph with + integrated RDF triple store and provenance tracking + + - AIngle Cortex: Cryptographic semantic layer combining knowledge graphs, + reasoning engines, and zero-knowledge proof systems + + - Kaneru: Unified Multi-Agent Execution System for autonomous + goal decomposition and multi-step reasoning + + - Ineru: Neural-inspired persistence system with attention-weighted + short-term memory and semantic long-term consolidation + + - Proof-of-Logic Engine: Forward and backward chaining inference engine + with ontological consistency validation + + - Zero-Knowledge Integration: Pedersen commitments, Schnorr signatures, + and Bulletproofs range proofs for verifiable computation + + - Nested Learning: Meta-optimization framework for adaptive model behavior + + - AIngle Minimal: Lightweight edge runtime for IoT and embedded devices + +While the source code is licensed under Apache License 2.0, the architectural +designs, algorithms, and integration patterns constitute trade secrets and +proprietary know-how of Apilium Technologies OÜ. + +================================================================================ +CONTACT +================================================================================ + +Apilium Technologies OÜ +Tallinn, Estonia +Registration: 17409213 + +Legal inquiries: legal@apilium.com +Licensing inquiries: partners@apilium.com +Security reports: security@apilium.com +General: hello@apilium.com + +https://apilium.com +https://github.com/ApiliumCode/aingle diff --git a/PATENTS b/PATENTS new file mode 100644 index 00000000..6b70b1c0 --- /dev/null +++ b/PATENTS @@ -0,0 +1,81 @@ +# AIngle — Protected Technologies & Patent Notice + +Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. + +This file describes the proprietary technologies, algorithms, and +architectural innovations embodied in AIngle that are protected under +applicable intellectual property laws. This notice supplements (but +does not replace) the Apache License 2.0 patent grant in Section 3. + +## Protected Innovations + +### 1. Semantic DAG Architecture + +A content-addressed directed acyclic graph that combines cryptographic +integrity (blake3 hashing), RDF triple storage with SPO/POS/OSP indexing, +and provenance-tracked knowledge management in a single unified data +structure. + +### 2. AIngle Cortex + +A cryptographic semantic layer that integrates: +- Native RDF triple store with SPARQL 1.1 query execution +- Pattern-based graph queries with namespace isolation +- Proof-of-Logic validation engine +- Zero-knowledge proof generation and verification +- REST/WebSocket API for external integration + +### 3. Proof-of-Logic Engine + +An inference engine implementing both forward chaining (data-driven) +and backward chaining (goal-driven) reasoning over RDF knowledge graphs, +with ontological consistency validation and rule-based inference. + +### 4. Kaneru (Unified Multi-Agent Execution System) + +A multi-agent framework for autonomous goal decomposition featuring: +- Hierarchical task planning with optimistic execution +- Dynamic goal tree construction and pruning +- Inter-agent coordination via semantic message passing +- Capability-based agent selection and delegation + +### 5. Ineru + +A neural-inspired persistence system implementing: +- Attention-weighted short-term memory with configurable decay +- Semantic long-term memory with automatic consolidation +- Importance scoring for STM-to-LTM promotion +- Content-addressed storage with semantic similarity search + +### 6. Zero-Knowledge Proof Integration + +Integration of zero-knowledge proof systems into a semantic knowledge +graph, enabling verifiable computation over private data: +- Pedersen commitment schemes for value hiding +- Schnorr signature proofs for identity verification +- Bulletproofs range proofs for numerical assertions +- Proof composition for complex multi-step verification + +### 7. Nested Learning + +A meta-optimization framework that enables adaptive model behavior +through hierarchical learning loops operating on the knowledge graph. + +### 8. AIngle Minimal + +A lightweight edge runtime designed for IoT and resource-constrained +devices, implementing a subset of the AIngle protocol with: +- Minimal memory footprint graph operations +- Device capability sharing via semantic descriptors +- Peer-to-peer sync with gossip protocol + +## Patent Status + +Apilium Technologies OÜ reserves all rights to seek patent protection +for the innovations described above in any jurisdiction. The Apache +License 2.0 patent grant (Section 3) applies only to contributions +as defined in that license and does not constitute a waiver of patent +rights for the system-level architectural innovations described herein. + +For patent licensing inquiries: legal@apilium.com +For partnership opportunities: partners@apilium.com diff --git a/README.md b/README.md index 262b22e2..f635ccc1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- AIngle + AIngle(TM)

@@ -13,7 +13,8 @@

Build Status License - Rust + Rust + Powers Mayros AI

@@ -127,8 +128,8 @@ Prove facts without revealing data. Schnorr signatures, Pedersen commitments, an -### ⚡ HOPE Agents -Hierarchical Optimistic Policy Engine. Reinforcement learning (Q-Learning, SARSA, TD) for autonomous decision-making. From anomaly detection to resource optimization. +### ⚡ Kaneru +Unified Multi-Agent Execution System. Reinforcement learning (Q-Learning, SARSA, TD) for autonomous decision-making. From anomaly detection to resource optimization. @@ -156,13 +157,80 @@ 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 ``` ┌────────────────────────────────────────────────────────────────────────┐ │ APPLICATION LAYER │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ -│ │ Zomes │ │ Contracts │ │ HOPE Agents │ │ DAG Viz │ │ +│ │ Zomes │ │ Contracts │ │ Kaneru │ │ DAG Viz │ │ │ │ (WASM) │ │ (Rust DSL) │ │ (RL) │ │ (D3.js) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ └───────────┘ │ ├────────────────────────────────────────────────────────────────────────┤ @@ -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** @@ -233,7 +310,7 @@ cargo run --release -- --help |-------|-------------| | [Getting Started](docs/tutorials/getting-started.md) | Build your first AIngle application | | [IoT Networks](docs/tutorials/iot-sensor-network.md) | Deploy sensors with edge intelligence | -| [HOPE Agents](docs/tutorials/ai-powered-app.md) | Add autonomous decision-making | +| [Kaneru](docs/tutorials/ai-powered-app.md) | Add autonomous decision-making | | [Semantic Queries](docs/tutorials/semantic-queries.md) | Master GraphQL and SPARQL | | [Privacy (ZK)](docs/tutorials/privacy-with-zk.md) | Implement zero-knowledge proofs | | [Visualization](docs/tutorials/dag-visualization.md) | Monitor your system in real-time | @@ -260,10 +337,17 @@ cargo doc --workspace --no-deps --open | Component | Purpose | |-----------|---------| -| `hope_agents` | Reinforcement learning framework | +| `kaneru` | Kaneru multi-agent execution framework | | `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 | @@ -291,7 +375,7 @@ Official SDKs for integrating AIngle into your applications: ```javascript import { AIngleClient } from '@apilium/aingle-sdk'; -const client = new AIngleClient('http://localhost:8080'); +const client = new AIngleClient('http://localhost:19090'); // Create an entry const hash = await client.createEntry({ sensor: 'temp', value: 23.5 }); @@ -306,7 +390,7 @@ client.subscribe((entry) => { ```bash # Start node with REST API enabled -aingle-minimal run --rest-port 8080 +aingle-minimal run --rest-port 19080 ``` --- @@ -328,11 +412,17 @@ See our [contribution guidelines](CONTRIBUTING.md) for details. ## License -**Apache License 2.0** +**Dual License: Apache-2.0 + Commercial** + +Copyright © 2019-2026 Apilium Technologies OÜ -Copyright © 2019-2025 Apilium Technologies +AIngle is available under two licenses: -See [LICENSE](LICENSE) for the full license text. +- **Apache License 2.0** — Free for personal use, education, research, evaluation, and organizations with annual revenue below USD $1M. See [LICENSE-APACHE](LICENSE-APACHE). + +- **Commercial License** — Required for commercial integration, SaaS offerings, and organizations with annual revenue above USD $1M. See [LICENSE-COMMERCIAL](LICENSE-COMMERCIAL). + +For commercial licensing: [partners@apilium.com](mailto:partners@apilium.com) --- @@ -349,5 +439,15 @@ See [LICENSE](LICENSE) for the full license text.

- Apilium Technologies • Tallinn, Estonia + Apilium Technologies OÜ • Tallinn, Estonia

+ +--- + + + +**Trademarks**: AIngle, AIngle Cortex, Ineru, and Kaneru are trademarks of Apilium Technologies OÜ. See [NOTICE](./NOTICE) for details. + +**License**: Dual licensed under [Apache-2.0](./LICENSE-APACHE) and [Commercial](./LICENSE-COMMERCIAL). See [PATENTS](./PATENTS) for protected technologies. + + diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..31d526a2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,48 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in AIngle, please report it responsibly. + +**Email**: security@apilium.com + +Please include: +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +## Response Timeline + +- **Acknowledgment**: Within 48 hours +- **Initial assessment**: Within 7 days +- **Patch release**: Within 30 days (critical vulnerabilities may be expedited) + +## Disclosure Policy + +- Do not disclose the vulnerability publicly until a patch has been released +- We will credit reporters in the security advisory (unless anonymity is requested) +- We do not pursue legal action against good-faith security researchers + +## Scope + +This policy covers all code in the AIngle repository, including: +- All crates under `crates/` +- Build scripts and CI/CD configurations +- Documentation that may expose sensitive implementation details + +## Cryptographic Components + +AIngle uses the following cryptographic libraries: +- `blake3` — Content hashing +- `ed25519-dalek` — Digital signatures +- `rustls` — TLS connections +- Custom zero-knowledge proof implementations (Pedersen, Schnorr, Bulletproofs) + +Issues in these components are considered high priority. + +## Contact + +Apilium Technologies OÜ +security@apilium.com +https://apilium.com/en/security diff --git a/crates/adk/Cargo.toml b/crates/adk/Cargo.toml index 8ad48a8c..862ea24b 100644 --- a/crates/adk/Cargo.toml +++ b/crates/adk/Cargo.toml @@ -2,7 +2,7 @@ name = "adk" version = "0.0.1" description = "The AIngle ADK" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/adk" diff --git a/crates/adk/src/adk.rs b/crates/adk/src/adk.rs index e9cfac89..b9cffd73 100644 --- a/crates/adk/src/adk.rs +++ b/crates/adk/src/adk.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; #[cfg(feature = "mock")] diff --git a/crates/adk/src/capability.rs b/crates/adk/src/capability.rs index f08460c6..0e2c99fb 100644 --- a/crates/adk/src/capability.rs +++ b/crates/adk/src/capability.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Create capability claims on the local source chain. diff --git a/crates/adk/src/chain.rs b/crates/adk/src/chain.rs index c8a49525..8ed634c8 100644 --- a/crates/adk/src/chain.rs +++ b/crates/adk/src/chain.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Query the _headers_ of a remote agent's chain. diff --git a/crates/adk/src/ed25519.rs b/crates/adk/src/ed25519.rs index aee8d292..872ccdbd 100644 --- a/crates/adk/src/ed25519.rs +++ b/crates/adk/src/ed25519.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Sign something that is serializable using the private key for the passed public key. diff --git a/crates/adk/src/entry.rs b/crates/adk/src/entry.rs index 1dc4550a..db1a6743 100644 --- a/crates/adk/src/entry.rs +++ b/crates/adk/src/entry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// General function that can create any entry type. diff --git a/crates/adk/src/graph.rs b/crates/adk/src/graph.rs index 96f389d9..57d84b47 100644 --- a/crates/adk/src/graph.rs +++ b/crates/adk/src/graph.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use aingle_zome_types::graph::{ GraphQueryInput, GraphQueryOutput, GraphStoreInput, GraphStoreOutput, ObjectValue, diff --git a/crates/adk/src/hash_path.rs b/crates/adk/src/hash_path.rs index bd5cc2b0..0adde593 100644 --- a/crates/adk/src/hash_path.rs +++ b/crates/adk/src/hash_path.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// The anchor pattern implemented in terms of [ `path::Path` ] /// /// The anchor pattern predates the path crate. diff --git a/crates/adk/src/hash_path/anchor.rs b/crates/adk/src/hash_path/anchor.rs index 90edef88..09aba782 100644 --- a/crates/adk/src/hash_path/anchor.rs +++ b/crates/adk/src/hash_path/anchor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::hash_path::path::Component; use crate::hash_path::path::Path; use crate::prelude::*; diff --git a/crates/adk/src/hash_path/path.rs b/crates/adk/src/hash_path/path.rs index 06b0ea15..549562a8 100644 --- a/crates/adk/src/hash_path/path.rs +++ b/crates/adk/src/hash_path/path.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::hash_path::shard::ShardStrategy; use crate::hash_path::shard::SHARDEND; use crate::prelude::*; diff --git a/crates/adk/src/hash_path/shard.rs b/crates/adk/src/hash_path/shard.rs index 559506c7..22230163 100644 --- a/crates/adk/src/hash_path/shard.rs +++ b/crates/adk/src/hash_path/shard.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::hash_path::path::Component; use crate::hash_path::path::Path; use std::str::FromStr; diff --git a/crates/adk/src/info.rs b/crates/adk/src/info.rs index 83160d63..a97efcc7 100644 --- a/crates/adk/src/info.rs +++ b/crates/adk/src/info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Trivial wrapper for `__agent_info` host function. diff --git a/crates/adk/src/lib.rs b/crates/adk/src/lib.rs index 3ab162bd..ab91cb33 100644 --- a/crates/adk/src/lib.rs +++ b/crates/adk/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] #![allow(rustdoc::broken_intra_doc_links)] diff --git a/crates/adk/src/link.rs b/crates/adk/src/link.rs index 78bd5111..c7751140 100644 --- a/crates/adk/src/link.rs +++ b/crates/adk/src/link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Create a link from a base entry to a target entry, with an optional tag. diff --git a/crates/adk/src/map_extern.rs b/crates/adk/src/map_extern.rs index d6804e3d..a6bc730d 100644 --- a/crates/adk/src/map_extern.rs +++ b/crates/adk/src/map_extern.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Hides away the gross bit where we hook up integer pointers to length-prefixed guest memory diff --git a/crates/adk/src/memory.rs b/crates/adk/src/memory.rs index 9cf6a7c7..6c845de2 100644 --- a/crates/adk/src/memory.rs +++ b/crates/adk/src/memory.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use aingle_zome_types::graph::{ MemoryRecallInput, MemoryRecallOutput, MemoryRememberInput, MemoryRememberOutput, diff --git a/crates/adk/src/p2p.rs b/crates/adk/src/p2p.rs index f1b4f35e..e31a5bb7 100644 --- a/crates/adk/src/p2p.rs +++ b/crates/adk/src/p2p.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// # Call diff --git a/crates/adk/src/prelude.rs b/crates/adk/src/prelude.rs index d0a63a20..203afa0a 100644 --- a/crates/adk/src/prelude.rs +++ b/crates/adk/src/prelude.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub use crate::adk::*; pub use crate::app_entry; pub use crate::capability::create_cap_claim; diff --git a/crates/adk/src/random.rs b/crates/adk/src/random.rs index 09f17c8b..9473c245 100644 --- a/crates/adk/src/random.rs +++ b/crates/adk/src/random.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Get N cryptographically strong random bytes. diff --git a/crates/adk/src/time.rs b/crates/adk/src/time.rs index 1a252c7b..776022b3 100644 --- a/crates/adk/src/time.rs +++ b/crates/adk/src/time.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Current system time from the host. diff --git a/crates/adk/src/trace.rs b/crates/adk/src/trace.rs index ba0d0739..e3621549 100644 --- a/crates/adk/src/trace.rs +++ b/crates/adk/src/trace.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use std::fmt::Write; use std::sync::atomic::AtomicUsize; diff --git a/crates/adk/src/x_salsa20_poly1305.rs b/crates/adk/src/x_salsa20_poly1305.rs index ed0d85b1..5842db53 100644 --- a/crates/adk/src/x_salsa20_poly1305.rs +++ b/crates/adk/src/x_salsa20_poly1305.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// Generate a new x25519 keypair in lair from entropy. diff --git a/crates/adk_derive/Cargo.toml b/crates/adk_derive/Cargo.toml index 552c9dc8..d727f886 100644 --- a/crates/adk_derive/Cargo.toml +++ b/crates/adk_derive/Cargo.toml @@ -2,7 +2,7 @@ name = "adk_derive" version = "0.0.1" description = "derive macros for the aingle adk" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/adk_derive" diff --git a/crates/adk_derive/src/lib.rs b/crates/adk_derive/src/lib.rs index e3397644..b1c0173d 100644 --- a/crates/adk_derive/src/lib.rs +++ b/crates/adk_derive/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![crate_type = "proc-macro"] use proc_macro::TokenStream; diff --git a/crates/ai/Cargo.toml b/crates/ai/Cargo.toml index ffb9e2ce..82602a85 100644 --- a/crates/ai/Cargo.toml +++ b/crates/ai/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_cli" version = "0.0.1" description = "Command-line interface for AIngle distributed ledger" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_cli" diff --git a/crates/ai/src/bin/ai.rs b/crates/ai/src/bin/ai.rs index 362e635f..81e4236b 100644 --- a/crates/ai/src/bin/ai.rs +++ b/crates/ai/src/bin/ai.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_cli as ai; use structopt::StructOpt; diff --git a/crates/ai/src/lib.rs b/crates/ai/src/lib.rs index 8adf26b1..e4617838 100644 --- a/crates/ai/src/lib.rs +++ b/crates/ai/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![warn(missing_docs)] //! A library and CLI to help create, run and interact with aingle conductor setups. diff --git a/crates/ai_bundle/Cargo.toml b/crates/ai_bundle/Cargo.toml index 71569d7c..c2b2fead 100644 --- a/crates/ai_bundle/Cargo.toml +++ b/crates/ai_bundle/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_cli_bundle" version = "0.0.1" description = "SAF and hApp bundling functionality for the `ai` AIngle CLI utility" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_cli_bundle" @@ -33,7 +33,7 @@ thiserror = "2.0" tokio = { version = "1", features = [ "full" ] } [dev-dependencies] -assert_cmd = "2.0" +assert_cmd = "2.1" matches = "0.1" predicates = "1.0" tempdir = "0.3" diff --git a/crates/ai_bundle/src/bin/ai-app.rs b/crates/ai_bundle/src/bin/ai-app.rs index eceb3737..ea9a1940 100644 --- a/crates/ai_bundle/src/bin/ai-app.rs +++ b/crates/ai_bundle/src/bin/ai-app.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_cli_bundle::AinAppBundle; use structopt::StructOpt; diff --git a/crates/ai_bundle/src/bin/ai-saf.rs b/crates/ai_bundle/src/bin/ai-saf.rs index ca3af455..b2d1772d 100644 --- a/crates/ai_bundle/src/bin/ai-saf.rs +++ b/crates/ai_bundle/src/bin/ai-saf.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_cli_bundle::AinSafBundle; use structopt::StructOpt; diff --git a/crates/ai_bundle/src/cli.rs b/crates/ai_bundle/src/cli.rs index 75c3c27c..44bdc6b8 100644 --- a/crates/ai_bundle/src/cli.rs +++ b/crates/ai_bundle/src/cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![forbid(missing_docs)] //! Binary `ai-saf` command executable. diff --git a/crates/ai_bundle/src/error.rs b/crates/ai_bundle/src/error.rs index 413ed21c..27e7bdbb 100644 --- a/crates/ai_bundle/src/error.rs +++ b/crates/ai_bundle/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::PathBuf; use aingle_middleware_bytes::SerializedBytesError; diff --git a/crates/ai_bundle/src/init.rs b/crates/ai_bundle/src/init.rs index 25022562..8877a999 100644 --- a/crates/ai_bundle/src/init.rs +++ b/crates/ai_bundle/src/init.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::io::Write; use std::{io, path::PathBuf}; diff --git a/crates/ai_bundle/src/lib.rs b/crates/ai_bundle/src/lib.rs index f34e6237..5e28d20b 100644 --- a/crates/ai_bundle/src/lib.rs +++ b/crates/ai_bundle/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + mod cli; mod error; mod init; diff --git a/crates/ai_bundle/src/packing.rs b/crates/ai_bundle/src/packing.rs index fd359e42..74ae3a0e 100644 --- a/crates/ai_bundle/src/packing.rs +++ b/crates/ai_bundle/src/packing.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![forbid(missing_docs)] //! Defines the CLI commands for packing/unpacking both SAF and hApp bundles diff --git a/crates/ai_bundle/tests/test_cli.rs b/crates/ai_bundle/tests/test_cli.rs index 4c068cae..e91e65a2 100644 --- a/crates/ai_bundle/tests/test_cli.rs +++ b/crates/ai_bundle/tests/test_cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_types::prelude::*; use aingle_util::ffs; use assert_cmd::prelude::*; diff --git a/crates/ai_fixt/Cargo.toml b/crates/ai_fixt/Cargo.toml index f0551a07..13b766f5 100644 --- a/crates/ai_fixt/Cargo.toml +++ b/crates/ai_fixt/Cargo.toml @@ -2,7 +2,7 @@ name = "ai_fixt" version = "0.0.1" description = "minimum viable fixtures" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/ai_fixt" @@ -17,7 +17,7 @@ lazy_static = "1.4" parking_lot = "0.12" paste = "1.0" rand = "0.9" -rand_core = "0.6" +rand_core = "0.9" serde = { version = "1.0", features = [ "derive" ] } strum = "0.26" strum_macros = "0.26" diff --git a/crates/ai_fixt/src/bool.rs b/crates/ai_fixt/src/bool.rs index 6f161ab7..682b2c69 100644 --- a/crates/ai_fixt/src/bool.rs +++ b/crates/ai_fixt/src/bool.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; fixturator!(bool, false, crate::rng().random(), { diff --git a/crates/ai_fixt/src/bytes.rs b/crates/ai_fixt/src/bytes.rs index f03d1790..95d71517 100644 --- a/crates/ai_fixt/src/bytes.rs +++ b/crates/ai_fixt/src/bytes.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use rand::Rng; diff --git a/crates/ai_fixt/src/lib.rs b/crates/ai_fixt/src/lib.rs index e6d2f085..7f92b5ff 100644 --- a/crates/ai_fixt/src/lib.rs +++ b/crates/ai_fixt/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::assign_op_pattern)] #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] diff --git a/crates/ai_fixt/src/number.rs b/crates/ai_fixt/src/number.rs index 59fe8981..80bbd506 100644 --- a/crates/ai_fixt/src/number.rs +++ b/crates/ai_fixt/src/number.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use rand::seq::IndexedRandom; diff --git a/crates/ai_fixt/src/prelude.rs b/crates/ai_fixt/src/prelude.rs index 584f98ee..68b0ac6f 100644 --- a/crates/ai_fixt/src/prelude.rs +++ b/crates/ai_fixt/src/prelude.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub use crate::basic_test; pub use crate::bool::BoolFixturator; pub use crate::bytes::Bytes; diff --git a/crates/ai_fixt/src/rng.rs b/crates/ai_fixt/src/rng.rs index 523b1431..337883ec 100644 --- a/crates/ai_fixt/src/rng.rs +++ b/crates/ai_fixt/src/rng.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Seedable random number generator to be used in all fixturator randomness //! //! In tests, when an unpredictable value causes a test failure, it's important to diff --git a/crates/ai_fixt/src/serialized_bytes.rs b/crates/ai_fixt/src/serialized_bytes.rs index 619ed7b4..9862df41 100644 --- a/crates/ai_fixt/src/serialized_bytes.rs +++ b/crates/ai_fixt/src/serialized_bytes.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! @todo move all this out to the serialized bytes crate use crate::prelude::*; use aingle_middleware_bytes::prelude::*; diff --git a/crates/ai_fixt/src/string.rs b/crates/ai_fixt/src/string.rs index dfe27ad3..72cb03f3 100644 --- a/crates/ai_fixt/src/string.rs +++ b/crates/ai_fixt/src/string.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use rand::Rng; diff --git a/crates/ai_fixt/src/unit.rs b/crates/ai_fixt/src/unit.rs index 6edff0df..02545ddb 100644 --- a/crates/ai_fixt/src/unit.rs +++ b/crates/ai_fixt/src/unit.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; type Unit = (); diff --git a/crates/ai_fixt/test/Cargo.toml b/crates/ai_fixt/test/Cargo.toml index 3fcfaad7..3fa66c97 100644 --- a/crates/ai_fixt/test/Cargo.toml +++ b/crates/ai_fixt/test/Cargo.toml @@ -2,7 +2,7 @@ name = "ai_fixt_test" version = "0.0.1" description = "tests the fixturator macros in an external crate" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/ai_fixt_test" diff --git a/crates/ai_fixt/test/src/lib.rs b/crates/ai_fixt/test/src/lib.rs index 51938f8f..592cd0ff 100644 --- a/crates/ai_fixt/test/src/lib.rs +++ b/crates/ai_fixt/test/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// this is in a separate crate from the ai_fixt crate to show that we've addressed the orphan rule /// and other issues e.g. pub/private data use ::ai_fixt::prelude::*; diff --git a/crates/ai_hash/Cargo.toml b/crates/ai_hash/Cargo.toml index 9b3c3bf0..9c16772d 100644 --- a/crates/ai_hash/Cargo.toml +++ b/crates/ai_hash/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "ai_hash" -version = "0.0.2" +version = "0.4.2" authors = ["Apilium Technologies "] keywords = [ "aingle", "ai", "hash", "blake", "blake2b" ] categories = [ "cryptography" ] edition = "2018" description = "hashing helpers supporting sgd sharding" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/ai_hash" @@ -16,13 +16,13 @@ serde = "1" serde_bytes = "0.11" arbitrary = {version = "1.0", optional = true} -base64 = {version = "0.21", optional = true} +base64 = {version = "0.22", optional = true} blake2b_simd = {version = "0.5.10", optional = true} derive_more = { version = "0.99", optional = true } ai_fixt = { version = ">=0.0.1", path = "../ai_fixt", optional = true } aingle_middleware_bytes = {version = "=0.0.3", optional = true } rand = {version = "0.9", optional = true} -rusqlite = { version = "0.25", optional = true } +rusqlite = { version = "0.32", optional = true } tracing = { version = "0.1", optional = true} thiserror = "2.0" diff --git a/crates/ai_hash/src/aliases.rs b/crates/ai_hash/src/aliases.rs index 209bc9a4..70940397 100644 --- a/crates/ai_hash/src/aliases.rs +++ b/crates/ai_hash/src/aliases.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Type aliases for the various concrete AiHash types use crate::hash_type; diff --git a/crates/ai_hash/src/encode.rs b/crates/ai_hash/src/encode.rs index 7154b46d..4592b71a 100644 --- a/crates/ai_hash/src/encode.rs +++ b/crates/ai_hash/src/encode.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::assert_length; use crate::error::AiHashError; use crate::AiHash; diff --git a/crates/ai_hash/src/encode_raw.rs b/crates/ai_hash/src/encode_raw.rs index 2445fd8d..ea898110 100644 --- a/crates/ai_hash/src/encode_raw.rs +++ b/crates/ai_hash/src/encode_raw.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::AiHash; use crate::HashType; diff --git a/crates/ai_hash/src/error.rs b/crates/ai_hash/src/error.rs index d2610d2f..349b5e52 100644 --- a/crates/ai_hash/src/error.rs +++ b/crates/ai_hash/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AiHash Error Type. use crate::AI_HASH_PREFIX_LEN; diff --git a/crates/ai_hash/src/fixt.rs b/crates/ai_hash/src/fixt.rs index 4fcb0952..a0b30313 100644 --- a/crates/ai_hash/src/fixt.rs +++ b/crates/ai_hash/src/fixt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use crate::encode::ai_sgd_location_bytes; diff --git a/crates/ai_hash/src/has_hash.rs b/crates/ai_hash/src/has_hash.rs index 2f421ce4..3c5f0658 100644 --- a/crates/ai_hash/src/has_hash.rs +++ b/crates/ai_hash/src/has_hash.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Definition of the HasHash trait use crate::AiHash; diff --git a/crates/ai_hash/src/hash.rs b/crates/ai_hash/src/hash.rs index a8cc2b01..6585b77f 100644 --- a/crates/ai_hash/src/hash.rs +++ b/crates/ai_hash/src/hash.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines the AiHash type, used for all hashes in AIngle. //! //! AiHashes come in a variety of types. See the `hash_type::primitive` diff --git a/crates/ai_hash/src/hash_b64.rs b/crates/ai_hash/src/hash_b64.rs index 223c8b28..48e566bc 100644 --- a/crates/ai_hash/src/hash_b64.rs +++ b/crates/ai_hash/src/hash_b64.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Implements base-64 serialization for AiHashes //! //! It's already the case that AiHash can be deserialized from either a byte diff --git a/crates/ai_hash/src/hash_ext.rs b/crates/ai_hash/src/hash_ext.rs index 13eca5c6..5bc0c0c8 100644 --- a/crates/ai_hash/src/hash_ext.rs +++ b/crates/ai_hash/src/hash_ext.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::assert_length; use crate::encode; use crate::hash_type; diff --git a/crates/ai_hash/src/hash_type.rs b/crates/ai_hash/src/hash_type.rs index 418829d1..42ae43b1 100644 --- a/crates/ai_hash/src/hash_type.rs +++ b/crates/ai_hash/src/hash_type.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines the prefixes for the various HashTypes, as well as the traits //! which unify them diff --git a/crates/ai_hash/src/hash_type/composite.rs b/crates/ai_hash/src/hash_type/composite.rs index 7e991490..63a6eaba 100644 --- a/crates/ai_hash/src/hash_type/composite.rs +++ b/crates/ai_hash/src/hash_type/composite.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::error::AiHashError; use std::convert::TryInto; diff --git a/crates/ai_hash/src/hash_type/primitive.rs b/crates/ai_hash/src/hash_type/primitive.rs index 5b2d292c..0f090684 100644 --- a/crates/ai_hash/src/hash_type/primitive.rs +++ b/crates/ai_hash/src/hash_type/primitive.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::error::AiHashError; use crate::hash_type; diff --git a/crates/ai_hash/src/hashable_content.rs b/crates/ai_hash/src/hashable_content.rs index 55637e75..9864c040 100644 --- a/crates/ai_hash/src/hashable_content.rs +++ b/crates/ai_hash/src/hashable_content.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::HashType; use aingle_middleware_bytes::prelude::*; diff --git a/crates/ai_hash/src/hashed.rs b/crates/ai_hash/src/hashed.rs index 4858cef4..a25780ba 100644 --- a/crates/ai_hash/src/hashed.rs +++ b/crates/ai_hash/src/hashed.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::AiHashOf; use crate::HasHash; use crate::HashableContent; diff --git a/crates/ai_hash/src/lib.rs b/crates/ai_hash/src/lib.rs index def635c1..6608008d 100644 --- a/crates/ai_hash/src/lib.rs +++ b/crates/ai_hash/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines AiHash and its various HashTypes #![deny(missing_docs)] diff --git a/crates/ai_hash/src/ser.rs b/crates/ai_hash/src/ser.rs index 3c5bcdd7..eeb40b24 100644 --- a/crates/ai_hash/src/ser.rs +++ b/crates/ai_hash/src/ser.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines the serialization rules for AiHashes use crate::AiHash; diff --git a/crates/ai_hash/src/tests.rs b/crates/ai_hash/src/tests.rs index a338a81e..ba3ae575 100644 --- a/crates/ai_hash/src/tests.rs +++ b/crates/ai_hash/src/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![cfg(test)] use crate::{HashableContent, AiHashed}; diff --git a/crates/ai_sandbox/Cargo.toml b/crates/ai_sandbox/Cargo.toml index 2c5eeae5..c5e13314 100644 --- a/crates/ai_sandbox/Cargo.toml +++ b/crates/ai_sandbox/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_cli_sandbox" version = "0.0.1" description = "Sandbox environment for AIngle development and testing" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_cli_sandbox" @@ -36,6 +36,6 @@ url2 = "0.0.6" walkdir = "2" [dev-dependencies] -assert_cmd = "2.0" +assert_cmd = "2.1" matches = "0.1" portpicker = "0.1.0" diff --git a/crates/ai_sandbox/examples/setup_5.rs b/crates/ai_sandbox/examples/setup_5.rs index 8ee21e9e..9116ff95 100644 --- a/crates/ai_sandbox/examples/setup_5.rs +++ b/crates/ai_sandbox/examples/setup_5.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::PathBuf; use ai_sandbox::calls::ActivateApp; diff --git a/crates/ai_sandbox/src/bin/ai-sandbox.rs b/crates/ai_sandbox/src/bin/ai-sandbox.rs index 29317e63..9bfddd14 100644 --- a/crates/ai_sandbox/src/bin/ai-sandbox.rs +++ b/crates/ai_sandbox/src/bin/ai-sandbox.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use structopt::StructOpt; #[tokio::main] diff --git a/crates/ai_sandbox/src/bundles.rs b/crates/ai_sandbox/src/bundles.rs index d85aa401..bd1762b8 100644 --- a/crates/ai_sandbox/src/bundles.rs +++ b/crates/ai_sandbox/src/bundles.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for working with saf files. use std::path::Path; use std::path::PathBuf; diff --git a/crates/ai_sandbox/src/calls.rs b/crates/ai_sandbox/src/calls.rs index 16cba2dc..d4d33eb1 100644 --- a/crates/ai_sandbox/src/calls.rs +++ b/crates/ai_sandbox/src/calls.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for making [`AdminRequest`]s to the admin api. //! //! This module is designed for use in a CLI so it is more simplified diff --git a/crates/ai_sandbox/src/cli.rs b/crates/ai_sandbox/src/cli.rs index 038e3daf..fccd3b5a 100644 --- a/crates/ai_sandbox/src/cli.rs +++ b/crates/ai_sandbox/src/cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Definitions of StructOpt options for use in the CLI use crate::cmds::*; diff --git a/crates/ai_sandbox/src/cmds.rs b/crates/ai_sandbox/src/cmds.rs index 0599e9be..9fed6559 100644 --- a/crates/ai_sandbox/src/cmds.rs +++ b/crates/ai_sandbox/src/cmds.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::PathBuf; use aingle_p2p::kitsune_p2p::KitsuneP2pConfig; diff --git a/crates/ai_sandbox/src/config.rs b/crates/ai_sandbox/src/config.rs index 4e92a3ba..c016e562 100644 --- a/crates/ai_sandbox/src/config.rs +++ b/crates/ai_sandbox/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for creating, reading and writing [`ConductorConfig`]s. use std::path::PathBuf; diff --git a/crates/ai_sandbox/src/generate.rs b/crates/ai_sandbox/src/generate.rs index 8cb9745b..2fb7dfb6 100644 --- a/crates/ai_sandbox/src/generate.rs +++ b/crates/ai_sandbox/src/generate.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for generating new directories and [`ConductorConfig`]. use std::path::PathBuf; diff --git a/crates/ai_sandbox/src/lib.rs b/crates/ai_sandbox/src/lib.rs index 24579667..e609e703 100644 --- a/crates/ai_sandbox/src/lib.rs +++ b/crates/ai_sandbox/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![warn(missing_docs)] //! A library and CLI to help create, run and interact with aingle conductor sandboxes. diff --git a/crates/ai_sandbox/src/ports.rs b/crates/ai_sandbox/src/ports.rs index 394c4fa9..32892217 100644 --- a/crates/ai_sandbox/src/ports.rs +++ b/crates/ai_sandbox/src/ports.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for working with websockets and ports. use std::path::PathBuf; use std::sync::Arc; diff --git a/crates/ai_sandbox/src/run.rs b/crates/ai_sandbox/src/run.rs index 6aed5703..811ee916 100644 --- a/crates/ai_sandbox/src/run.rs +++ b/crates/ai_sandbox/src/run.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for running the conductor. use std::path::Path; use std::{path::PathBuf, process::Stdio}; diff --git a/crates/ai_sandbox/src/sandbox.rs b/crates/ai_sandbox/src/sandbox.rs index 844b1fd3..d1259a51 100644 --- a/crates/ai_sandbox/src/sandbox.rs +++ b/crates/ai_sandbox/src/sandbox.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Common use sandboxes with lots of default choices. use std::path::Path; use std::path::PathBuf; diff --git a/crates/ai_sandbox/src/save.rs b/crates/ai_sandbox/src/save.rs index ac282dc1..66d6d73b 100644 --- a/crates/ai_sandbox/src/save.rs +++ b/crates/ai_sandbox/src/save.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Manage persistence of sandboxes //! This module gives basic helpers to save / load your sandboxes //! in a `.ai` file. diff --git a/crates/ai_sandbox/tests/cli.rs b/crates/ai_sandbox/tests/cli.rs index 980b87df..80793bc4 100644 --- a/crates/ai_sandbox/tests/cli.rs +++ b/crates/ai_sandbox/tests/cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::future::Future; use std::sync::Arc; diff --git a/crates/aingle/Cargo.toml b/crates/aingle/Cargo.toml index 9f47c5cc..f1fe24cd 100644 --- a/crates/aingle/Cargo.toml +++ b/crates/aingle/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle" -version = "0.0.100" +version = "0.6.0" description = "AIngle, a framework for distributed applications" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle" @@ -12,33 +12,33 @@ edition = "2018" [dependencies] anyhow = "1.0" async-trait = "0.1" -base64 = "0.21" +base64 = "0.22" byteorder = "1.3.4" cfg-if = "0.1" chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } derive_more = "0.99.3" directories = "2.0.2" either = "1.5.0" -fallible-iterator = "0.2.0" +fallible-iterator = "0.3.0" ai_fixt = { version = "^0.0.1", path = "../ai_fixt" } -futures = "0.3.1" +futures = "0.3.32" ghost_actor = "0.3.0-alpha.1" ai_hash = { version = ">=0.0.1", path = "../ai_hash", features = ["full"] } aingle_cascade = { version = "0.0.1", path = "../aingle_cascade" } aingle_conductor_api = { version = "0.0.1", path = "../aingle_conductor_api" } -aingle_ai = { version = "0.1.0", path = "../aingle_ai", optional = true } +aingle_ai = { version = "0.6", path = "../aingle_ai", optional = true } aingle_keystore = { version = "0.0.1", path = "../aingle_keystore" } aingle_p2p = { version = "0.0.1", path = "../aingle_p2p" } aingle_sqlite = { version = "0.0.1", path = "../aingle_sqlite" } aingle_middleware_bytes = "=0.0.3" aingle_state = { version = "0.0.1", path = "../aingle_state" } aingle_types = { version = "0.0.1", path = "../aingle_types" } -aingle_cortex = { version = "0.2.0", path = "../aingle_cortex", default-features = false, features = ["rest"] } +aingle_cortex = { version = "0.6", path = "../aingle_cortex", default-features = false, features = ["rest"] } aingle_wasmer_host = "0.0.1" aingle_websocket = { version = "0.0.1", path = "../aingle_websocket" } -wasmer = "=6.0.0" +wasmer = "=7.0.1" aingle_zome_types = { version = ">=0.0.1", path = "../aingle_zome_types" } -human-panic = "2.0.4" +human-panic = "2.0.6" kitsune_p2p = { version = "0.0.1", path = "../kitsune_p2p/kitsune_p2p" } kitsune_p2p_types = { version = "0.0.1", path = "../kitsune_p2p/types" } lazy_static = "1.4.0" @@ -55,7 +55,7 @@ rand = "0.9" ring = "0.17" # Ensure rustls uses ring crypto provider for TLS rustls = { workspace = true } -rusqlite = { version = "0.25" } +rusqlite = { version = "0.32" } serde = { version = "1.0", features = [ "derive" ] } serde_json = { version = "1.0", features = [ "preserve_order" ] } serde_yml = "0.0.12" @@ -67,7 +67,7 @@ thiserror = "2.0" tokio = { version = "1", features = [ "full" ] } tokio-stream = "0.1" aingle_util = { version = "0.0.1", path = "../aingle_util" } -toml = "0.5.6" +toml = "0.9.10" tracing = "0.1" tracing-futures = "0.2.4" tracing-subscriber = "0.3" @@ -91,11 +91,11 @@ sd-notify = "0.3.0" [dev-dependencies] anyhow = "1.0" -assert_cmd = "2.0" +assert_cmd = "2.1" criterion = "0.5" maplit = "1" -pretty_assertions = "0.7.2" -serial_test = "3.0" +pretty_assertions = "1.4" +serial_test = "3.4" test-case = "3.3" # Dependencies for test_utils: keep in sync with above @@ -155,5 +155,5 @@ db-encryption = ['aingle_sqlite/db-encryption'] # Incompatible with "db-encryption" no-deps = ['aingle_sqlite/no-deps'] -# Enable AI integration layer (Titans Memory, Nested Learning, HOPE Agents) +# Enable AI integration layer (Ineru, Nested Learning, Kaneru) ai-integration = ['aingle_ai'] diff --git a/crates/aingle/benches/bench.rs b/crates/aingle/benches/bench.rs index ff6c0ce7..da61db9b 100644 --- a/crates/aingle/benches/bench.rs +++ b/crates/aingle/benches/bench.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ::ai_fixt::prelude::*; use adk::prelude::*; use ai_hash::fixt::AgentPubKeyFixturator; diff --git a/crates/aingle/benches/consistency.rs b/crates/aingle/benches/consistency.rs index 211421d5..2ba1b482 100644 --- a/crates/aingle/benches/consistency.rs +++ b/crates/aingle/benches/consistency.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use criterion::criterion_group; use criterion::criterion_main; use criterion::BenchmarkId; diff --git a/crates/aingle/src/bin/aingle/main.rs b/crates/aingle/src/bin/aingle/main.rs index 853ce988..de43ff87 100644 --- a/crates/aingle/src/bin/aingle/main.rs +++ b/crates/aingle/src/bin/aingle/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle::conductor::config::ConductorConfig; use aingle::conductor::interactive; use aingle::conductor::manager::handle_shutdown; @@ -17,7 +20,17 @@ const ERROR_CODE: i32 = 42; const MAGIC_CONDUCTOR_READY_STRING: &str = "Conductor ready."; #[derive(Debug, StructOpt)] -#[structopt(name = "aingle", about = "The AIngle Conductor.")] +#[structopt( + name = "aingle", + about = "The AIngle Conductor.", + version = env!("CARGO_PKG_VERSION"), + long_version = concat!( + env!("CARGO_PKG_VERSION"), "\n", + "Copyright 2019-2026 Apilium Technologies OÜ\n", + "License: Apache-2.0 OR Commercial\n", + "https://github.com/ApiliumCode/aingle" + ) +)] struct Opt { #[structopt( long, diff --git a/crates/aingle/src/conductor.rs b/crates/aingle/src/conductor.rs index 5bd11df6..0f56bf1d 100644 --- a/crates/aingle/src/conductor.rs +++ b/crates/aingle/src/conductor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A Conductor manages interactions between its contained [Cell]s, as well as //! interactions with the outside world. It is primarily a mediator of messages. //! diff --git a/crates/aingle/src/conductor/ai_service.rs b/crates/aingle/src/conductor/ai_service.rs index c456b4d0..70880665 100644 --- a/crates/aingle/src/conductor/ai_service.rs +++ b/crates/aingle/src/conductor/ai_service.rs @@ -1,7 +1,10 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AI Service for the AIngle Conductor //! //! This module provides AI-powered validation and consensus features -//! using the aingle_ai crate (Titans Memory, Nested Learning, HOPE Agents). +//! using the aingle_ai crate (Ineru, Nested Learning, Kaneru). #[cfg(feature = "ai-integration")] use aingle_ai::{emergent::AiLayer, AiConfig, AiTransaction, ConsensusLevel, ValidationPrediction}; @@ -47,7 +50,7 @@ use tracing::{debug, info, trace, warn}; /// AI Service for intelligent validation and consensus #[cfg(feature = "ai-integration")] pub struct AiService { - /// Unified AI layer (Titans + Nested Learning + Emergent) + /// Unified AI layer (Ineru + Nested Learning + Emergent) ai_layer: Arc>, /// Configuration @@ -225,8 +228,8 @@ impl AiService { let ai_layer = self.ai_layer.read(); let stats = ai_layer.stats(); AiLayerStatsSnapshot { - titans_short_term_size: stats.titans_short_term_size, - titans_long_term_size: stats.titans_long_term_size, + ineru_short_term_size: stats.ineru_short_term_size, + ineru_long_term_size: stats.ineru_long_term_size, nested_tx_count: stats.nested_tx_count, nested_block_count: stats.nested_block_count, } @@ -284,10 +287,10 @@ impl AiService { /// Snapshot of AI layer statistics #[derive(Debug, Clone)] pub struct AiLayerStatsSnapshot { - /// Titans short-term memory size - pub titans_short_term_size: usize, - /// Titans long-term memory size - pub titans_long_term_size: usize, + /// Ineru short-term memory size + pub ineru_short_term_size: usize, + /// Ineru long-term memory size + pub ineru_long_term_size: usize, /// Nested learning transaction count pub nested_tx_count: u64, /// Nested learning block count diff --git a/crates/aingle/src/conductor/api.rs b/crates/aingle/src/conductor/api.rs index 2af22aa1..63498f8a 100644 --- a/crates/aingle/src/conductor/api.rs +++ b/crates/aingle/src/conductor/api.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! Defines the three Conductor APIs by which other code can communicate diff --git a/crates/aingle/src/conductor/api/api_cell.rs b/crates/aingle/src/conductor/api/api_cell.rs index 44318602..51ab2fe4 100644 --- a/crates/aingle/src/conductor/api/api_cell.rs +++ b/crates/aingle/src/conductor/api/api_cell.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The CellConductorApi allows Cells to talk to their Conductor use std::sync::Arc; diff --git a/crates/aingle/src/conductor/api/api_external.rs b/crates/aingle/src/conductor/api/api_external.rs index 58d9d2ff..13616784 100644 --- a/crates/aingle/src/conductor/api/api_external.rs +++ b/crates/aingle/src/conductor/api/api_external.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::conductor::interface::error::InterfaceResult; use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle/src/conductor/api/api_external/admin_interface.rs b/crates/aingle/src/conductor/api/api_external/admin_interface.rs index 6071d54a..b4e74a74 100644 --- a/crates/aingle/src/conductor/api/api_external/admin_interface.rs +++ b/crates/aingle/src/conductor/api/api_external/admin_interface.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::InterfaceApi; use crate::conductor::api::error::ConductorApiError; use crate::conductor::api::error::ConductorApiResult; diff --git a/crates/aingle/src/conductor/api/api_external/app_interface.rs b/crates/aingle/src/conductor/api/api_external/app_interface.rs index 8a1168eb..a2b8d87b 100644 --- a/crates/aingle/src/conductor/api/api_external/app_interface.rs +++ b/crates/aingle/src/conductor/api/api_external/app_interface.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::InterfaceApi; use crate::conductor::api::error::ConductorApiResult; use crate::conductor::api::error::ExternalApiWireError; diff --git a/crates/aingle/src/conductor/api/error.rs b/crates/aingle/src/conductor/api/error.rs index e1fb4553..1f2c69fc 100644 --- a/crates/aingle/src/conductor/api/error.rs +++ b/crates/aingle/src/conductor/api/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Errors occurring during a [CellConductorApi] or [InterfaceApi] call use crate::conductor::error::ConductorError; diff --git a/crates/aingle/src/conductor/api/mock.rs b/crates/aingle/src/conductor/api/mock.rs index b5e319df..0bd371f4 100644 --- a/crates/aingle/src/conductor/api/mock.rs +++ b/crates/aingle/src/conductor/api/mock.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use super::CellConductorApiT; diff --git a/crates/aingle/src/conductor/cell.rs b/crates/aingle/src/conductor/cell.rs index 75d03920..c286d494 100644 --- a/crates/aingle/src/conductor/cell.rs +++ b/crates/aingle/src/conductor/cell.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A Cell is an "instance" of AIngle SAF. //! //! It combines an AgentPubKey with a Saf to create a SourceChain, upon which diff --git a/crates/aingle/src/conductor/cell/error.rs b/crates/aingle/src/conductor/cell/error.rs index ce302fd0..62aa765a 100644 --- a/crates/aingle/src/conductor/cell/error.rs +++ b/crates/aingle/src/conductor/cell/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::conductor::entry_def_store::error::EntryDefStoreError; use crate::conductor::{api::error::ConductorApiError, error::ConductorError}; use crate::core::ribosome::error::RibosomeError; diff --git a/crates/aingle/src/conductor/cell/gossip_test.rs b/crates/aingle/src/conductor/cell/gossip_test.rs index dbd18263..d773423a 100644 --- a/crates/aingle/src/conductor/cell/gossip_test.rs +++ b/crates/aingle/src/conductor/cell/gossip_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::test_utils::conductor_setup::ConductorTestData; use crate::test_utils::new_zome_call; use crate::test_utils::wait_for_integration; diff --git a/crates/aingle/src/conductor/cell/op_query_test.rs b/crates/aingle/src/conductor/cell/op_query_test.rs index be51973c..44bf6949 100644 --- a/crates/aingle/src/conductor/cell/op_query_test.rs +++ b/crates/aingle/src/conductor/cell/op_query_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_sqlite::db::WriteManager; use aingle_state::mutations; use aingle_state::prelude::test_cell_env; diff --git a/crates/aingle/src/conductor/cell/test.rs b/crates/aingle/src/conductor/cell/test.rs index 0c431966..e3adfa2f 100644 --- a/crates/aingle/src/conductor/cell/test.rs +++ b/crates/aingle/src/conductor/cell/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::conductor::manager::spawn_task_manager; use crate::core::ribosome::guest_callback::genesis_self_check::GenesisSelfCheckResult; use crate::core::ribosome::MockRibosomeT; diff --git a/crates/aingle/src/conductor/cell/validation_package.rs b/crates/aingle/src/conductor/cell/validation_package.rs index 66de308e..ae3d341a 100644 --- a/crates/aingle/src/conductor/cell/validation_package.rs +++ b/crates/aingle/src/conductor/cell/validation_package.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::core::ribosome::guest_callback::validation_package::ValidationPackageResult; use crate::core::ribosome::RibosomeT; diff --git a/crates/aingle/src/conductor/conductor.rs b/crates/aingle/src/conductor/conductor.rs index c3848782..fc88d361 100644 --- a/crates/aingle/src/conductor/conductor.rs +++ b/crates/aingle/src/conductor/conductor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! A Conductor is a dynamically changing group of [Cell]s. //! diff --git a/crates/aingle/src/conductor/conductor/tests.rs b/crates/aingle/src/conductor/conductor/tests.rs index be1d9681..e36a092c 100644 --- a/crates/aingle/src/conductor/conductor/tests.rs +++ b/crates/aingle/src/conductor/conductor/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::Conductor; use super::ConductorState; use super::*; diff --git a/crates/aingle/src/conductor/config.rs b/crates/aingle/src/conductor/config.rs index 967dac97..d7fe026c 100644 --- a/crates/aingle/src/conductor/config.rs +++ b/crates/aingle/src/conductor/config.rs @@ -1 +1,4 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub use aingle_conductor_api::config::conductor::*; diff --git a/crates/aingle/src/conductor/entry_def_store.rs b/crates/aingle/src/conductor/entry_def_store.rs index ec0f2ad2..1eba21cf 100644 --- a/crates/aingle/src/conductor/entry_def_store.rs +++ b/crates/aingle/src/conductor/entry_def_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Entry Defs Store //! Stores all the entry definitions across zomes use crate::core::ribosome::guest_callback::entry_defs::EntryDefsHostAccess; diff --git a/crates/aingle/src/conductor/entry_def_store/error.rs b/crates/aingle/src/conductor/entry_def_store/error.rs index ae6bf1f2..4af8d252 100644 --- a/crates/aingle/src/conductor/entry_def_store/error.rs +++ b/crates/aingle/src/conductor/entry_def_store/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use crate::core::ribosome::error::RibosomeError; diff --git a/crates/aingle/src/conductor/error.rs b/crates/aingle/src/conductor/error.rs index 3dd05656..430577a8 100644 --- a/crates/aingle/src/conductor/error.rs +++ b/crates/aingle/src/conductor/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::interface::error::InterfaceError; use super::{entry_def_store::error::EntryDefStoreError, state::AppInterfaceId}; use crate::conductor::cell::error::CellError; diff --git a/crates/aingle/src/conductor/handle.rs b/crates/aingle/src/conductor/handle.rs index 725d82f5..dae5cc8e 100644 --- a/crates/aingle/src/conductor/handle.rs +++ b/crates/aingle/src/conductor/handle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines [ConductorHandle], a lightweight cloneable reference to a Conductor //! with a limited public interface. //! diff --git a/crates/aingle/src/conductor/interactive.rs b/crates/aingle/src/conductor/interactive.rs index fa686295..92739b4e 100644 --- a/crates/aingle/src/conductor/interactive.rs +++ b/crates/aingle/src/conductor/interactive.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helper functions for interacting with the user when running a Conductor //! with the --interactive flag diff --git a/crates/aingle/src/conductor/interface.rs b/crates/aingle/src/conductor/interface.rs index 3ff5efa7..cec28f2b 100644 --- a/crates/aingle/src/conductor/interface.rs +++ b/crates/aingle/src/conductor/interface.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Interfaces are long-running tasks which listen for incoming messages //! and dispatch them to the appropriate handlers within AIngle. //! They also allow emitting responses and one-way Signals. diff --git a/crates/aingle/src/conductor/interface/error.rs b/crates/aingle/src/conductor/interface/error.rs index 8001bb86..2f11b556 100644 --- a/crates/aingle/src/conductor/interface/error.rs +++ b/crates/aingle/src/conductor/interface/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::conductor::error::ConductorError; use aingle_middleware_bytes::SerializedBytesError; use aingle_types::signal::Signal; diff --git a/crates/aingle/src/conductor/interface/websocket.rs b/crates/aingle/src/conductor/interface/websocket.rs index 7b9c26c0..1b54cb2c 100644 --- a/crates/aingle/src/conductor/interface/websocket.rs +++ b/crates/aingle/src/conductor/interface/websocket.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Module for establishing Websocket-based Interfaces, //! i.e. those configured with `InterfaceDriver::Websocket` diff --git a/crates/aingle/src/conductor/logger.rs b/crates/aingle/src/conductor/logger.rs index e69de29b..af77c4d6 100644 --- a/crates/aingle/src/conductor/logger.rs +++ b/crates/aingle/src/conductor/logger.rs @@ -0,0 +1,3 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + diff --git a/crates/aingle/src/conductor/manager/error.rs b/crates/aingle/src/conductor/manager/error.rs index 8288d9db..a09de841 100644 --- a/crates/aingle/src/conductor/manager/error.rs +++ b/crates/aingle/src/conductor/manager/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use crate::conductor::error::ConductorError; diff --git a/crates/aingle/src/conductor/manager/mod.rs b/crates/aingle/src/conductor/manager/mod.rs index 34d7fef9..9fc49014 100644 --- a/crates/aingle/src/conductor/manager/mod.rs +++ b/crates/aingle/src/conductor/manager/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! We want to have control over certain long running //! tasks that we care about. //! If a task that is added to the task manager ends diff --git a/crates/aingle/src/conductor/p2p_agent_store.rs b/crates/aingle/src/conductor/p2p_agent_store.rs index 7e6c21c4..5d84ff3b 100644 --- a/crates/aingle/src/conductor/p2p_agent_store.rs +++ b/crates/aingle/src/conductor/p2p_agent_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Queries for the P2pAgentStore db use ai_hash::AgentPubKey; diff --git a/crates/aingle/src/conductor/p2p_metrics.rs b/crates/aingle/src/conductor/p2p_metrics.rs index 1332f3d5..ba38a2df 100644 --- a/crates/aingle/src/conductor/p2p_metrics.rs +++ b/crates/aingle/src/conductor/p2p_metrics.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Queries for the P2pMetrics store // TODO [ B-04249 ] move this to the combined aingle_sqlite crate once // consolidated with aingle_state diff --git a/crates/aingle/src/conductor/paths.rs b/crates/aingle/src/conductor/paths.rs index 0b7fecd2..568b8079 100644 --- a/crates/aingle/src/conductor/paths.rs +++ b/crates/aingle/src/conductor/paths.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines default paths for various resources pub use aingle_conductor_api::config::conductor::paths::*; diff --git a/crates/aingle/src/conductor/saf_store.rs b/crates/aingle/src/conductor/saf_store.rs index 77c78bb3..77a2c9e3 100644 --- a/crates/aingle/src/conductor/saf_store.rs +++ b/crates/aingle/src/conductor/saf_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_types::prelude::*; use aingle_zome_types::entry_def::EntryDef; use std::collections::HashMap; diff --git a/crates/aingle/src/conductor/state.rs b/crates/aingle/src/conductor/state.rs index e62cef2b..e6b4dd2e 100644 --- a/crates/aingle/src/conductor/state.rs +++ b/crates/aingle/src/conductor/state.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Structs which allow the Conductor's state to be persisted across //! startups and shutdowns diff --git a/crates/aingle/src/core.rs b/crates/aingle/src/core.rs index 1e376e65..2a6dfa4d 100644 --- a/crates/aingle/src/core.rs +++ b/crates/aingle/src/core.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines the core AIngle workflows #![deny(missing_docs)] diff --git a/crates/aingle/src/core/queue_consumer.rs b/crates/aingle/src/core/queue_consumer.rs index 08b626bb..d68b84cd 100644 --- a/crates/aingle/src/core/queue_consumer.rs +++ b/crates/aingle/src/core/queue_consumer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Manages the spawning of tasks which process the various work queues in //! the system, as well as notifying subsequent queue processors to pick up the //! work that was left off. diff --git a/crates/aingle/src/core/queue_consumer/app_validation_consumer.rs b/crates/aingle/src/core/queue_consumer/app_validation_consumer.rs index df784ff6..6e0900e3 100644 --- a/crates/aingle/src/core/queue_consumer/app_validation_consumer.rs +++ b/crates/aingle/src/core/queue_consumer/app_validation_consumer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for sys validation use super::*; diff --git a/crates/aingle/src/core/queue_consumer/integrate_sgd_ops_consumer.rs b/crates/aingle/src/core/queue_consumer/integrate_sgd_ops_consumer.rs index 3f99d87d..820cf39d 100644 --- a/crates/aingle/src/core/queue_consumer/integrate_sgd_ops_consumer.rs +++ b/crates/aingle/src/core/queue_consumer/integrate_sgd_ops_consumer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for SgdOp integration use super::*; diff --git a/crates/aingle/src/core/queue_consumer/publish_sgd_ops_consumer.rs b/crates/aingle/src/core/queue_consumer/publish_sgd_ops_consumer.rs index 67b30cd6..ea0de96e 100644 --- a/crates/aingle/src/core/queue_consumer/publish_sgd_ops_consumer.rs +++ b/crates/aingle/src/core/queue_consumer/publish_sgd_ops_consumer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for sys validation use super::*; diff --git a/crates/aingle/src/core/queue_consumer/sys_validation_consumer.rs b/crates/aingle/src/core/queue_consumer/sys_validation_consumer.rs index 3116376f..11294728 100644 --- a/crates/aingle/src/core/queue_consumer/sys_validation_consumer.rs +++ b/crates/aingle/src/core/queue_consumer/sys_validation_consumer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for sys validation use super::*; diff --git a/crates/aingle/src/core/queue_consumer/validation_receipt_consumer.rs b/crates/aingle/src/core/queue_consumer/validation_receipt_consumer.rs index a86d3ee3..ee184f08 100644 --- a/crates/aingle/src/core/queue_consumer/validation_receipt_consumer.rs +++ b/crates/aingle/src/core/queue_consumer/validation_receipt_consumer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for validation receipt use super::*; diff --git a/crates/aingle/src/core/ribosome.rs b/crates/aingle/src/core/ribosome.rs index c60fa8aa..d75a8271 100644 --- a/crates/aingle/src/core/ribosome.rs +++ b/crates/aingle/src/core/ribosome.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A Ribosome is a structure which knows how to execute hApp code. //! //! We have only one instance of this: [RealRibosome]. The abstract trait exists diff --git a/crates/aingle/src/core/ribosome/error.rs b/crates/aingle/src/core/ribosome/error.rs index 9d427346..ccd38da3 100644 --- a/crates/aingle/src/core/ribosome/error.rs +++ b/crates/aingle/src/core/ribosome/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! Errors occurring during a [Ribosome] call diff --git a/crates/aingle/src/core/ribosome/guest_callback.rs b/crates/aingle/src/core/ribosome/guest_callback.rs index 3f3878df..70d86dc5 100644 --- a/crates/aingle/src/core/ribosome/guest_callback.rs +++ b/crates/aingle/src/core/ribosome/guest_callback.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub mod entry_defs; pub mod genesis_self_check; pub mod init; diff --git a/crates/aingle/src/core/ribosome/guest_callback/entry_defs.rs b/crates/aingle/src/core/ribosome/guest_callback/entry_defs.rs index 07db3414..ee5a64ee 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/entry_defs.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/entry_defs.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/genesis_self_check.rs b/crates/aingle/src/core/ribosome/guest_callback/genesis_self_check.rs index fc76e6f2..fc39b3cf 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/genesis_self_check.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/genesis_self_check.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/init.rs b/crates/aingle/src/core/ribosome/guest_callback/init.rs index 37588506..d98fa04b 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/init.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/init.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/migrate_agent.rs b/crates/aingle/src/core/ribosome/guest_callback/migrate_agent.rs index 3afeedb9..5bc91e9c 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/migrate_agent.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/migrate_agent.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/post_commit.rs b/crates/aingle/src/core/ribosome/guest_callback/post_commit.rs index 964f361f..bdde3240 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/post_commit.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/post_commit.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/validate.rs b/crates/aingle/src/core/ribosome/guest_callback/validate.rs index 857b221d..0034d7df 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/validate.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/validate.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/validate_link.rs b/crates/aingle/src/core/ribosome/guest_callback/validate_link.rs index 9d99fd4e..a3d6442d 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/validate_link.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/validate_link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/guest_callback/validation_package.rs b/crates/aingle/src/core/ribosome/guest_callback/validation_package.rs index 9e7c3cc2..13dba2ed 100644 --- a/crates/aingle/src/core/ribosome/guest_callback/validation_package.rs +++ b/crates/aingle/src/core/ribosome/guest_callback/validation_package.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::FnComponents; use crate::core::ribosome::HostAccess; use crate::core::ribosome::Invocation; diff --git a/crates/aingle/src/core/ribosome/host_fn.rs b/crates/aingle/src/core/ribosome/host_fn.rs index 9f1580fb..699bccc6 100644 --- a/crates/aingle/src/core/ribosome/host_fn.rs +++ b/crates/aingle/src/core/ribosome/host_fn.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::CallContext; use super::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/agent_info.rs b/crates/aingle/src/core/ribosome/host_fn/agent_info.rs index 9b1709d8..7673f863 100644 --- a/crates/aingle/src/core/ribosome/host_fn/agent_info.rs +++ b/crates/aingle/src/core/ribosome/host_fn/agent_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/app_info.rs b/crates/aingle/src/core/ribosome/host_fn/app_info.rs index a7e49a52..a47d7725 100644 --- a/crates/aingle/src/core/ribosome/host_fn/app_info.rs +++ b/crates/aingle/src/core/ribosome/host_fn/app_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/call.rs b/crates/aingle/src/core/ribosome/host_fn/call.rs index 3dbd8ccf..c8f6ebc2 100644 --- a/crates/aingle/src/core/ribosome/host_fn/call.rs +++ b/crates/aingle/src/core/ribosome/host_fn/call.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use crate::core::ribosome::ZomeCall; diff --git a/crates/aingle/src/core/ribosome/host_fn/call_info.rs b/crates/aingle/src/core/ribosome/host_fn/call_info.rs index 8e596f32..1e53b2d1 100644 --- a/crates/aingle/src/core/ribosome/host_fn/call_info.rs +++ b/crates/aingle/src/core/ribosome/host_fn/call_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/call_remote.rs b/crates/aingle/src/core/ribosome/host_fn/call_remote.rs index 51cdfde6..bb35afc2 100644 --- a/crates/aingle/src/core/ribosome/host_fn/call_remote.rs +++ b/crates/aingle/src/core/ribosome/host_fn/call_remote.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_p2p::AIngleP2pCellT; diff --git a/crates/aingle/src/core/ribosome/host_fn/capability_claims.rs b/crates/aingle/src/core/ribosome/host_fn/capability_claims.rs index c934701e..ae423ad7 100644 --- a/crates/aingle/src/core/ribosome/host_fn/capability_claims.rs +++ b/crates/aingle/src/core/ribosome/host_fn/capability_claims.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/capability_grants.rs b/crates/aingle/src/core/ribosome/host_fn/capability_grants.rs index 14110ac4..972ff90d 100644 --- a/crates/aingle/src/core/ribosome/host_fn/capability_grants.rs +++ b/crates/aingle/src/core/ribosome/host_fn/capability_grants.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_wasmer_host::prelude::WasmError; diff --git a/crates/aingle/src/core/ribosome/host_fn/capability_info.rs b/crates/aingle/src/core/ribosome/host_fn/capability_info.rs index 15dac53e..e548b43f 100644 --- a/crates/aingle/src/core/ribosome/host_fn/capability_info.rs +++ b/crates/aingle/src/core/ribosome/host_fn/capability_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/create.rs b/crates/aingle/src/core/ribosome/host_fn/create.rs index bc52a733..0f1a3162 100644 --- a/crates/aingle/src/core/ribosome/host_fn/create.rs +++ b/crates/aingle/src/core/ribosome/host_fn/create.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::error::RibosomeError; use crate::core::ribosome::guest_callback::entry_defs::EntryDefsInvocation; use crate::core::ribosome::guest_callback::entry_defs::EntryDefsResult; diff --git a/crates/aingle/src/core/ribosome/host_fn/create_link.rs b/crates/aingle/src/core/ribosome/host_fn/create_link.rs index ba0ead47..e52ff31e 100644 --- a/crates/aingle/src/core/ribosome/host_fn/create_link.rs +++ b/crates/aingle/src/core/ribosome/host_fn/create_link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeError; use crate::core::ribosome::RibosomeT; diff --git a/crates/aingle/src/core/ribosome/host_fn/create_x25519_keypair.rs b/crates/aingle/src/core/ribosome/host_fn/create_x25519_keypair.rs index d64af1fc..c4361308 100644 --- a/crates/aingle/src/core/ribosome/host_fn/create_x25519_keypair.rs +++ b/crates/aingle/src/core/ribosome/host_fn/create_x25519_keypair.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_keystore::keystore_actor::KeystoreSenderExt; diff --git a/crates/aingle/src/core/ribosome/host_fn/delete.rs b/crates/aingle/src/core/ribosome/host_fn/delete.rs index 79a34a23..3d3008c9 100644 --- a/crates/aingle/src/core/ribosome/host_fn/delete.rs +++ b/crates/aingle/src/core/ribosome/host_fn/delete.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::error::RibosomeError; use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; diff --git a/crates/aingle/src/core/ribosome/host_fn/delete_link.rs b/crates/aingle/src/core/ribosome/host_fn/delete_link.rs index 7812ce01..f1632059 100644 --- a/crates/aingle/src/core/ribosome/host_fn/delete_link.rs +++ b/crates/aingle/src/core/ribosome/host_fn/delete_link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::error::RibosomeError; use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; diff --git a/crates/aingle/src/core/ribosome/host_fn/emit_signal.rs b/crates/aingle/src/core/ribosome/host_fn/emit_signal.rs index 9fe52db4..9bca996b 100644 --- a/crates/aingle/src/core/ribosome/host_fn/emit_signal.rs +++ b/crates/aingle/src/core/ribosome/host_fn/emit_signal.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::RibosomeT; use crate::core::ribosome::CallContext; use aingle_types::signal::Signal; diff --git a/crates/aingle/src/core/ribosome/host_fn/get.rs b/crates/aingle/src/core/ribosome/host_fn/get.rs index 098ce67c..f2128aa2 100644 --- a/crates/aingle/src/core/ribosome/host_fn/get.rs +++ b/crates/aingle/src/core/ribosome/host_fn/get.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cascade::Cascade; diff --git a/crates/aingle/src/core/ribosome/host_fn/get_agent_activity.rs b/crates/aingle/src/core/ribosome/host_fn/get_agent_activity.rs index 22b81c76..3824720a 100644 --- a/crates/aingle/src/core/ribosome/host_fn/get_agent_activity.rs +++ b/crates/aingle/src/core/ribosome/host_fn/get_agent_activity.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cascade::Cascade; diff --git a/crates/aingle/src/core/ribosome/host_fn/get_details.rs b/crates/aingle/src/core/ribosome/host_fn/get_details.rs index a715e0d5..9d0c6ea0 100644 --- a/crates/aingle/src/core/ribosome/host_fn/get_details.rs +++ b/crates/aingle/src/core/ribosome/host_fn/get_details.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cascade::Cascade; diff --git a/crates/aingle/src/core/ribosome/host_fn/get_link_details.rs b/crates/aingle/src/core/ribosome/host_fn/get_link_details.rs index 3e9cf911..7023797f 100644 --- a/crates/aingle/src/core/ribosome/host_fn/get_link_details.rs +++ b/crates/aingle/src/core/ribosome/host_fn/get_link_details.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cascade::Cascade; diff --git a/crates/aingle/src/core/ribosome/host_fn/get_links.rs b/crates/aingle/src/core/ribosome/host_fn/get_links.rs index 8782627a..9e6f0e0d 100644 --- a/crates/aingle/src/core/ribosome/host_fn/get_links.rs +++ b/crates/aingle/src/core/ribosome/host_fn/get_links.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cascade::Cascade; diff --git a/crates/aingle/src/core/ribosome/host_fn/graph_query.rs b/crates/aingle/src/core/ribosome/host_fn/graph_query.rs index 1a7213b5..a741dabf 100644 --- a/crates/aingle/src/core/ribosome/host_fn/graph_query.rs +++ b/crates/aingle/src/core/ribosome/host_fn/graph_query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cortex::client::{CortexClientConfig, CortexInternalClient}; diff --git a/crates/aingle/src/core/ribosome/host_fn/graph_store.rs b/crates/aingle/src/core/ribosome/host_fn/graph_store.rs index 84528b00..3cd24998 100644 --- a/crates/aingle/src/core/ribosome/host_fn/graph_store.rs +++ b/crates/aingle/src/core/ribosome/host_fn/graph_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cortex::client::{CortexClientConfig, CortexInternalClient}; diff --git a/crates/aingle/src/core/ribosome/host_fn/hash_entry.rs b/crates/aingle/src/core/ribosome/host_fn/hash_entry.rs index a7627d55..3e53a696 100644 --- a/crates/aingle/src/core/ribosome/host_fn/hash_entry.rs +++ b/crates/aingle/src/core/ribosome/host_fn/hash_entry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use ai_hash::HasHash; diff --git a/crates/aingle/src/core/ribosome/host_fn/memory_recall.rs b/crates/aingle/src/core/ribosome/host_fn/memory_recall.rs index c003dd5e..041f5eca 100644 --- a/crates/aingle/src/core/ribosome/host_fn/memory_recall.rs +++ b/crates/aingle/src/core/ribosome/host_fn/memory_recall.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cortex::client::{CortexClientConfig, CortexInternalClient}; diff --git a/crates/aingle/src/core/ribosome/host_fn/memory_remember.rs b/crates/aingle/src/core/ribosome/host_fn/memory_remember.rs index eb012203..7607c443 100644 --- a/crates/aingle/src/core/ribosome/host_fn/memory_remember.rs +++ b/crates/aingle/src/core/ribosome/host_fn/memory_remember.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_cortex::client::{CortexClientConfig, CortexInternalClient}; diff --git a/crates/aingle/src/core/ribosome/host_fn/query.rs b/crates/aingle/src/core/ribosome/host_fn/query.rs index 4d46b7d6..abde2da8 100644 --- a/crates/aingle/src/core/ribosome/host_fn/query.rs +++ b/crates/aingle/src/core/ribosome/host_fn/query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/random_bytes.rs b/crates/aingle/src/core/ribosome/host_fn/random_bytes.rs index d9053647..fbeb5a68 100644 --- a/crates/aingle/src/core/ribosome/host_fn/random_bytes.rs +++ b/crates/aingle/src/core/ribosome/host_fn/random_bytes.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/remote_signal.rs b/crates/aingle/src/core/ribosome/host_fn/remote_signal.rs index 6aa33142..0b2bacde 100644 --- a/crates/aingle/src/core/ribosome/host_fn/remote_signal.rs +++ b/crates/aingle/src/core/ribosome/host_fn/remote_signal.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_p2p::AIngleP2pCellT; diff --git a/crates/aingle/src/core/ribosome/host_fn/saf_info.rs b/crates/aingle/src/core/ribosome/host_fn/saf_info.rs index 14e38a9d..47359bf2 100644 --- a/crates/aingle/src/core/ribosome/host_fn/saf_info.rs +++ b/crates/aingle/src/core/ribosome/host_fn/saf_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/schedule.rs b/crates/aingle/src/core/ribosome/host_fn/schedule.rs index a7275ba6..70bcd12a 100644 --- a/crates/aingle/src/core/ribosome/host_fn/schedule.rs +++ b/crates/aingle/src/core/ribosome/host_fn/schedule.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/sign.rs b/crates/aingle/src/core/ribosome/host_fn/sign.rs index c58d14c5..ece6e3c1 100644 --- a/crates/aingle/src/core/ribosome/host_fn/sign.rs +++ b/crates/aingle/src/core/ribosome/host_fn/sign.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_keystore::keystore_actor::KeystoreSenderExt; diff --git a/crates/aingle/src/core/ribosome/host_fn/sign_ephemeral.rs b/crates/aingle/src/core/ribosome/host_fn/sign_ephemeral.rs index 13a26a6a..4f5456c0 100644 --- a/crates/aingle/src/core/ribosome/host_fn/sign_ephemeral.rs +++ b/crates/aingle/src/core/ribosome/host_fn/sign_ephemeral.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/sleep.rs b/crates/aingle/src/core/ribosome/host_fn/sleep.rs index 9e096479..3dfe1480 100644 --- a/crates/aingle/src/core/ribosome/host_fn/sleep.rs +++ b/crates/aingle/src/core/ribosome/host_fn/sleep.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/sys_time.rs b/crates/aingle/src/core/ribosome/host_fn/sys_time.rs index 042b8353..4b14fdd4 100644 --- a/crates/aingle/src/core/ribosome/host_fn/sys_time.rs +++ b/crates/aingle/src/core/ribosome/host_fn/sys_time.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_wasmer_host::prelude::WasmError; diff --git a/crates/aingle/src/core/ribosome/host_fn/trace.rs b/crates/aingle/src/core/ribosome/host_fn/trace.rs index d85b2dc6..b92abaf0 100644 --- a/crates/aingle/src/core/ribosome/host_fn/trace.rs +++ b/crates/aingle/src/core/ribosome/host_fn/trace.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/unreachable.rs b/crates/aingle/src/core/ribosome/host_fn/unreachable.rs index c7b650f4..037c2834 100644 --- a/crates/aingle/src/core/ribosome/host_fn/unreachable.rs +++ b/crates/aingle/src/core/ribosome/host_fn/unreachable.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/update.rs b/crates/aingle/src/core/ribosome/host_fn/update.rs index 59cc12b8..19a31111 100644 --- a/crates/aingle/src/core/ribosome/host_fn/update.rs +++ b/crates/aingle/src/core/ribosome/host_fn/update.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::create::extract_entry_def; use super::delete::get_original_address; use crate::core::ribosome::CallContext; diff --git a/crates/aingle/src/core/ribosome/host_fn/verify_signature.rs b/crates/aingle/src/core/ribosome/host_fn/verify_signature.rs index d10dcf27..d47ba0f2 100644 --- a/crates/aingle/src/core/ribosome/host_fn/verify_signature.rs +++ b/crates/aingle/src/core/ribosome/host_fn/verify_signature.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_keystore::AgentPubKeyExt; diff --git a/crates/aingle/src/core/ribosome/host_fn/version.rs b/crates/aingle/src/core/ribosome/host_fn/version.rs index 9d93055e..16906710 100644 --- a/crates/aingle/src/core/ribosome/host_fn/version.rs +++ b/crates/aingle/src/core/ribosome/host_fn/version.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_decrypt.rs b/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_decrypt.rs index a317b68c..0e334ba8 100644 --- a/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_decrypt.rs +++ b/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_decrypt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_keystore::keystore_actor::KeystoreSenderExt; diff --git a/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_encrypt.rs b/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_encrypt.rs index a04f9aa5..d86af9b8 100644 --- a/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_encrypt.rs +++ b/crates/aingle/src/core/ribosome/host_fn/x_25519_x_salsa20_poly1305_encrypt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_keystore::keystore_actor::KeystoreSenderExt; diff --git a/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_decrypt.rs b/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_decrypt.rs index 46224632..1113d09f 100644 --- a/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_decrypt.rs +++ b/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_decrypt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use std::sync::Arc; diff --git a/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_encrypt.rs b/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_encrypt.rs index 12a9789c..ca25a493 100644 --- a/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_encrypt.rs +++ b/crates/aingle/src/core/ribosome/host_fn/x_salsa20_poly1305_encrypt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/ribosome/host_fn/zome_info.rs b/crates/aingle/src/core/ribosome/host_fn/zome_info.rs index 473a2d6a..4546c201 100644 --- a/crates/aingle/src/core/ribosome/host_fn/zome_info.rs +++ b/crates/aingle/src/core/ribosome/host_fn/zome_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::core::ribosome::CallContext; use crate::core::ribosome::RibosomeT; use ai_hash::HasHash; diff --git a/crates/aingle/src/core/ribosome/indirect_call_test.rs b/crates/aingle/src/core/ribosome/indirect_call_test.rs index d3f509ec..63f971d8 100644 --- a/crates/aingle/src/core/ribosome/indirect_call_test.rs +++ b/crates/aingle/src/core/ribosome/indirect_call_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Minimal test to debug indirect call type mismatch //! //! This test creates a minimal WASM module to verify if the issue diff --git a/crates/aingle/src/core/ribosome/real_ribosome.rs b/crates/aingle/src/core/ribosome/real_ribosome.rs index 6758f237..014511ee 100644 --- a/crates/aingle/src/core/ribosome/real_ribosome.rs +++ b/crates/aingle/src/core/ribosome/real_ribosome.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::guest_callback::entry_defs::EntryDefsHostAccess; use super::guest_callback::init::InitHostAccess; use super::guest_callback::migrate_agent::MigrateAgentHostAccess; diff --git a/crates/aingle/src/core/sys_validate.rs b/crates/aingle/src/core/sys_validate.rs index 0161df53..edbc49c7 100644 --- a/crates/aingle/src/core/sys_validate.rs +++ b/crates/aingle/src/core/sys_validate.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # System Validation Checks //! This module contains all the checks we run for sys validation diff --git a/crates/aingle/src/core/sys_validate/error.rs b/crates/aingle/src/core/sys_validate/error.rs index 0aba0442..a3c53c2c 100644 --- a/crates/aingle/src/core/sys_validate/error.rs +++ b/crates/aingle/src/core/sys_validate/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::convert::TryFrom; use super::SourceChainError; diff --git a/crates/aingle/src/core/sys_validate/tests.rs b/crates/aingle/src/core/sys_validate/tests.rs index b7f0995c..f644ba6a 100644 --- a/crates/aingle/src/core/sys_validate/tests.rs +++ b/crates/aingle/src/core/sys_validate/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::conductor::api::error::ConductorApiError; use crate::conductor::api::MockCellConductorApi; diff --git a/crates/aingle/src/core/validation.rs b/crates/aingle/src/core/validation.rs index 98f4ec07..5d6895dd 100644 --- a/crates/aingle/src/core/validation.rs +++ b/crates/aingle/src/core/validation.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types needed for all validation use std::convert::TryFrom; diff --git a/crates/aingle/src/core/workflow.rs b/crates/aingle/src/core/workflow.rs index ac0ecf90..f5374060 100644 --- a/crates/aingle/src/core/workflow.rs +++ b/crates/aingle/src/core/workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Workflows are the core building block of AIngle functionality. //! //! ## Properties diff --git a/crates/aingle/src/core/workflow/app_validation_workflow.rs b/crates/aingle/src/core/workflow/app_validation_workflow.rs index 7822c656..de4b2cc4 100644 --- a/crates/aingle/src/core/workflow/app_validation_workflow.rs +++ b/crates/aingle/src/core/workflow/app_validation_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for sys validation use std::convert::TryInto; diff --git a/crates/aingle/src/core/workflow/app_validation_workflow/error.rs b/crates/aingle/src/core/workflow/app_validation_workflow/error.rs index 39b3570f..c563744d 100644 --- a/crates/aingle/src/core/workflow/app_validation_workflow/error.rs +++ b/crates/aingle/src/core/workflow/app_validation_workflow/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_p2p::AIngleP2pError; use aingle_types::prelude::*; use thiserror::Error; diff --git a/crates/aingle/src/core/workflow/app_validation_workflow/network_call_tests.rs b/crates/aingle/src/core/workflow/app_validation_workflow/network_call_tests.rs index d98ca43a..1f97690f 100644 --- a/crates/aingle/src/core/workflow/app_validation_workflow/network_call_tests.rs +++ b/crates/aingle/src/core/workflow/app_validation_workflow/network_call_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::Element; use adk::prelude::EntryType; use adk::prelude::ValidationPackage; diff --git a/crates/aingle/src/core/workflow/app_validation_workflow/tests.rs b/crates/aingle/src/core/workflow/app_validation_workflow/tests.rs index a3dc32a5..839b6471 100644 --- a/crates/aingle/src/core/workflow/app_validation_workflow/tests.rs +++ b/crates/aingle/src/core/workflow/app_validation_workflow/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::conductor::ConductorHandle; use crate::core::ribosome::ZomeCallInvocation; use crate::test_utils::host_fn_caller::*; diff --git a/crates/aingle/src/core/workflow/app_validation_workflow/types.rs b/crates/aingle/src/core/workflow/app_validation_workflow/types.rs index 72863896..d0aef147 100644 --- a/crates/aingle/src/core/workflow/app_validation_workflow/types.rs +++ b/crates/aingle/src/core/workflow/app_validation_workflow/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::convert::TryFrom; use ai_hash::AnySgdHash; diff --git a/crates/aingle/src/core/workflow/app_validation_workflow/validation_package.rs b/crates/aingle/src/core/workflow/app_validation_workflow/validation_package.rs index 733cc6a5..443d17f7 100644 --- a/crates/aingle/src/core/workflow/app_validation_workflow/validation_package.rs +++ b/crates/aingle/src/core/workflow/app_validation_workflow/validation_package.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_p2p::AIngleP2pCell; use aingle_state::host_fn_workspace::HostFnWorkspace; use aingle_types::prelude::*; diff --git a/crates/aingle/src/core/workflow/call_zome_workflow.rs b/crates/aingle/src/core/workflow/call_zome_workflow.rs index c8953d2b..73d294fd 100644 --- a/crates/aingle/src/core/workflow/call_zome_workflow.rs +++ b/crates/aingle/src/core/workflow/call_zome_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::app_validation_workflow; use super::app_validation_workflow::Outcome; use super::error::WorkflowResult; diff --git a/crates/aingle/src/core/workflow/call_zome_workflow/validation_test.rs b/crates/aingle/src/core/workflow/call_zome_workflow/validation_test.rs index c513f468..6d740bc5 100644 --- a/crates/aingle/src/core/workflow/call_zome_workflow/validation_test.rs +++ b/crates/aingle/src/core/workflow/call_zome_workflow/validation_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::conductor::api::error::ConductorApiError; use crate::conductor::CellError; use crate::conductor::ConductorHandle; diff --git a/crates/aingle/src/core/workflow/error.rs b/crates/aingle/src/core/workflow/error.rs index cd3a21a6..340c2be8 100644 --- a/crates/aingle/src/core/workflow/error.rs +++ b/crates/aingle/src/core/workflow/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + // Error types are self-explanatory #![allow(missing_docs)] diff --git a/crates/aingle/src/core/workflow/genesis_workflow.rs b/crates/aingle/src/core/workflow/genesis_workflow.rs index b46a0ff4..f080a178 100644 --- a/crates/aingle/src/core/workflow/genesis_workflow.rs +++ b/crates/aingle/src/core/workflow/genesis_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Genesis Workflow: Initialize the source chain with the initial entries: //! - Saf //! - AgentValidationPkg diff --git a/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow.rs b/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow.rs index 4c2ac94e..89dd8c55 100644 --- a/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow.rs +++ b/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for SgdOp integration use super::error::WorkflowResult; diff --git a/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow/test.rs b/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow/test.rs index fd5bc7fb..394c6e9e 100644 --- a/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow/test.rs +++ b/crates/aingle/src/core/workflow/incoming_sgd_ops_workflow/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use ::ai_fixt::prelude::*; use aingle_keystore::AgentPubKeyExt; diff --git a/crates/aingle/src/core/workflow/initialize_zomes_workflow.rs b/crates/aingle/src/core/workflow/initialize_zomes_workflow.rs index 4824548d..415e56cc 100644 --- a/crates/aingle/src/core/workflow/initialize_zomes_workflow.rs +++ b/crates/aingle/src/core/workflow/initialize_zomes_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::error::WorkflowResult; use crate::conductor::api::CellConductorApiT; use crate::core::ribosome::guest_callback::init::InitHostAccess; diff --git a/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow.rs b/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow.rs index dcb6fd0f..c2d5b8d9 100644 --- a/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow.rs +++ b/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for SgdOp integration use super::error::WorkflowResult; diff --git a/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/query_tests.rs b/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/query_tests.rs index 080dbd4a..bc780e23 100644 --- a/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/query_tests.rs +++ b/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/query_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::collections::HashMap; use std::collections::HashSet; diff --git a/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/tests.rs b/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/tests.rs index b52f6d32..a93de5f6 100644 --- a/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/tests.rs +++ b/crates/aingle/src/core/workflow/integrate_sgd_ops_workflow/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![cfg(test)] #![cfg(feature = "test_utils")] diff --git a/crates/aingle/src/core/workflow/publish_sgd_ops_workflow.rs b/crates/aingle/src/core/workflow/publish_sgd_ops_workflow.rs index 5c26df3e..3b4629eb 100644 --- a/crates/aingle/src/core/workflow/publish_sgd_ops_workflow.rs +++ b/crates/aingle/src/core/workflow/publish_sgd_ops_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Publish Sgd Op Workflow //! //! ## Open questions diff --git a/crates/aingle/src/core/workflow/publish_sgd_ops_workflow/publish_query.rs b/crates/aingle/src/core/workflow/publish_sgd_ops_workflow/publish_query.rs index d11696d1..773aeacd 100644 --- a/crates/aingle/src/core/workflow/publish_sgd_ops_workflow/publish_query.rs +++ b/crates/aingle/src/core/workflow/publish_sgd_ops_workflow/publish_query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + // use std::time::SystemTime; // use std::time::UNIX_EPOCH; diff --git a/crates/aingle/src/core/workflow/sys_validation_workflow.rs b/crates/aingle/src/core/workflow/sys_validation_workflow.rs index c29d4726..33bd994d 100644 --- a/crates/aingle/src/core/workflow/sys_validation_workflow.rs +++ b/crates/aingle/src/core/workflow/sys_validation_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The workflow and queue consumer for sys validation #![allow(deprecated)] diff --git a/crates/aingle/src/core/workflow/sys_validation_workflow/chain_test.rs b/crates/aingle/src/core/workflow/sys_validation_workflow/chain_test.rs index 17f56393..50775063 100644 --- a/crates/aingle/src/core/workflow/sys_validation_workflow/chain_test.rs +++ b/crates/aingle/src/core/workflow/sys_validation_workflow/chain_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::conductor::ConductorHandle; use crate::test_utils::setup_app; diff --git a/crates/aingle/src/core/workflow/sys_validation_workflow/test_ideas.rs b/crates/aingle/src/core/workflow/sys_validation_workflow/test_ideas.rs index 3dc2353d..6315286d 100644 --- a/crates/aingle/src/core/workflow/sys_validation_workflow/test_ideas.rs +++ b/crates/aingle/src/core/workflow/sys_validation_workflow/test_ideas.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// Stubs for things that might break validation /// This test shows a way to create a delete with a rejected header diff --git a/crates/aingle/src/core/workflow/sys_validation_workflow/tests.rs b/crates/aingle/src/core/workflow/sys_validation_workflow/tests.rs index 94844d28..c1468cfd 100644 --- a/crates/aingle/src/core/workflow/sys_validation_workflow/tests.rs +++ b/crates/aingle/src/core/workflow/sys_validation_workflow/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::test_utils::host_fn_caller::*; use crate::test_utils::setup_app; use crate::test_utils::wait_for_integration; diff --git a/crates/aingle/src/core/workflow/sys_validation_workflow/types.rs b/crates/aingle/src/core/workflow/sys_validation_workflow/types.rs index 58bbea2d..30258a03 100644 --- a/crates/aingle/src/core/workflow/sys_validation_workflow/types.rs +++ b/crates/aingle/src/core/workflow/sys_validation_workflow/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; #[derive(Debug)] diff --git a/crates/aingle/src/core/workflow/sys_validation_workflow/validation_query.rs b/crates/aingle/src/core/workflow/sys_validation_workflow/validation_query.rs index 6380a7c9..ed26f3d2 100644 --- a/crates/aingle/src/core/workflow/sys_validation_workflow/validation_query.rs +++ b/crates/aingle/src/core/workflow/sys_validation_workflow/validation_query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::SgdOpHash; use aingle_state::query::prelude::*; use aingle_types::env::EnvRead; diff --git a/crates/aingle/src/core/workflow/validation_receipt_workflow.rs b/crates/aingle/src/core/workflow/validation_receipt_workflow.rs index 15399e1f..fda9e93e 100644 --- a/crates/aingle/src/core/workflow/validation_receipt_workflow.rs +++ b/crates/aingle/src/core/workflow/validation_receipt_workflow.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_p2p::AIngleP2pCell; use aingle_p2p::AIngleP2pCellT; use aingle_state::prelude::*; diff --git a/crates/aingle/src/core/workflow/validation_receipt_workflow/tests.rs b/crates/aingle/src/core/workflow/validation_receipt_workflow/tests.rs index 84796096..3d36c86b 100644 --- a/crates/aingle/src/core/workflow/validation_receipt_workflow/tests.rs +++ b/crates/aingle/src/core/workflow/validation_receipt_workflow/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::sweettest::*; use crate::test_utils::consistency_10s; use adk::prelude::*; diff --git a/crates/aingle/src/fixt.rs b/crates/aingle/src/fixt.rs index f4fb116e..58df15e9 100644 --- a/crates/aingle/src/fixt.rs +++ b/crates/aingle/src/fixt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub mod curve; use crate::conductor::api::CellConductorApi; diff --git a/crates/aingle/src/fixt/curve.rs b/crates/aingle/src/fixt/curve.rs index 6c4c8400..15f4b239 100644 --- a/crates/aingle/src/fixt/curve.rs +++ b/crates/aingle/src/fixt/curve.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_wasm_test_utils::TestWasm; pub struct Zomes(pub Vec); diff --git a/crates/aingle/src/lib.rs b/crates/aingle/src/lib.rs index abbab259..b8c4b3f3 100644 --- a/crates/aingle/src/lib.rs +++ b/crates/aingle/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! All the components you need to build a AIngle Conductor // Toggle this to see what needs to be eventually refactored (as warnings). diff --git a/crates/aingle/src/local_network_tests.rs b/crates/aingle/src/local_network_tests.rs index 826b1c6a..a2724fe2 100644 --- a/crates/aingle/src/local_network_tests.rs +++ b/crates/aingle/src/local_network_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::convert::TryFrom; use std::sync::Arc; diff --git a/crates/aingle/src/perf.rs b/crates/aingle/src/perf.rs index e69de29b..af77c4d6 100644 --- a/crates/aingle/src/perf.rs +++ b/crates/aingle/src/perf.rs @@ -0,0 +1,3 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + diff --git a/crates/aingle/src/sweettest/mod.rs b/crates/aingle/src/sweettest/mod.rs index 404667b8..b56e1e1d 100644 --- a/crates/aingle/src/sweettest/mod.rs +++ b/crates/aingle/src/sweettest/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! sweettest = Streamlined AIngle test utils with lots of added sugar //! //! Features: diff --git a/crates/aingle/src/sweettest/sweet_agents.rs b/crates/aingle/src/sweettest/sweet_agents.rs index 55e1841d..6b2e4143 100644 --- a/crates/aingle/src/sweettest/sweet_agents.rs +++ b/crates/aingle/src/sweettest/sweet_agents.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Simple methods for generating collections of AgentPubKeys for use in tests use ai_hash::AgentPubKey; diff --git a/crates/aingle/src/sweettest/sweet_app.rs b/crates/aingle/src/sweettest/sweet_app.rs index efe9a587..8bd82a04 100644 --- a/crates/aingle/src/sweettest/sweet_app.rs +++ b/crates/aingle/src/sweettest/sweet_app.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::SweetCell; use ai_hash::AgentPubKey; use aingle_types::app::InstalledAppId; diff --git a/crates/aingle/src/sweettest/sweet_cell.rs b/crates/aingle/src/sweettest/sweet_cell.rs index 50a05f5b..39247e56 100644 --- a/crates/aingle/src/sweettest/sweet_cell.rs +++ b/crates/aingle/src/sweettest/sweet_cell.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::SweetZome; use adk::prelude::*; use ai_hash::SafHash; diff --git a/crates/aingle/src/sweettest/sweet_conductor.rs b/crates/aingle/src/sweettest/sweet_conductor.rs index 31da27c2..064dc771 100644 --- a/crates/aingle/src/sweettest/sweet_conductor.rs +++ b/crates/aingle/src/sweettest/sweet_conductor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A wrapper around ConductorHandle with more convenient methods for testing // TODO [ B-03669 ] move to own crate diff --git a/crates/aingle/src/sweettest/sweet_conductor_batch.rs b/crates/aingle/src/sweettest/sweet_conductor_batch.rs index 2ba38802..cf602625 100644 --- a/crates/aingle/src/sweettest/sweet_conductor_batch.rs +++ b/crates/aingle/src/sweettest/sweet_conductor_batch.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::{standard_config, SweetAgents, SweetAppBatch, SweetConductor}; use crate::conductor::{config::ConductorConfig, error::ConductorResult}; use adk::prelude::*; diff --git a/crates/aingle/src/sweettest/sweet_conductor_handle.rs b/crates/aingle/src/sweettest/sweet_conductor_handle.rs index d7e3965a..134826e9 100644 --- a/crates/aingle/src/sweettest/sweet_conductor_handle.rs +++ b/crates/aingle/src/sweettest/sweet_conductor_handle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::SweetZome; use crate::conductor::{api::error::ConductorApiResult, ConductorHandle}; use aingle_conductor_api::ZomeCall; diff --git a/crates/aingle/src/sweettest/sweet_network.rs b/crates/aingle/src/sweettest/sweet_network.rs index 9ef5a0ce..dacea905 100644 --- a/crates/aingle/src/sweettest/sweet_network.rs +++ b/crates/aingle/src/sweettest/sweet_network.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use kitsune_p2p::KitsuneP2pConfig; /// Helper for constructing common kitsune networks diff --git a/crates/aingle/src/sweettest/sweet_saf.rs b/crates/aingle/src/sweettest/sweet_saf.rs index 5f45bbec..ac8e7526 100644 --- a/crates/aingle/src/sweettest/sweet_saf.rs +++ b/crates/aingle/src/sweettest/sweet_saf.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_types::prelude::*; use std::path::Path; diff --git a/crates/aingle/src/sweettest/sweet_zome.rs b/crates/aingle/src/sweettest/sweet_zome.rs index b028c56f..86c65907 100644 --- a/crates/aingle/src/sweettest/sweet_zome.rs +++ b/crates/aingle/src/sweettest/sweet_zome.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; /// A reference to a Zome in a Cell created by a SweetConductor installation function. diff --git a/crates/aingle/src/test_utils.rs b/crates/aingle/src/test_utils.rs index 86512a09..c0c8384f 100644 --- a/crates/aingle/src/test_utils.rs +++ b/crates/aingle/src/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Utils for AIngle tests use crate::conductor::api::RealAppInterfaceApi; diff --git a/crates/aingle/src/test_utils/conductor_setup.rs b/crates/aingle/src/test_utils/conductor_setup.rs index fe362604..9b526ebc 100644 --- a/crates/aingle/src/test_utils/conductor_setup.rs +++ b/crates/aingle/src/test_utils/conductor_setup.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use super::host_fn_caller::HostFnCaller; diff --git a/crates/aingle/src/test_utils/host_fn_caller.rs b/crates/aingle/src/test_utils/host_fn_caller.rs index 8f1b57a9..b80a989c 100644 --- a/crates/aingle/src/test_utils/host_fn_caller.rs +++ b/crates/aingle/src/test_utils/host_fn_caller.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use crate::conductor::api::CellConductorApi; diff --git a/crates/aingle/src/test_utils/test_conductor/test_handle.rs b/crates/aingle/src/test_utils/test_conductor/test_handle.rs index e3b6d363..1d9ea544 100644 --- a/crates/aingle/src/test_utils/test_conductor/test_handle.rs +++ b/crates/aingle/src/test_utils/test_conductor/test_handle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A wrapper around ConductorHandle with more convenient methods for testing // TODO [ B-03669 ] move to own crate diff --git a/crates/aingle/src/test_utils/wait_for_any.rs b/crates/aingle/src/test_utils/wait_for_any.rs index 09b55856..15f500ad 100644 --- a/crates/aingle/src/test_utils/wait_for_any.rs +++ b/crates/aingle/src/test_utils/wait_for_any.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::time::Duration; /// Wait for any condition with custom wait parameters and assertions. diff --git a/crates/aingle/tests/agent_scaling.rs b/crates/aingle/tests/agent_scaling.rs index 1088919b..db09f6fe 100644 --- a/crates/aingle/tests/agent_scaling.rs +++ b/crates/aingle/tests/agent_scaling.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![cfg(feature = "test_utils")] use adk::prelude::Links; diff --git a/crates/aingle/tests/authored_test.rs b/crates/aingle/tests/authored_test.rs index 92b1b89c..5a44d746 100644 --- a/crates/aingle/tests/authored_test.rs +++ b/crates/aingle/tests/authored_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::convert::TryFrom; use std::convert::TryInto; use std::time::Duration; diff --git a/crates/aingle/tests/inline_zome_spec.rs b/crates/aingle/tests/inline_zome_spec.rs index 136a4667..b64aeb0a 100644 --- a/crates/aingle/tests/inline_zome_spec.rs +++ b/crates/aingle/tests/inline_zome_spec.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![cfg(feature = "test_utils")] use std::sync::Arc; diff --git a/crates/aingle/tests/multi_conductor.rs b/crates/aingle/tests/multi_conductor.rs index fd8361de..014fe296 100644 --- a/crates/aingle/tests/multi_conductor.rs +++ b/crates/aingle/tests/multi_conductor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; use aingle::conductor::config::ConductorConfig; use aingle::sweettest::SweetNetwork; diff --git a/crates/aingle/tests/network_tests.rs b/crates/aingle/tests/network_tests.rs index 0a2746bf..1f9e4ac7 100644 --- a/crates/aingle/tests/network_tests.rs +++ b/crates/aingle/tests/network_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![cfg(feature = "test_utils")] #![cfg(todo_redo_old_tests)] #![allow(unused_imports)] diff --git a/crates/aingle/tests/ser_regression.rs b/crates/aingle/tests/ser_regression.rs index 1a7e4a27..9157be1d 100644 --- a/crates/aingle/tests/ser_regression.rs +++ b/crates/aingle/tests/ser_regression.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(deprecated)] use ::ai_fixt::prelude::*; diff --git a/crates/aingle/tests/sgd_arc.rs b/crates/aingle/tests/sgd_arc.rs index b10996e0..210d0217 100644 --- a/crates/aingle/tests/sgd_arc.rs +++ b/crates/aingle/tests/sgd_arc.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle::sweettest::SweetAgents; use aingle::sweettest::SweetConductor; use aingle_keystore::KeystoreSender; diff --git a/crates/aingle/tests/speed_tests.rs b/crates/aingle/tests/speed_tests.rs index d34760c8..8eb3de4a 100644 --- a/crates/aingle/tests/speed_tests.rs +++ b/crates/aingle/tests/speed_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Speed tests //! These are designed to diagnose performance issues from a macro level. //! They are not intended to detect performance regressions or to be run in CI. diff --git a/crates/aingle/tests/test_cli.rs b/crates/aingle/tests/test_cli.rs index 0b152ca9..81dfba7c 100644 --- a/crates/aingle/tests/test_cli.rs +++ b/crates/aingle/tests/test_cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![cfg(test)] use assert_cmd::prelude::*; diff --git a/crates/aingle/tests/test_utils.rs b/crates/aingle/tests/test_utils.rs index bc1fe564..5ac6c24b 100644 --- a/crates/aingle/tests/test_utils.rs +++ b/crates/aingle/tests/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle::conductor::ConductorHandle; use aingle_websocket::WebsocketReceiver; use aingle_websocket::WebsocketSender; diff --git a/crates/aingle/tests/websocket.rs b/crates/aingle/tests/websocket.rs index 1aaf7470..6d7ca165 100644 --- a/crates/aingle/tests/websocket.rs +++ b/crates/aingle/tests/websocket.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ::ai_fixt::prelude::*; use adk::prelude::RemoteSignal; use aingle::sweettest::SweetAgents; diff --git a/crates/aingle_ai/Cargo.toml b/crates/aingle_ai/Cargo.toml index 22e790a1..a087fc5a 100644 --- a/crates/aingle_ai/Cargo.toml +++ b/crates/aingle_ai/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_ai" -version = "0.1.0" -description = "AI integration layer for AIngle - Titans Memory, Nested Learning, HOPE Agents" -license = "Apache-2.0" +version = "0.6.3" +description = "AI integration layer for AIngle - Ineru, Nested Learning, Kaneru" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_ai" @@ -41,11 +41,11 @@ blake2 = "0.10" chrono = { version = "0.4", features = ["serde"] } # Configuration -toml = "0.8" +toml = "0.9" # Optional: Full ML capabilities (disabled by default for IoT) [dependencies.candle-core] -version = "0.4" +version = "0.9" optional = true [dependencies.candle-nn] diff --git a/crates/aingle_ai/src/config.rs b/crates/aingle_ai/src/config.rs index ced1f43e..9b55a283 100644 --- a/crates/aingle_ai/src/config.rs +++ b/crates/aingle_ai/src/config.rs @@ -1,21 +1,24 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Global AI configuration -use crate::hope::HopeConfig; +use crate::kaneru::KaneruConfig; use crate::nested_learning::NestedConfig; -use crate::titans::TitansConfig; +use crate::ineru::IneruConfig; use serde::{Deserialize, Serialize}; /// Global AI configuration for AIngle nodes #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AiConfig { - /// Titans Memory configuration - pub titans: TitansConfig, + /// Ineru memory configuration + pub titans: IneruConfig, /// Nested Learning configuration pub nested_learning: NestedConfig, - /// HOPE Agent configuration - pub hope: HopeConfig, + /// Kaneru Agent configuration + pub kaneru: KaneruConfig, /// Enable predictive validation pub predictive_validation: bool, @@ -30,9 +33,9 @@ pub struct AiConfig { impl Default for AiConfig { fn default() -> Self { Self { - titans: TitansConfig::default(), + titans: IneruConfig::default(), nested_learning: NestedConfig::default(), - hope: HopeConfig::default(), + kaneru: KaneruConfig::default(), predictive_validation: true, adaptive_consensus: true, iot_mode: false, @@ -44,9 +47,9 @@ impl AiConfig { /// Create IoT-optimized configuration pub fn iot() -> Self { Self { - titans: TitansConfig::iot(), + titans: IneruConfig::iot(), nested_learning: NestedConfig::iot(), - hope: HopeConfig::iot(), + kaneru: KaneruConfig::iot(), predictive_validation: false, // Too expensive for IoT adaptive_consensus: true, iot_mode: true, @@ -56,9 +59,9 @@ impl AiConfig { /// Create full-power configuration for servers pub fn full_power() -> Self { Self { - titans: TitansConfig::full_power(), + titans: IneruConfig::full_power(), nested_learning: NestedConfig::full_power(), - hope: HopeConfig::full_power(), + kaneru: KaneruConfig::full_power(), predictive_validation: true, adaptive_consensus: true, iot_mode: false, @@ -79,7 +82,7 @@ impl AiConfig { pub fn validate(&self) -> Result<(), String> { self.titans.validate()?; self.nested_learning.validate()?; - self.hope.validate()?; + self.kaneru.validate()?; Ok(()) } } diff --git a/crates/aingle_ai/src/emergent/adaptive_consensus.rs b/crates/aingle_ai/src/emergent/adaptive_consensus.rs index 63e7aa2d..1ddc2f98 100644 --- a/crates/aingle_ai/src/emergent/adaptive_consensus.rs +++ b/crates/aingle_ai/src/emergent/adaptive_consensus.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Adaptive Consensus //! //! Adjust consensus level based on transaction importance. diff --git a/crates/aingle_ai/src/emergent/mod.rs b/crates/aingle_ai/src/emergent/mod.rs index 2626599f..d591a5dc 100644 --- a/crates/aingle_ai/src/emergent/mod.rs +++ b/crates/aingle_ai/src/emergent/mod.rs @@ -1,7 +1,10 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Emergent Capabilities //! -//! Higher-level AI capabilities that emerge from combining Titans Memory, -//! Nested Learning, and HOPE Agents. +//! Higher-level AI capabilities that emerge from combining Ineru, +//! Nested Learning, and Kaneru. //! //! ## Components //! @@ -15,13 +18,13 @@ pub use adaptive_consensus::AdaptiveConsensus; pub use predictive_validator::PredictiveValidator; use crate::nested_learning::NestedLearning; -use crate::titans::TitansMemory; +use crate::ineru::IneruMemory; use crate::types::{AiTransaction, ConsensusLevel, ValidationPrediction}; /// Unified AI layer combining all capabilities pub struct AiLayer { - /// Titans Memory for pattern learning - titans: TitansMemory, + /// Ineru memory for pattern learning + ineru: IneruMemory, /// Nested Learning for optimization nested: NestedLearning, @@ -37,10 +40,10 @@ impl AiLayer { /// Create a new AI layer with default configuration pub fn new() -> Self { use crate::nested_learning::NestedConfig; - use crate::titans::TitansConfig; + use crate::ineru::IneruConfig; Self { - titans: TitansMemory::new(TitansConfig::default()), + ineru: IneruMemory::new(IneruConfig::default()), nested: NestedLearning::new(NestedConfig::default()), predictor: PredictiveValidator::new(), consensus: AdaptiveConsensus::new(), @@ -49,14 +52,14 @@ impl AiLayer { /// Process a transaction through the full AI pipeline pub fn process(&mut self, tx: &AiTransaction) -> AiProcessResult { - // 1. Process through Titans memory - let titans_result = self.titans.process(tx).ok(); + // 1. Process through Ineru memory + let ineru_result = self.ineru.process(tx).ok(); // 2. Process through Nested Learning let nested_result = self.nested.process(tx).ok(); // 3. Get validation prediction - let prediction = self.predictor.predict(tx, &self.titans, &self.nested); + let prediction = self.predictor.predict(tx, &self.ineru, &self.nested); // 4. Determine consensus level let consensus_level = self.consensus.determine_level(tx, &prediction); @@ -64,7 +67,7 @@ impl AiLayer { AiProcessResult { prediction, consensus_level, - stored_pattern: titans_result.map(|r| r.stored_long_term).unwrap_or(false), + stored_pattern: ineru_result.map(|r| r.stored_long_term).unwrap_or(false), validation_strategy: nested_result.map(|r| r.strategy), } } @@ -72,7 +75,7 @@ impl AiLayer { /// Query for similar patterns pub fn query_similar(&self, tx: &AiTransaction, limit: usize) -> Vec { let pattern = tx.to_pattern(); - self.titans + self.ineru .query(&pattern, limit) .into_iter() .map(|m| PatternMatch { @@ -84,12 +87,12 @@ impl AiLayer { /// Get AI layer statistics pub fn stats(&self) -> AiLayerStats { - let titans_stats = self.titans.stats(); + let ineru_stats = self.ineru.stats(); let nested_stats = self.nested.stats(); AiLayerStats { - titans_short_term_size: titans_stats.short_term_size, - titans_long_term_size: titans_stats.long_term_size, + ineru_short_term_size: ineru_stats.short_term_size, + ineru_long_term_size: ineru_stats.long_term_size, nested_tx_count: nested_stats.tx_count, nested_block_count: nested_stats.block_count, } @@ -127,10 +130,10 @@ pub struct PatternMatch { /// AI layer statistics #[derive(Debug, Clone)] pub struct AiLayerStats { - /// Titans short-term memory size - pub titans_short_term_size: usize, - /// Titans long-term memory size - pub titans_long_term_size: usize, + /// Ineru short-term memory size + pub ineru_short_term_size: usize, + /// Ineru long-term memory size + pub ineru_long_term_size: usize, /// Nested learning transaction count pub nested_tx_count: u64, /// Nested learning block count diff --git a/crates/aingle_ai/src/emergent/predictive_validator.rs b/crates/aingle_ai/src/emergent/predictive_validator.rs index 8cf85ad2..d96d7f23 100644 --- a/crates/aingle_ai/src/emergent/predictive_validator.rs +++ b/crates/aingle_ai/src/emergent/predictive_validator.rs @@ -1,9 +1,12 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Predictive Validator //! //! Predict validation outcome before full validation. use crate::nested_learning::NestedLearning; -use crate::titans::TitansMemory; +use crate::ineru::IneruMemory; use crate::types::{AiTransaction, ValidationPrediction}; /// Predict validation outcome before full validation @@ -32,12 +35,12 @@ impl PredictiveValidator { pub fn predict( &self, tx: &AiTransaction, - titans: &TitansMemory, + ineru: &IneruMemory, nested: &NestedLearning, ) -> ValidationPrediction { // Use Titans memory for pattern matching let pattern = tx.to_pattern(); - let similar_patterns = titans.query(&pattern, 100); + let similar_patterns = ineru.query(&pattern, 100); // Use Nested Learning for complexity estimation let complexity = { @@ -158,7 +161,7 @@ pub struct PredictionAccuracy { mod tests { use super::*; use crate::nested_learning::NestedConfig; - use crate::titans::TitansConfig; + use crate::ineru::IneruConfig; fn make_test_tx(id: u8) -> AiTransaction { AiTransaction { @@ -174,11 +177,11 @@ mod tests { #[test] fn test_predictive_validator() { let validator = PredictiveValidator::new(); - let titans = TitansMemory::new(TitansConfig::default()); + let ineru = IneruMemory::new(IneruConfig::default()); let nested = NestedLearning::new(NestedConfig::default()); let tx = make_test_tx(1); - let prediction = validator.predict(&tx, &titans, &nested); + let prediction = validator.predict(&tx, &ineru, &nested); assert!(prediction.confidence >= 0.0 && prediction.confidence <= 1.0); assert!(prediction.estimated_time_ms > 0); diff --git a/crates/aingle_ai/src/error.rs b/crates/aingle_ai/src/error.rs index 75173665..976ef679 100644 --- a/crates/aingle_ai/src/error.rs +++ b/crates/aingle_ai/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for the AI module use thiserror::Error; diff --git a/crates/aingle_ai/src/titans/config.rs b/crates/aingle_ai/src/ineru/config.rs similarity index 87% rename from crates/aingle_ai/src/titans/config.rs rename to crates/aingle_ai/src/ineru/config.rs index 69cb0b1f..b884d75e 100644 --- a/crates/aingle_ai/src/titans/config.rs +++ b/crates/aingle_ai/src/ineru/config.rs @@ -1,10 +1,13 @@ -//! Titans Memory configuration +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Ineru memory configuration use serde::{Deserialize, Serialize}; -/// Configuration for Titans Memory system +/// Configuration for Ineru memory system #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TitansConfig { +pub struct IneruConfig { /// Short-term window size (number of transactions) pub window_size: usize, @@ -34,7 +37,7 @@ pub struct TitansConfig { pub compression_ratio: f32, } -impl Default for TitansConfig { +impl Default for IneruConfig { fn default() -> Self { Self { window_size: 1000, @@ -50,7 +53,7 @@ impl Default for TitansConfig { } } -impl TitansConfig { +impl IneruConfig { /// IoT-optimized configuration (minimal memory usage) pub fn iot() -> Self { Self { @@ -119,35 +122,35 @@ mod tests { #[test] fn test_default_config() { - let config = TitansConfig::default(); + let config = IneruConfig::default(); assert!(config.validate().is_ok()); } #[test] fn test_iot_config() { - let config = TitansConfig::iot(); + let config = IneruConfig::iot(); assert!(config.validate().is_ok()); - assert!(config.window_size < TitansConfig::default().window_size); + assert!(config.window_size < IneruConfig::default().window_size); } #[test] fn test_validation() { - let mut config = TitansConfig::default(); + let mut config = IneruConfig::default(); config.window_size = 0; assert!(config.validate().is_err()); - config = TitansConfig::default(); + config = IneruConfig::default(); config.surprise_threshold = 1.5; assert!(config.validate().is_err()); } #[test] fn test_memory_estimation() { - let config = TitansConfig::default(); + let config = IneruConfig::default(); let bytes = config.estimated_memory_bytes(); assert!(bytes > 0); - let iot_config = TitansConfig::iot(); + let iot_config = IneruConfig::iot(); let iot_bytes = iot_config.estimated_memory_bytes(); assert!(iot_bytes < bytes); } diff --git a/crates/aingle_ai/src/titans/long_term.rs b/crates/aingle_ai/src/ineru/long_term.rs similarity index 98% rename from crates/aingle_ai/src/titans/long_term.rs rename to crates/aingle_ai/src/ineru/long_term.rs index 99de1093..f4502b98 100644 --- a/crates/aingle_ai/src/titans/long_term.rs +++ b/crates/aingle_ai/src/ineru/long_term.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Long-Term Memory implementation //! //! Neural compression of historical patterns with surprise-based updates. diff --git a/crates/aingle_ai/src/titans/mod.rs b/crates/aingle_ai/src/ineru/mod.rs similarity index 90% rename from crates/aingle_ai/src/titans/mod.rs rename to crates/aingle_ai/src/ineru/mod.rs index 5974a93a..0393fba6 100644 --- a/crates/aingle_ai/src/titans/mod.rs +++ b/crates/aingle_ai/src/ineru/mod.rs @@ -1,4 +1,7 @@ -//! # Titans Memory Layer +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! # Ineru Memory Layer //! //! Implementation of dual memory system based on the Titans paper (arXiv 2501.00663). //! @@ -11,11 +14,11 @@ //! ## Example //! //! ```rust,no_run -//! use aingle_ai::titans::{TitansMemory, TitansConfig}; +//! use aingle_ai::ineru::{IneruMemory, IneruConfig}; //! use aingle_ai::AiTransaction; //! -//! let config = TitansConfig::default(); -//! let mut memory = TitansMemory::new(config); +//! let config = IneruConfig::default(); +//! let mut memory = IneruMemory::new(config); //! //! // Process a transaction //! // let result = memory.process(&tx); @@ -26,7 +29,7 @@ mod long_term; mod short_term; mod surprise_gate; -pub use config::TitansConfig; +pub use config::IneruConfig; pub use long_term::{LongTermMemory, MemoryBank}; pub use short_term::ShortTermMemory; pub use surprise_gate::SurpriseGate; @@ -39,12 +42,12 @@ use parking_lot::RwLock; use std::sync::Arc; use tracing::{debug, trace}; -/// Titans Memory System for AIngle nodes +/// Ineru Memory System for AIngle nodes /// /// Implements dual memory architecture: /// - Short-term: Fast access to recent transactions /// - Long-term: Compressed historical patterns -pub struct TitansMemory { +pub struct IneruMemory { /// Short-term memory: Recent transactions (attention-based) short_term: Arc>, @@ -55,12 +58,12 @@ pub struct TitansMemory { surprise_gate: SurpriseGate, /// Configuration - config: TitansConfig, + config: IneruConfig, } -impl TitansMemory { - /// Create a new Titans memory system - pub fn new(config: TitansConfig) -> Self { +impl IneruMemory { + /// Create a new Ineru memory system + pub fn new(config: IneruConfig) -> Self { let short_term = ShortTermMemory::new(config.window_size); let long_term = LongTermMemory::new(config.memory_capacity, config.embedding_dim); let surprise_gate = SurpriseGate::new(config.surprise_threshold); @@ -81,7 +84,7 @@ impl TitansMemory { /// 4. Updates long-term memory if threshold exceeded pub fn process(&mut self, tx: &AiTransaction) -> AiResult { let pattern = tx.to_pattern(); - trace!(hash = ?tx.hash, "Processing transaction through Titans memory"); + trace!(hash = ?tx.hash, "Processing transaction through Ineru memory"); // 1. Add to short-term memory { @@ -288,9 +291,9 @@ mod tests { } #[test] - fn test_titans_memory_basic() { - let config = TitansConfig::default(); - let mut memory = TitansMemory::new(config); + fn test_ineru_memory_basic() { + let config = IneruConfig::default(); + let mut memory = IneruMemory::new(config); let tx = make_test_tx(1); let result = memory.process(&tx).unwrap(); @@ -300,9 +303,9 @@ mod tests { } #[test] - fn test_titans_memory_query() { - let config = TitansConfig::default(); - let mut memory = TitansMemory::new(config); + fn test_ineru_memory_query() { + let config = IneruConfig::default(); + let mut memory = IneruMemory::new(config); // Add some transactions for i in 0..10 { @@ -320,12 +323,12 @@ mod tests { #[test] fn test_anomaly_detection() { - let config = TitansConfig { + let config = IneruConfig { anomaly_detection: true, anomaly_threshold: 0.5, - ..TitansConfig::default() + ..IneruConfig::default() }; - let mut memory = TitansMemory::new(config); + let mut memory = IneruMemory::new(config); // Train on similar transactions for i in 0..20 { @@ -354,8 +357,8 @@ mod tests { #[test] fn test_memory_stats() { - let config = TitansConfig::default(); - let mut memory = TitansMemory::new(config.clone()); + let config = IneruConfig::default(); + let mut memory = IneruMemory::new(config.clone()); let stats = memory.stats(); assert_eq!(stats.short_term_size, 0); diff --git a/crates/aingle_ai/src/titans/short_term.rs b/crates/aingle_ai/src/ineru/short_term.rs similarity index 98% rename from crates/aingle_ai/src/titans/short_term.rs rename to crates/aingle_ai/src/ineru/short_term.rs index e8cce017..a8691624 100644 --- a/crates/aingle_ai/src/titans/short_term.rs +++ b/crates/aingle_ai/src/ineru/short_term.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Short-Term Memory implementation //! //! Sliding window of recent transactions with attention-based weighting. diff --git a/crates/aingle_ai/src/titans/surprise_gate.rs b/crates/aingle_ai/src/ineru/surprise_gate.rs similarity index 97% rename from crates/aingle_ai/src/titans/surprise_gate.rs rename to crates/aingle_ai/src/ineru/surprise_gate.rs index 1de06694..48a21b8f 100644 --- a/crates/aingle_ai/src/titans/surprise_gate.rs +++ b/crates/aingle_ai/src/ineru/surprise_gate.rs @@ -1,9 +1,12 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Surprise Gate implementation //! //! Controls when to update long-term memory based on "surprise" metric. //! Inspired by the Titans paper's surprise-gated memory updates. -use crate::titans::LongTermMemory; +use crate::ineru::LongTermMemory; use crate::types::Pattern; use std::collections::VecDeque; diff --git a/crates/aingle_ai/src/hope/config.rs b/crates/aingle_ai/src/kaneru/config.rs similarity index 90% rename from crates/aingle_ai/src/hope/config.rs rename to crates/aingle_ai/src/kaneru/config.rs index ae52e42c..11936081 100644 --- a/crates/aingle_ai/src/hope/config.rs +++ b/crates/aingle_ai/src/kaneru/config.rs @@ -1,10 +1,13 @@ -//! HOPE Agent configuration +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Kaneru Agent configuration use serde::{Deserialize, Serialize}; -/// Configuration for HOPE Agent +/// Configuration for Kaneru Agent #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HopeConfig { +pub struct KaneruConfig { /// Enable self-modification pub self_modification_enabled: bool, @@ -41,7 +44,7 @@ pub enum SafetyLevel { Permissive, } -impl Default for HopeConfig { +impl Default for KaneruConfig { fn default() -> Self { Self { self_modification_enabled: true, @@ -56,7 +59,7 @@ impl Default for HopeConfig { } } -impl HopeConfig { +impl KaneruConfig { /// IoT-optimized configuration pub fn iot() -> Self { Self { @@ -112,13 +115,13 @@ mod tests { #[test] fn test_default_config() { - let config = HopeConfig::default(); + let config = KaneruConfig::default(); assert!(config.validate().is_ok()); } #[test] fn test_iot_config() { - let config = HopeConfig::iot(); + let config = KaneruConfig::iot(); assert!(config.validate().is_ok()); assert!(!config.self_modification_enabled); } diff --git a/crates/aingle_ai/src/hope/context_learner.rs b/crates/aingle_ai/src/kaneru/context_learner.rs similarity index 97% rename from crates/aingle_ai/src/hope/context_learner.rs rename to crates/aingle_ai/src/kaneru/context_learner.rs index a4624577..925acfba 100644 --- a/crates/aingle_ai/src/hope/context_learner.rs +++ b/crates/aingle_ai/src/kaneru/context_learner.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Context Learner implementation //! //! Infinite in-context learning without forgetting. diff --git a/crates/aingle_ai/src/hope/continuum_memory.rs b/crates/aingle_ai/src/kaneru/continuum_memory.rs similarity index 98% rename from crates/aingle_ai/src/hope/continuum_memory.rs rename to crates/aingle_ai/src/kaneru/continuum_memory.rs index 2f244b77..4104c47e 100644 --- a/crates/aingle_ai/src/hope/continuum_memory.rs +++ b/crates/aingle_ai/src/kaneru/continuum_memory.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Continuum Memory implementation //! //! Non-discrete memory system with smooth interpolation. diff --git a/crates/aingle_ai/src/hope/mod.rs b/crates/aingle_ai/src/kaneru/mod.rs similarity index 91% rename from crates/aingle_ai/src/hope/mod.rs rename to crates/aingle_ai/src/kaneru/mod.rs index f7ffa54d..22b90603 100644 --- a/crates/aingle_ai/src/hope/mod.rs +++ b/crates/aingle_ai/src/kaneru/mod.rs @@ -1,6 +1,9 @@ -//! # HOPE Agent Layer +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! # Kaneru Agent Layer //! -//! Higher Order Program Evolution - Self-modifying nodes with continual learning. +//! Unified Multi-Agent Execution System - Self-modifying nodes with continual learning. //! //! ## Components //! @@ -11,7 +14,7 @@ //! //! ## Safety //! -//! HOPE agents have strict safety bounds that prevent: +//! Kaneru agents have strict safety bounds that prevent: //! - Modification of cryptographic code //! - Modification of consensus rules //! - Modification of identity handling @@ -19,10 +22,10 @@ //! ## Example //! //! ```rust,no_run -//! use aingle_ai::hope::{HopeAgent, HopeConfig}; +//! use aingle_ai::kaneru::{KaneruAgent, KaneruConfig}; //! -//! let config = HopeConfig::default(); -//! let mut agent = HopeAgent::new(config); +//! let config = KaneruConfig::default(); +//! let mut agent = KaneruAgent::new(config); //! //! // Process experience //! // agent.process_experience(&experience); @@ -34,7 +37,7 @@ mod continuum_memory; mod reconfigurator; mod self_modifier; -pub use config::HopeConfig; +pub use config::KaneruConfig; pub use context_learner::ContextLearner; pub use continuum_memory::ContinuumMemory; pub use reconfigurator::{AutoReconfigurator, NodeConfig}; @@ -46,8 +49,8 @@ use parking_lot::RwLock; use std::sync::Arc; use tracing::{debug, info}; -/// HOPE Agent: Self-modifying node with continual learning -pub struct HopeAgent { +/// Kaneru Agent: Self-modifying node with continual learning +pub struct KaneruAgent { /// Continuum memory (non-discrete) memory: Arc>, @@ -61,15 +64,15 @@ pub struct HopeAgent { reconfigurator: Arc>, /// Configuration - config: HopeConfig, + config: KaneruConfig, /// Agent state state: AgentState, } -impl HopeAgent { - /// Create a new HOPE agent - pub fn new(config: HopeConfig) -> Self { +impl KaneruAgent { + /// Create a new Kaneru agent + pub fn new(config: KaneruConfig) -> Self { Self { memory: Arc::new(RwLock::new(ContinuumMemory::new(config.memory_dim))), modifier: Arc::new(RwLock::new(SelfModifier::new(&config))), @@ -157,7 +160,7 @@ impl HopeAgent { if let ReconfigResult::Changed(ref new_config) = result { info!( mode = ?new_config.mode, - "HOPE Agent reconfigured" + "Kaneru Agent reconfigured" ); self.apply_node_config(new_config); } @@ -392,9 +395,9 @@ mod tests { } #[test] - fn test_hope_agent_basic() { - let config = HopeConfig::default(); - let mut agent = HopeAgent::new(config); + fn test_kaneru_agent_basic() { + let config = KaneruConfig::default(); + let mut agent = KaneruAgent::new(config); let exp = make_experience(1); let result = agent.process_experience(&exp).unwrap(); @@ -403,9 +406,9 @@ mod tests { } #[test] - fn test_hope_agent_query() { - let config = HopeConfig::default(); - let mut agent = HopeAgent::new(config); + fn test_kaneru_agent_query() { + let config = KaneruConfig::default(); + let mut agent = KaneruAgent::new(config); // Add some experiences for i in 0..5 { @@ -443,8 +446,8 @@ mod tests { #[test] fn test_reconfiguration() { - let config = HopeConfig::default(); - let mut agent = HopeAgent::new(config); + let config = KaneruConfig::default(); + let mut agent = KaneruAgent::new(config); let resources = Resources { memory_available: 5 * 1024 * 1024, // 5MB - Critical diff --git a/crates/aingle_ai/src/hope/reconfigurator.rs b/crates/aingle_ai/src/kaneru/reconfigurator.rs similarity index 97% rename from crates/aingle_ai/src/hope/reconfigurator.rs rename to crates/aingle_ai/src/kaneru/reconfigurator.rs index d8034f5d..5b6ea352 100644 --- a/crates/aingle_ai/src/hope/reconfigurator.rs +++ b/crates/aingle_ai/src/kaneru/reconfigurator.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Auto-Reconfigurator implementation //! //! Resource-aware automatic reconfiguration. diff --git a/crates/aingle_ai/src/hope/self_modifier.rs b/crates/aingle_ai/src/kaneru/self_modifier.rs similarity index 97% rename from crates/aingle_ai/src/hope/self_modifier.rs rename to crates/aingle_ai/src/kaneru/self_modifier.rs index 7af1bd6c..2200b30b 100644 --- a/crates/aingle_ai/src/hope/self_modifier.rs +++ b/crates/aingle_ai/src/kaneru/self_modifier.rs @@ -1,9 +1,12 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Self-Modifier implementation //! //! Behavior modification with safety bounds. use super::config::SafetyLevel; -use super::{HopeConfig, Outcome}; +use super::{KaneruConfig, Outcome}; /// Self-Modifier: Node that modifies its own behavior pub struct SelfModifier { @@ -28,7 +31,7 @@ pub struct SelfModifier { impl SelfModifier { /// Create new self-modifier - pub fn new(config: &HopeConfig) -> Self { + pub fn new(config: &KaneruConfig) -> Self { Self { rules: Vec::new(), history: Vec::new(), @@ -319,7 +322,7 @@ mod tests { #[test] fn test_self_modifier_basic() { - let config = HopeConfig::default(); + let config = KaneruConfig::default(); let mut modifier = SelfModifier::new(&config); let outcome = Outcome { @@ -356,7 +359,7 @@ mod tests { #[test] fn test_rule_evolution() { - let mut config = HopeConfig::default(); + let mut config = KaneruConfig::default(); config.max_rules = 10; let mut modifier = SelfModifier::new(&config); diff --git a/crates/aingle_ai/src/lib.rs b/crates/aingle_ai/src/lib.rs index add14579..1be71d33 100644 --- a/crates/aingle_ai/src/lib.rs +++ b/crates/aingle_ai/src/lib.rs @@ -1,10 +1,13 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # AIngle AI Integration Layer //! //! This crate provides AI capabilities for AIngle nodes, implementing: //! -//! - **Titans Memory**: Dual memory system (short-term + long-term) for pattern learning +//! - **Ineru**: Dual memory system (short-term + long-term) for pattern learning //! - **Nested Learning**: Multi-level optimization for consensus and validation -//! - **HOPE Agents**: Self-modifying nodes with continual learning +//! - **Kaneru**: Self-modifying nodes with continual learning //! - **Emergent Capabilities**: Predictive validation, adaptive consensus //! //! ## Architecture @@ -17,7 +20,7 @@ //! │ //! ▼ //! ┌─────────────────────────────────────────────────────────────┐ -//! │ TITANS MEMORY LAYER │ +//! │ INERU MEMORY LAYER │ //! │ (Dual memory per node) │ //! │ ┌──────────────────┐ ┌──────────────────┐ │ //! │ │ SHORT-TERM │◄──►│ LONG-TERM │ │ @@ -27,7 +30,7 @@ //! │ //! ▼ //! ┌─────────────────────────────────────────────────────────────┐ -//! │ HOPE AGENT LAYER │ +//! │ KANERU AGENT LAYER │ //! │ (Self-modifying nodes) │ //! └─────────────────────────────────────────────────────────────┘ //! ``` @@ -41,11 +44,11 @@ //! ## Example //! //! ```rust,no_run -//! use aingle_ai::titans::{TitansMemory, TitansConfig}; +//! use aingle_ai::ineru::{IneruMemory, IneruConfig}; //! -//! // Create Titans memory system -//! let config = TitansConfig::default(); -//! let mut memory = TitansMemory::new(config); +//! // Create Ineru memory system +//! let config = IneruConfig::default(); +//! let mut memory = IneruMemory::new(config); //! //! // Process transactions //! // let result = memory.process(&transaction); @@ -55,9 +58,9 @@ #![warn(clippy::all)] pub mod emergent; -pub mod hope; +pub mod kaneru; pub mod nested_learning; -pub mod titans; +pub mod ineru; mod config; mod error; @@ -72,7 +75,7 @@ pub mod prelude { pub use crate::config::AiConfig; pub use crate::emergent::{AdaptiveConsensus, PredictiveValidator}; pub use crate::error::{AiError, AiResult}; - pub use crate::hope::{HopeAgent, HopeConfig}; + pub use crate::kaneru::{KaneruAgent, KaneruConfig}; pub use crate::nested_learning::{NestedConfig, NestedLearning}; - pub use crate::titans::{LongTermMemory, ShortTermMemory, TitansConfig, TitansMemory}; + pub use crate::ineru::{LongTermMemory, ShortTermMemory, IneruConfig, IneruMemory}; } diff --git a/crates/aingle_ai/src/nested_learning/config.rs b/crates/aingle_ai/src/nested_learning/config.rs index dd3dd82e..e822f511 100644 --- a/crates/aingle_ai/src/nested_learning/config.rs +++ b/crates/aingle_ai/src/nested_learning/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Nested Learning configuration use serde::{Deserialize, Serialize}; diff --git a/crates/aingle_ai/src/nested_learning/meta_level.rs b/crates/aingle_ai/src/nested_learning/meta_level.rs index 6d757518..21d0c258 100644 --- a/crates/aingle_ai/src/nested_learning/meta_level.rs +++ b/crates/aingle_ai/src/nested_learning/meta_level.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Meta-Level optimization //! //! Global network parameters with slow updates (~1000 blocks). diff --git a/crates/aingle_ai/src/nested_learning/mod.rs b/crates/aingle_ai/src/nested_learning/mod.rs index df29afc6..d661db94 100644 --- a/crates/aingle_ai/src/nested_learning/mod.rs +++ b/crates/aingle_ai/src/nested_learning/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Nested Learning Layer //! //! Multi-level optimization system based on Nested Learning paper (OpenReview nbMeRvNb7A). diff --git a/crates/aingle_ai/src/nested_learning/optimizer_level.rs b/crates/aingle_ai/src/nested_learning/optimizer_level.rs index 687d46d7..d25e0dae 100644 --- a/crates/aingle_ai/src/nested_learning/optimizer_level.rs +++ b/crates/aingle_ai/src/nested_learning/optimizer_level.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Optimizer-Level optimization //! //! Validation strategies with medium-frequency updates (~100 transactions). diff --git a/crates/aingle_ai/src/nested_learning/transaction_level.rs b/crates/aingle_ai/src/nested_learning/transaction_level.rs index 9a12649a..fd38a931 100644 --- a/crates/aingle_ai/src/nested_learning/transaction_level.rs +++ b/crates/aingle_ai/src/nested_learning/transaction_level.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Transaction-Level processing //! //! Fast feature extraction and classification per transaction. diff --git a/crates/aingle_ai/src/types.rs b/crates/aingle_ai/src/types.rs index e5dbf3c9..502a3d3f 100644 --- a/crates/aingle_ai/src/types.rs +++ b/crates/aingle_ai/src/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Common types for the AI module use serde::{Deserialize, Serialize}; diff --git a/crates/aingle_cascade/Cargo.toml b/crates/aingle_cascade/Cargo.toml index 654e4f9e..5ff14a24 100644 --- a/crates/aingle_cascade/Cargo.toml +++ b/crates/aingle_cascade/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_cascade" version = "0.0.1" description = "Logic for cascading updates to AIngle state and network interaction" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_cascade" @@ -12,7 +12,7 @@ edition = "2018" [dependencies] derive_more = "0.99.3" either = "1.5" -fallible-iterator = "0.2" +fallible-iterator = "0.3" ai_fixt = { version = "^0.0.1", path = "../ai_fixt" } futures = "0.3" ghost_actor = "0.3.0-alpha.1" @@ -39,7 +39,7 @@ mockall = { version = "0.11", optional = true } [dev-dependencies] matches = "0.1" -pretty_assertions = "0.7.2" +pretty_assertions = "1.4" [features] default = ["test_utils"] diff --git a/crates/aingle_cascade/src/agent_activity.rs b/crates/aingle_cascade/src/agent_activity.rs index 4af7031f..e6b1c5a4 100644 --- a/crates/aingle_cascade/src/agent_activity.rs +++ b/crates/aingle_cascade/src/agent_activity.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::collections::HashSet; use super::*; diff --git a/crates/aingle_cascade/src/authority.rs b/crates/aingle_cascade/src/authority.rs index 99831c6c..d023e3b7 100644 --- a/crates/aingle_cascade/src/authority.rs +++ b/crates/aingle_cascade/src/authority.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use self::get_agent_activity_query::hashes::GetAgentActivityQuery; use self::get_entry_ops_query::GetEntryOpsQuery; use self::get_links_ops_query::GetLinksOpsQuery; diff --git a/crates/aingle_cascade/src/authority/get_agent_activity_query.rs b/crates/aingle_cascade/src/authority/get_agent_activity_query.rs index be37b1f6..024574e2 100644 --- a/crates/aingle_cascade/src/authority/get_agent_activity_query.rs +++ b/crates/aingle_cascade/src/authority/get_agent_activity_query.rs @@ -1,2 +1,5 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub mod deterministic; pub mod hashes; diff --git a/crates/aingle_cascade/src/authority/get_agent_activity_query/deterministic.rs b/crates/aingle_cascade/src/authority/get_agent_activity_query/deterministic.rs index 85ae3e93..61c75d2d 100644 --- a/crates/aingle_cascade/src/authority/get_agent_activity_query/deterministic.rs +++ b/crates/aingle_cascade/src/authority/get_agent_activity_query/deterministic.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Query for `deterministic_get_agent_activity`, designed for use in //! validation callbacks. //! diff --git a/crates/aingle_cascade/src/authority/get_agent_activity_query/hashes.rs b/crates/aingle_cascade/src/authority/get_agent_activity_query/hashes.rs index bad3a121..e0009038 100644 --- a/crates/aingle_cascade/src/authority/get_agent_activity_query/hashes.rs +++ b/crates/aingle_cascade/src/authority/get_agent_activity_query/hashes.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_p2p::event::GetActivityOptions; use aingle_sqlite::rusqlite::*; diff --git a/crates/aingle_cascade/src/authority/get_element_query.rs b/crates/aingle_cascade/src/authority/get_element_query.rs index 02a47803..ff61fe35 100644 --- a/crates/aingle_cascade/src/authority/get_element_query.rs +++ b/crates/aingle_cascade/src/authority/get_element_query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::HeaderHash; use aingle_p2p::event::GetOptions; use aingle_sqlite::rusqlite::named_params; diff --git a/crates/aingle_cascade/src/authority/get_entry_ops_query.rs b/crates/aingle_cascade/src/authority/get_entry_ops_query.rs index 4967c0b1..f05f5345 100644 --- a/crates/aingle_cascade/src/authority/get_entry_ops_query.rs +++ b/crates/aingle_cascade/src/authority/get_entry_ops_query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::EntryHash; use aingle_sqlite::rusqlite::named_params; use aingle_sqlite::rusqlite::Row; diff --git a/crates/aingle_cascade/src/authority/get_links_ops_query.rs b/crates/aingle_cascade/src/authority/get_links_ops_query.rs index 92ae1682..746e0803 100644 --- a/crates/aingle_cascade/src/authority/get_links_ops_query.rs +++ b/crates/aingle_cascade/src/authority/get_links_ops_query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::EntryHash; use aingle_sqlite::rusqlite::named_params; use aingle_sqlite::rusqlite::Row; diff --git a/crates/aingle_cascade/src/authority/test.rs b/crates/aingle_cascade/src/authority/test.rs index da460f09..cc0bcf5e 100644 --- a/crates/aingle_cascade/src/authority/test.rs +++ b/crates/aingle_cascade/src/authority/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::authority::handle_get_agent_activity; use crate::test_utils::*; diff --git a/crates/aingle_cascade/src/error.rs b/crates/aingle_cascade/src/error.rs index f0ca3496..ecea3c34 100644 --- a/crates/aingle_cascade/src/error.rs +++ b/crates/aingle_cascade/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::{AnySgdHash, HeaderHash}; use aingle_middleware_bytes::SerializedBytesError; use aingle_p2p::AIngleP2pError; diff --git a/crates/aingle_cascade/src/lib.rs b/crates/aingle_cascade/src/lib.rs index 9a320999..d7732755 100644 --- a/crates/aingle_cascade/src/lib.rs +++ b/crates/aingle_cascade/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Cascade //! ## Retrieve vs Get //! Get checks CRUD metadata before returning an the data diff --git a/crates/aingle_cascade/src/test_utils.rs b/crates/aingle_cascade/src/test_utils.rs index 03e2a245..9e8d07a4 100644 --- a/crates/aingle_cascade/src/test_utils.rs +++ b/crates/aingle_cascade/src/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::authority; use ai_hash::hash_type::AnySgd; use ai_hash::AgentPubKey; diff --git a/crates/aingle_cascade/src/test_utils/activity_test_data.rs b/crates/aingle_cascade/src/test_utils/activity_test_data.rs index dc420e3a..0843b7a8 100644 --- a/crates/aingle_cascade/src/test_utils/activity_test_data.rs +++ b/crates/aingle_cascade/src/test_utils/activity_test_data.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ::ai_fixt::prelude::*; use ai_hash::AgentPubKey; use ai_hash::EntryHash; diff --git a/crates/aingle_cascade/src/test_utils/element_test_data.rs b/crates/aingle_cascade/src/test_utils/element_test_data.rs index 9941897a..3569888c 100644 --- a/crates/aingle_cascade/src/test_utils/element_test_data.rs +++ b/crates/aingle_cascade/src/test_utils/element_test_data.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::EntryHash; use ai_hash::HeaderHash; use aingle_types::header::WireDelete; diff --git a/crates/aingle_cascade/src/test_utils/entry_test_data.rs b/crates/aingle_cascade/src/test_utils/entry_test_data.rs index f68ea904..bd8f1382 100644 --- a/crates/aingle_cascade/src/test_utils/entry_test_data.rs +++ b/crates/aingle_cascade/src/test_utils/entry_test_data.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::EntryHash; use ai_hash::HeaderHash; use aingle_types::header::NewEntryHeader; diff --git a/crates/aingle_cascade/tests/get_activity.rs b/crates/aingle_cascade/tests/get_activity.rs index a2ad34d1..c2c3bfb4 100644 --- a/crates/aingle_cascade/tests/get_activity.rs +++ b/crates/aingle_cascade/tests/get_activity.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_cascade::test_utils::*; use aingle_cascade::Cascade; use aingle_state::prelude::test_cell_env; diff --git a/crates/aingle_cascade/tests/get_entry.rs b/crates/aingle_cascade/tests/get_entry.rs index 9dc14084..2d40e464 100644 --- a/crates/aingle_cascade/tests/get_entry.rs +++ b/crates/aingle_cascade/tests/get_entry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::HasHash; use aingle_cascade::test_utils::*; use aingle_cascade::Cascade; diff --git a/crates/aingle_cascade/tests/get_links.rs b/crates/aingle_cascade/tests/get_links.rs index 24eaa1e4..a4b0bfb6 100644 --- a/crates/aingle_cascade/tests/get_links.rs +++ b/crates/aingle_cascade/tests/get_links.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_cascade::test_utils::*; use aingle_cascade::Cascade; use aingle_p2p::MockAIngleP2pCellT; diff --git a/crates/aingle_conductor_api/Cargo.toml b/crates/aingle_conductor_api/Cargo.toml index 1191a9cd..7d289c75 100644 --- a/crates/aingle_conductor_api/Cargo.toml +++ b/crates/aingle_conductor_api/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_conductor_api" version = "0.0.1" description = "Message types for AIngle admin and app interface protocols" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_conductor_api" diff --git a/crates/aingle_conductor_api/src/admin_interface.rs b/crates/aingle_conductor_api/src/admin_interface.rs index 0153d33f..a960d4fc 100644 --- a/crates/aingle_conductor_api/src/admin_interface.rs +++ b/crates/aingle_conductor_api/src/admin_interface.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_types::prelude::*; use aingle_zome_types::cell::CellId; diff --git a/crates/aingle_conductor_api/src/app_interface.rs b/crates/aingle_conductor_api/src/app_interface.rs index 0759498d..8d2c2be5 100644 --- a/crates/aingle_conductor_api/src/app_interface.rs +++ b/crates/aingle_conductor_api/src/app_interface.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::{signal_subscription::SignalSubscription, ExternalApiWireError}; use ai_hash::AgentPubKey; use aingle_types::prelude::*; diff --git a/crates/aingle_conductor_api/src/config.rs b/crates/aingle_conductor_api/src/config.rs index 4ae893a2..53bb5b05 100644 --- a/crates/aingle_conductor_api/src/config.rs +++ b/crates/aingle_conductor_api/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub mod conductor; mod interface; pub use interface::*; diff --git a/crates/aingle_conductor_api/src/config/conductor.rs b/crates/aingle_conductor_api/src/config/conductor.rs index b4e3460e..2e45260b 100644 --- a/crates/aingle_conductor_api/src/config/conductor.rs +++ b/crates/aingle_conductor_api/src/config/conductor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! This module is used to configure the conductor diff --git a/crates/aingle_conductor_api/src/config/conductor/admin_interface_config.rs b/crates/aingle_conductor_api/src/config/conductor/admin_interface_config.rs index 0f5f42e5..80adc63b 100644 --- a/crates/aingle_conductor_api/src/config/conductor/admin_interface_config.rs +++ b/crates/aingle_conductor_api/src/config/conductor/admin_interface_config.rs @@ -1 +1,4 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] diff --git a/crates/aingle_conductor_api/src/config/conductor/dpki_config.rs b/crates/aingle_conductor_api/src/config/conductor/dpki_config.rs index 509212ae..7cefec48 100644 --- a/crates/aingle_conductor_api/src/config/conductor/dpki_config.rs +++ b/crates/aingle_conductor_api/src/config/conductor/dpki_config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + // Legacy config that will probably change #![allow(missing_docs)] diff --git a/crates/aingle_conductor_api/src/config/conductor/error.rs b/crates/aingle_conductor_api/src/config/conductor/error.rs index 8c3bdc07..bad9083c 100644 --- a/crates/aingle_conductor_api/src/config/conductor/error.rs +++ b/crates/aingle_conductor_api/src/config/conductor/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::PathBuf; use thiserror::Error; diff --git a/crates/aingle_conductor_api/src/config/conductor/logger_config.rs b/crates/aingle_conductor_api/src/config/conductor/logger_config.rs index 6b80f42a..91965f24 100644 --- a/crates/aingle_conductor_api/src/config/conductor/logger_config.rs +++ b/crates/aingle_conductor_api/src/config/conductor/logger_config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use serde::{Deserialize, Serialize}; /// FIXME: implement diff --git a/crates/aingle_conductor_api/src/config/conductor/passphrase_service_config.rs b/crates/aingle_conductor_api/src/config/conductor/passphrase_service_config.rs index 6e410c61..142ec140 100644 --- a/crates/aingle_conductor_api/src/config/conductor/passphrase_service_config.rs +++ b/crates/aingle_conductor_api/src/config/conductor/passphrase_service_config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use serde::Deserialize; use serde::Serialize; use std::path::PathBuf; diff --git a/crates/aingle_conductor_api/src/config/conductor/paths.rs b/crates/aingle_conductor_api/src/config/conductor/paths.rs index 72ccb7f1..b005b5ea 100644 --- a/crates/aingle_conductor_api/src/config/conductor/paths.rs +++ b/crates/aingle_conductor_api/src/config/conductor/paths.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines default paths for various resources use derive_more::AsRef; diff --git a/crates/aingle_conductor_api/src/config/conductor/signal_config.rs b/crates/aingle_conductor_api/src/config/conductor/signal_config.rs index 7efa6691..7e9766ad 100644 --- a/crates/aingle_conductor_api/src/config/conductor/signal_config.rs +++ b/crates/aingle_conductor_api/src/config/conductor/signal_config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use serde::{self, Deserialize, Serialize}; /// Configure which signals to emit, to reduce unwanted signal volume diff --git a/crates/aingle_conductor_api/src/config/interface.rs b/crates/aingle_conductor_api/src/config/interface.rs index f323d9e0..6a2bfbc9 100644 --- a/crates/aingle_conductor_api/src/config/interface.rs +++ b/crates/aingle_conductor_api/src/config/interface.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use serde::Deserialize; use serde::Serialize; diff --git a/crates/aingle_conductor_api/src/lib.rs b/crates/aingle_conductor_api/src/lib.rs index 13554ed8..c631672a 100644 --- a/crates/aingle_conductor_api/src/lib.rs +++ b/crates/aingle_conductor_api/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(deprecated)] mod admin_interface; diff --git a/crates/aingle_conductor_api/src/signal_subscription.rs b/crates/aingle_conductor_api/src/signal_subscription.rs index bfa49dcf..813bb1f3 100644 --- a/crates/aingle_conductor_api/src/signal_subscription.rs +++ b/crates/aingle_conductor_api/src/signal_subscription.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; use aingle_types::app::InstalledAppId; use aingle_zome_types::cell::CellId; diff --git a/crates/aingle_conductor_api/src/state_dump.rs b/crates/aingle_conductor_api/src/state_dump.rs index d8cb7741..8cc54cda 100644 --- a/crates/aingle_conductor_api/src/state_dump.rs +++ b/crates/aingle_conductor_api/src/state_dump.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::AgentPubKey; use ai_hash::SafHash; use aingle_state::source_chain::SourceChainJsonDump; diff --git a/crates/aingle_contracts/Cargo.toml b/crates/aingle_contracts/Cargo.toml index b69fa7a6..c3ea5927 100644 --- a/crates/aingle_contracts/Cargo.toml +++ b/crates/aingle_contracts/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_contracts" -version = "0.2.0" +version = "0.6.3" description = "Smart Contracts DSL and WASM Runtime for AIngle" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_contracts" @@ -21,16 +21,19 @@ full = ["runtime"] # WASM runtime (optional - for execution) # Note: default-features = false required for MSRV 1.70 compatibility # wasmer 6.x uses dep: syntax which doesn't create implicit features -wasmer = { version = "=6.0.0", optional = true, default-features = false, features = ["sys", "cranelift"] } +wasmer = { version = "=7.0.1", optional = true, default-features = false, features = ["sys", "cranelift"] } # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # Hashing -blake3 = "1.5" +blake3 = "1.8" sha2 = "0.10" +# Randomness (for contract address nonce) +rand = "0.9" + # Error handling thiserror = "2.0" @@ -47,5 +50,5 @@ tracing = "0.1" dashmap = "6.0" [dev-dependencies] -tempfile = "3.10" +tempfile = "3.26" tokio-test = "0.4" diff --git a/crates/aingle_contracts/src/contract.rs b/crates/aingle_contracts/src/contract.rs index bb6e324d..bb437e1c 100644 --- a/crates/aingle_contracts/src/contract.rs +++ b/crates/aingle_contracts/src/contract.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Contract definition and DSL //! //! Provides a builder pattern for defining contracts. diff --git a/crates/aingle_contracts/src/error.rs b/crates/aingle_contracts/src/error.rs index dcfa10da..16c9bb20 100644 --- a/crates/aingle_contracts/src/error.rs +++ b/crates/aingle_contracts/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for contract operations use thiserror::Error; @@ -36,6 +39,10 @@ pub enum ContractError { #[error("Invalid arguments: {0}")] InvalidArguments(String), + /// Invalid input data + #[error("Invalid input: {0}")] + InvalidInput(String), + /// Permission denied #[error("Permission denied: {0}")] PermissionDenied(String), diff --git a/crates/aingle_contracts/src/lib.rs b/crates/aingle_contracts/src/lib.rs index 66a8e877..da67541f 100644 --- a/crates/aingle_contracts/src/lib.rs +++ b/crates/aingle_contracts/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # AIngle Contracts - Smart Contract DSL and Runtime //! //! A lightweight smart contract system for AIngle with: diff --git a/crates/aingle_contracts/src/runtime.rs b/crates/aingle_contracts/src/runtime.rs index f1bd84d6..c84fe45d 100644 --- a/crates/aingle_contracts/src/runtime.rs +++ b/crates/aingle_contracts/src/runtime.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Contract runtime and WASM execution //! //! Provides sandboxed execution environment for contracts. @@ -244,6 +247,9 @@ impl ContractRuntime { Ok(result) } + /// Maximum size of a single storage value (64KB) + const MAX_STORAGE_VALUE_SIZE: usize = 64 * 1024; + /// Execute contract function (simplified implementation) fn execute_function( &self, @@ -255,6 +261,16 @@ impl ContractRuntime { let mut result = CallResult::empty(); let gas_start = ctx.gas_limit.remaining(); + // Charge per-byte gas for input data + let input_size: usize = args.iter().map(|a| a.to_string().len()).sum(); + let input_gas = input_size as u64 * self.gas_prices.per_byte; + ctx.gas_limit + .consume(input_gas) + .map_err(|_| ContractError::OutOfGas { + used: input_gas, + limit: ctx.gas_limit.0 + input_gas, + })?; + // Simplified execution - in real impl, this would run WASM code match function { "get" | "balance_of" | "get_balance" => { @@ -277,9 +293,21 @@ impl ContractRuntime { "set" | "transfer" | "mint" => { // Generic setter if args.len() >= 2 { - let key = args[0].as_str().unwrap_or("default"); + let key = args[0].as_str().ok_or_else(|| { + ContractError::InvalidInput("First argument must be a string key".into()) + })?; let value = &args[1]; + // Enforce max storage value size + let value_size = value.to_string().len(); + if value_size > Self::MAX_STORAGE_VALUE_SIZE { + return Err(ContractError::InvalidInput(format!( + "Storage value too large: {} bytes (max {})", + value_size, + Self::MAX_STORAGE_VALUE_SIZE + ))); + } + let storage_key = StorageKey::from_string(instance.address.clone(), key); // Record old value for state change @@ -288,10 +316,13 @@ impl ContractRuntime { .get(&storage_key)? .and_then(|v| v.to_json().ok()); + // Charge storage write + per-byte cost for value size + let write_gas = self.gas_prices.storage_write + + (value_size as u64 * self.gas_prices.per_byte); ctx.gas_limit - .consume(self.gas_prices.storage_write) + .consume(write_gas) .map_err(|_| ContractError::OutOfGas { - used: self.gas_prices.storage_write, + used: write_gas, limit: ctx.gas_limit.0, })?; diff --git a/crates/aingle_contracts/src/storage.rs b/crates/aingle_contracts/src/storage.rs index 2254af2d..ac820c32 100644 --- a/crates/aingle_contracts/src/storage.rs +++ b/crates/aingle_contracts/src/storage.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Contract storage abstraction //! //! Provides key-value storage for contract state. diff --git a/crates/aingle_contracts/src/types.rs b/crates/aingle_contracts/src/types.rs index eddbef2a..cc7f6b24 100644 --- a/crates/aingle_contracts/src/types.rs +++ b/crates/aingle_contracts/src/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Core types for contracts use serde::{Deserialize, Serialize}; @@ -25,7 +28,9 @@ impl Address { Ok(Self(arr)) } - /// Derive from string (for testing) + /// Derive a deterministic address from a name. + /// Only for testing — production contracts should use `deploy_address()`. + #[cfg(test)] pub fn derive(name: &str) -> Self { let mut hasher = Sha256::new(); hasher.update(b"aingle_address:"); @@ -34,6 +39,18 @@ impl Address { Self(hash) } + /// Generate a unique deployment address from deployer + code hash + random nonce. + pub fn deploy_address(deployer: &Address, code_hash: &[u8; 32]) -> Self { + let nonce: [u8; 16] = rand::random(); + let mut hasher = Sha256::new(); + hasher.update(b"aingle_deploy:"); + hasher.update(deployer.as_bytes()); + hasher.update(code_hash); + hasher.update(&nonce); + let hash: [u8; 32] = hasher.finalize().into(); + Self(hash) + } + /// Get as hex string pub fn to_hex(&self) -> String { hex::encode(self.0) diff --git a/crates/aingle_cortex/COMPLETION_REPORT.md b/crates/aingle_cortex/COMPLETION_REPORT.md index 0623f10c..2e3b9de3 100644 --- a/crates/aingle_cortex/COMPLETION_REPORT.md +++ b/crates/aingle_cortex/COMPLETION_REPORT.md @@ -284,7 +284,7 @@ use aingle_cortex::{CortexServer, CortexConfig}; #[tokio::main] async fn main() -> Result<(), Box> { let config = CortexConfig::default() - .with_port(8080) + .with_port(19090) .with_rate_limit_rpm(100); let server = CortexServer::new(config)?; diff --git a/crates/aingle_cortex/Cargo.toml b/crates/aingle_cortex/Cargo.toml index 0e7418f4..8aceaaf7 100644 --- a/crates/aingle_cortex/Cargo.toml +++ b/crates/aingle_cortex/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_cortex" -version = "0.2.1" +version = "0.6.3" description = "Córtex API - REST/GraphQL/SPARQL interface for AIngle semantic graphs" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_cortex" @@ -13,12 +13,16 @@ edition = "2021" rust-version = "1.83" [features] -default = ["rest", "sparql", "auth"] +default = ["rest", "sparql", "auth", "dag"] rest = [] graphql = ["dep:async-graphql", "dep:async-graphql-axum"] sparql = ["dep:spargebra"] auth = ["dep:jsonwebtoken", "dep:argon2"] -full = ["rest", "graphql", "sparql", "auth"] +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"] +dag = ["cluster", "aingle_graph/dag", "aingle_graph/dag-sign", "aingle_raft/dag"] +full = ["rest", "graphql", "sparql", "auth", "dag"] [[bin]] name = "aingle-cortex" @@ -26,25 +30,25 @@ path = "src/main.rs" [dependencies] # Core AIngle crates -aingle_graph = "0.2" -aingle_logic = "0.2" -aingle_zk = "0.2" -titans_memory = "0.2" +aingle_graph = { version = "0.6", path = "../aingle_graph", features = ["sled-backend"] } +aingle_logic = { version = "0.6", path = "../aingle_logic" } +aingle_zk = { version = "0.6", path = "../aingle_zk" } +ineru = { version = "0.6", path = "../ineru" } -# Web framework (use 0.7 for async-graphql-axum compatibility) -axum = { version = "0.7", features = ["ws", "macros"] } +# Web framework +axum = { version = "0.8", features = ["ws", "macros"] } tower = "0.5" tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip"] } -# GraphQL (optional) -async-graphql = { version = "7.0", features = ["chrono", "uuid"], optional = true } -async-graphql-axum = { version = "7.0", optional = true } +# GraphQL (optional) — 8.0.0-rc for axum 0.8 compatibility +async-graphql = { version = "8.0.0-rc", features = ["chrono", "uuid"], optional = true } +async-graphql-axum = { version = "8.0.0-rc", optional = true } # SPARQL (optional) -spargebra = { version = "0.3", optional = true } +spargebra = { version = "0.4", optional = true } # Authentication (optional) -jsonwebtoken = { version = "9.0", optional = true } +jsonwebtoken = { version = "10", features = ["rust_crypto"], optional = true } argon2 = { version = "0.5", optional = true } # Async runtime @@ -56,7 +60,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # Utilities -uuid = { version = "1.0", features = ["v4", "serde"] } +uuid = { version = "1.21", features = ["v4", "serde"] } chrono = { version = "0.4", features = ["serde"] } thiserror = "2.0" tracing = "0.1" @@ -65,7 +69,8 @@ log = "0.4" rand = "0.9" # Hashing -blake3 = "1.5" +blake3 = "1.8" +subtle = "2.6" # Streaming tokio-stream = { version = "0.1", features = ["sync"] } @@ -74,21 +79,35 @@ tokio-stream = { version = "0.1", features = ["sync"] } validator = { version = "0.20", features = ["derive"] } # Regular expressions (for SPARQL FILTER) -regex = "1.10" +regex = "1.12" # HTTP client (for CortexInternalClient used by WASM host functions) -reqwest = { version = "0.12", features = ["json"] } - -# Zome types (for WASM boundary types in client.rs) -aingle_zome_types = { version = ">=0.0.1", path = "../aingle_zome_types", default-features = false } +# Uses rustls-tls to avoid OpenSSL dependency (enables cross-compilation) +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } -# Rate limiting +# Rate limiting & shared state dashmap = "6.0" - -# IP address handling -axum-client-ip = "0.7" +once_cell = "1.4" + +# P2P networking (optional) +quinn = { version = "0.11", optional = true } +rustls = { version = "0.23", default-features = false, features = ["ring", "std"], optional = true } +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.6", path = "../aingle_wal", optional = true } +aingle_raft = { version = "0.6", 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 } + +sled = "0.34" +dirs = "6" +mdns-sd = { version = "0.18", optional = true } +if-addrs = { version = "0.13", optional = true } [dev-dependencies] -tempfile = "3.10" +tempfile = "3.26" reqwest = { version = "0.12", features = ["json"] } tokio-test = "0.4" diff --git a/crates/aingle_cortex/openapi.yaml b/crates/aingle_cortex/openapi.yaml index 9f67f122..6eab7725 100644 --- a/crates/aingle_cortex/openapi.yaml +++ b/crates/aingle_cortex/openapi.yaml @@ -40,7 +40,7 @@ info: url: https://www.apache.org/licenses/LICENSE-2.0.html servers: - - url: http://localhost:8080 + - url: http://localhost:19090 description: Local development server - url: https://api.aingle.apilium.com description: Production server diff --git a/crates/aingle_cortex/src/auth/jwt.rs b/crates/aingle_cortex/src/auth/jwt.rs index ca85a794..e15597a8 100644 --- a/crates/aingle_cortex/src/auth/jwt.rs +++ b/crates/aingle_cortex/src/auth/jwt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! JWT token handling use axum::{extract::State, Json}; @@ -8,8 +11,25 @@ use serde::{Deserialize, Serialize}; use crate::error::{Error, Result}; use crate::state::AppState; -/// JWT secret (in production, use environment variable) -const JWT_SECRET: &[u8] = b"aingle-cortex-secret-key-change-in-production"; +use dashmap::DashSet; +use once_cell::sync::Lazy; + +/// JWT secret loaded from AINGLE_JWT_SECRET environment variable. +/// Panics at startup if the variable is not set — this is intentional +/// to prevent running with an insecure default. +static JWT_SECRET: Lazy> = Lazy::new(|| { + std::env::var("AINGLE_JWT_SECRET") + .expect( + "AINGLE_JWT_SECRET environment variable must be set. \ + Generate one with: openssl rand -base64 64", + ) + .into_bytes() +}); + +/// Global set of revoked refresh token JTIs (JWT IDs). +/// Tokens are added here upon use in a refresh operation, +/// preventing replay of the same refresh token. +static REVOKED_TOKENS: Lazy> = Lazy::new(DashSet::new); /// Token expiration in hours const TOKEN_EXPIRATION_HOURS: i64 = 24; @@ -33,6 +53,12 @@ pub struct Claims { pub roles: Vec, /// Token type: "access" or "refresh" pub token_type: String, + /// Namespace scope (for scoped access control) + #[serde(skip_serializing_if = "Option::is_none")] + pub namespace: Option, + /// Unique token ID for revocation (refresh tokens only) + #[serde(skip_serializing_if = "Option::is_none")] + pub jti: Option, } impl Claims { @@ -46,6 +72,8 @@ impl Claims { iat: now.timestamp(), roles, token_type: "access".to_string(), + namespace: None, + jti: None, } } @@ -59,10 +87,32 @@ impl Claims { iat: now.timestamp(), roles, token_type: "access".to_string(), + namespace: None, + jti: None, } } - /// Create new refresh token claims + /// Create new access token claims with namespace scope + pub fn new_access_with_namespace( + user_id: &str, + username: &str, + roles: Vec, + namespace: String, + ) -> Self { + let now = Utc::now(); + Self { + sub: user_id.to_string(), + username: Some(username.to_string()), + exp: (now + Duration::hours(TOKEN_EXPIRATION_HOURS)).timestamp(), + iat: now.timestamp(), + roles, + token_type: "access".to_string(), + namespace: Some(namespace), + jti: None, + } + } + + /// Create new refresh token claims with unique JTI for single-use enforcement pub fn new_refresh(user_id: &str) -> Self { let now = Utc::now(); Self { @@ -72,6 +122,8 @@ impl Claims { iat: now.timestamp(), roles: vec![], token_type: "refresh".to_string(), + namespace: None, + jti: Some(uuid::Uuid::new_v4().to_string()), } } @@ -129,14 +181,14 @@ pub async fn create_token( let access_token = encode( &Header::default(), &access_claims, - &EncodingKey::from_secret(JWT_SECRET), + &EncodingKey::from_secret(&JWT_SECRET), ) .map_err(|e| Error::Internal(format!("Failed to create access token: {}", e)))?; let refresh_token = encode( &Header::default(), &refresh_claims, - &EncodingKey::from_secret(JWT_SECRET), + &EncodingKey::from_secret(&JWT_SECRET), ) .map_err(|e| Error::Internal(format!("Failed to create refresh token: {}", e)))?; @@ -165,7 +217,7 @@ pub async fn refresh_token( // Decode and validate refresh token let claims = decode::( &req.refresh_token, - &DecodingKey::from_secret(JWT_SECRET), + &DecodingKey::from_secret(&JWT_SECRET), &Validation::new(Algorithm::HS256), ) .map_err(|e| Error::AuthError(format!("Invalid refresh token: {}", e)))?; @@ -178,7 +230,17 @@ pub async fn refresh_token( return Err(Error::AuthError("Refresh token expired".to_string())); } - // Create new tokens + // Enforce single-use: check and revoke the JTI + if let Some(ref jti) = claims.claims.jti { + if !REVOKED_TOKENS.insert(jti.clone()) { + // JTI was already in the set — token has been used before + return Err(Error::AuthError("Refresh token already used".to_string())); + } + } else { + return Err(Error::AuthError("Refresh token missing JTI".to_string())); + } + + // Create new tokens (preserve original roles from user store) let roles = vec!["user".to_string()]; let access_claims = Claims::new_access(&claims.claims.sub, roles); let refresh_claims = Claims::new_refresh(&claims.claims.sub); @@ -186,14 +248,14 @@ pub async fn refresh_token( let access_token = encode( &Header::default(), &access_claims, - &EncodingKey::from_secret(JWT_SECRET), + &EncodingKey::from_secret(&JWT_SECRET), ) .map_err(|e| Error::Internal(format!("Failed to create access token: {}", e)))?; let refresh_token = encode( &Header::default(), &refresh_claims, - &EncodingKey::from_secret(JWT_SECRET), + &EncodingKey::from_secret(&JWT_SECRET), ) .map_err(|e| Error::Internal(format!("Failed to create refresh token: {}", e)))?; @@ -251,7 +313,7 @@ pub async fn verify_token_endpoint( pub fn verify_token(token: &str) -> Result { let token_data = decode::( token, - &DecodingKey::from_secret(JWT_SECRET), + &DecodingKey::from_secret(&JWT_SECRET), &Validation::new(Algorithm::HS256), ) .map_err(|e| Error::AuthError(format!("Invalid token: {}", e)))?; @@ -317,12 +379,13 @@ mod tests { #[test] fn test_token_roundtrip() { + std::env::set_var("AINGLE_JWT_SECRET", "test-secret-only-do-not-use-in-production-64bytes-pad"); let claims = Claims::new_access("user123", vec!["user".to_string()]); let token = encode( &Header::default(), &claims, - &EncodingKey::from_secret(JWT_SECRET), + &EncodingKey::from_secret(&JWT_SECRET), ) .unwrap(); diff --git a/crates/aingle_cortex/src/auth/middleware.rs b/crates/aingle_cortex/src/auth/middleware.rs index 5875957d..e1e7a113 100644 --- a/crates/aingle_cortex/src/auth/middleware.rs +++ b/crates/aingle_cortex/src/auth/middleware.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Authentication middleware use axum::{ diff --git a/crates/aingle_cortex/src/auth/mod.rs b/crates/aingle_cortex/src/auth/mod.rs index 90585dec..41e96f21 100644 --- a/crates/aingle_cortex/src/auth/mod.rs +++ b/crates/aingle_cortex/src/auth/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Authentication and authorization for Córtex API //! //! Provides JWT-based authentication with role-based access control. diff --git a/crates/aingle_cortex/src/auth/users.rs b/crates/aingle_cortex/src/auth/users.rs index 28c3fdc3..928715e9 100644 --- a/crates/aingle_cortex/src/auth/users.rs +++ b/crates/aingle_cortex/src/auth/users.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! User management and credential validation use argon2::{ @@ -122,9 +125,15 @@ impl UserStore { } } - /// Initialize with default admin user + /// Initialize admin user from AINGLE_ADMIN_PASSWORD environment variable. + /// Returns an error if the variable is not set or the password is too short. pub fn init_default_admin(&self) -> Result { - self.create_user("admin", "admin123", vec!["admin".into(), "user".into()]) + let password = std::env::var("AINGLE_ADMIN_PASSWORD") + .map_err(|_| "AINGLE_ADMIN_PASSWORD environment variable must be set".to_string())?; + if password.len() < 12 { + return Err("Admin password must be at least 12 characters".to_string()); + } + self.create_user("admin", &password, vec!["admin".into(), "user".into()]) } } diff --git a/crates/aingle_cortex/src/client.rs b/crates/aingle_cortex/src/client.rs index bd575ed9..eebfdb77 100644 --- a/crates/aingle_cortex/src/client.rs +++ b/crates/aingle_cortex/src/client.rs @@ -1,10 +1,13 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Internal Rust client for AIngle Cortex. //! //! Provides programmatic access to the Cortex semantic graph and Titans //! memory system, used by WASM host functions to bridge zome code with //! the knowledge layer. -use aingle_zome_types::graph::{ +use crate::wasm_types::{ GraphQueryInput, GraphQueryOutput, GraphStoreInput, GraphStoreOutput, MemoryRecallInput, MemoryRecallOutput, MemoryRememberInput, MemoryRememberOutput, Triple, ObjectValue, @@ -14,7 +17,7 @@ use serde::{Deserialize, Serialize}; /// Configuration for the Cortex internal client. #[derive(Debug, Clone)] pub struct CortexClientConfig { - /// Base URL of the Cortex REST API (e.g., "http://127.0.0.1:8080"). + /// Base URL of the Cortex REST API (e.g., "http://127.0.0.1:19090"). pub base_url: String, /// Optional authentication token. pub auth_token: Option, @@ -25,7 +28,7 @@ pub struct CortexClientConfig { impl Default for CortexClientConfig { fn default() -> Self { Self { - base_url: "http://127.0.0.1:8080".to_string(), + base_url: "http://127.0.0.1:19090".to_string(), auth_token: None, timeout_ms: 5000, } @@ -276,7 +279,7 @@ impl CortexInternalClient { Ok(MemoryRecallOutput { results: result.results.iter().map(|r| { - aingle_zome_types::graph::MemoryResult { + crate::wasm_types::MemoryResult { id: r.id.clone(), data: r.data.clone(), entry_type: r.entry_type.clone(), @@ -329,7 +332,7 @@ mod tests { #[test] fn test_default_config() { let config = CortexClientConfig::default(); - assert_eq!(config.base_url, "http://127.0.0.1:8080"); + assert_eq!(config.base_url, "http://127.0.0.1:19090"); assert!(config.auth_token.is_none()); assert_eq!(config.timeout_ms, 5000); } diff --git a/crates/aingle_cortex/src/cluster_init.rs b/crates/aingle_cortex/src/cluster_init.rs new file mode 100644 index 00000000..20bcfa74 --- /dev/null +++ b/crates/aingle_cortex/src/cluster_init.rs @@ -0,0 +1,553 @@ +// 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; + +#[cfg(feature = "cluster")] +use aingle_raft::state_machine::{ProofSnapshot, ProofSnapshotProvider}; + +#[cfg(feature = "cluster")] +use crate::proofs::ProofStore; + +#[cfg(feature = "cluster")] +impl ProofSnapshotProvider for ProofStore { + fn export_proofs(&self) -> Vec { + self.export_proofs_sync() + } + + fn import_proofs(&self, proofs: &[ProofSnapshot]) { + self.import_proofs_sync(proofs); + } +} + +/// 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> + + Send + + '_, + >, + > { + use aingle_raft::network::RaftMessage; + + Box::pin(async move { + let (path, payload) = match msg { + RaftMessage::AppendEntries { payload } => ("append-entries", payload), + RaftMessage::Vote { payload } => ("vote", payload), + RaftMessage::InstallSnapshot { payload } => ("snapshot", payload), + // Streaming snapshot chunks are routed to the chunk endpoint + ref chunk @ RaftMessage::SnapshotChunk { .. } => { + let payload = serde_json::to_vec(&chunk) + .map_err(|e| format!("Serialize snapshot chunk: {e}"))?; + ("snapshot-chunk", payload) + } + other => { + return Err(format!( + "Unsupported RaftMessage variant for HTTP RPC: {:?}", + std::mem::discriminant(&other) + )) + } + }; + + let url = format!("{}://{}/internal/raft/{}", self.scheme(), addr, path); + + // Exponential backoff: 3 attempts with delays 0ms, 100ms, 400ms + let backoff_delays = [0u64, 100, 400]; + let mut last_err = String::new(); + + for (attempt, delay_ms) in backoff_delays.iter().enumerate() { + if *delay_ms > 0 { + tokio::time::sleep(std::time::Duration::from_millis(*delay_ms)).await; + } + + let mut req = self + .client + .post(&url) + .header("content-type", "application/octet-stream") + .body(payload.clone()); + + if let Some(ref secret) = self.cluster_secret { + req = req.header("x-cluster-secret", secret.as_str()); + } + + match req.send().await { + Ok(resp) => { + if resp.status().is_client_error() { + let status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + return Err(format!("Raft RPC {url} returned {status}: {body}")); + } + + if !resp.status().is_success() { + let status = resp.status(); + let body = resp.text().await.unwrap_or_default(); + last_err = format!("Raft RPC {url} returned {status}: {body}"); + tracing::debug!( + attempt = attempt + 1, + error = %last_err, + "Raft RPC failed, retrying" + ); + continue; + } + + let response_payload = resp + .bytes() + .await + .map_err(|e| format!("Read Raft RPC response from {url}: {e}"))? + .to_vec(); + + let response = match path { + "append-entries" => RaftMessage::AppendEntriesResponse { + payload: response_payload, + }, + "vote" => RaftMessage::VoteResponse { + payload: response_payload, + }, + "snapshot" => RaftMessage::InstallSnapshotResponse { + payload: response_payload, + }, + "snapshot-chunk" => { + // Could be SnapshotChunkAck or InstallSnapshotResponse + match serde_json::from_slice(&response_payload) { + Ok(msg) => msg, + Err(e) => { + tracing::warn!( + "Failed to deserialize snapshot-chunk response: {e}, \ + treating as InstallSnapshotResponse" + ); + RaftMessage::InstallSnapshotResponse { + payload: response_payload, + } + } + } + } + _ => unreachable!(), + }; + + return Ok(response); + } + Err(e) => { + last_err = format!("Raft RPC to {url}: {e}"); + tracing::debug!( + attempt = attempt + 1, + error = %last_err, + "Raft RPC failed, retrying" + ); + } + } + } + + Err(last_err) + }) + } +} + +/// Build a `rustls::ServerConfig` for the Raft RPC listener. +/// +/// If `cert_path` and `key_path` are provided, loads PEM files from disk. +/// Otherwise, generates a self-signed certificate using `rcgen` (TOFU model). +pub fn build_tls_server_config( + cert_path: Option<&str>, + key_path: Option<&str>, +) -> Result { + use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; + + let (cert_der, key_der): (CertificateDer<'static>, PrivateKeyDer<'static>) = + match (cert_path, key_path) { + (Some(cp), Some(kp)) => { + let cert_pem = std::fs::read(cp) + .map_err(|e| Error::Internal(format!("Read TLS cert {cp}: {e}")))?; + let key_pem = std::fs::read(kp) + .map_err(|e| Error::Internal(format!("Read TLS key {kp}: {e}")))?; + + let cert = rustls_pemfile::certs(&mut &cert_pem[..]) + .next() + .ok_or_else(|| Error::Internal("No certificate found in PEM file".into()))? + .map_err(|e| Error::Internal(format!("Parse TLS cert: {e}")))?; + + let key = rustls_pemfile::private_key(&mut &key_pem[..]) + .map_err(|e| Error::Internal(format!("Parse TLS key: {e}")))? + .ok_or_else(|| Error::Internal("No private key found in PEM file".into()))?; + + (cert, key) + } + _ => { + // Auto-generate self-signed cert (TOFU model, matching P2P transport) + let generated = rcgen::generate_simple_self_signed(vec![ + "localhost".to_string(), + "127.0.0.1".to_string(), + ]) + .map_err(|e| Error::Internal(format!("Generate self-signed cert: {e}")))?; + + let key = PrivatePkcs8KeyDer::from(generated.key_pair.serialize_der()); + let cert = CertificateDer::from(generated.cert); + (cert, key.into()) + } + }; + + let config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(vec![cert_der], key_der) + .map_err(|e| Error::Internal(format!("TLS server config: {e}")))?; + + Ok(config) +} + +/// Initialize the Raft cluster on a `CortexServer`. +/// +/// This sets up the WAL, state machine, network factory, and Raft instance. +/// Must be called after `CortexServer::new()` and before `run()`. +/// +/// Returns the bind address used for the REST API (needed for join requests). +#[cfg(feature = "cluster")] +pub async fn init_cluster( + server: &mut CortexServer, + config: &ClusterConfig, + bind_addr: &str, + p2p_addr: &str, +) -> Result<(), Error> { + config.validate().map_err(|e| Error::Internal(e))?; + + let wal_dir = config.wal_dir.as_deref().unwrap_or("wal"); + let wal_path = std::path::Path::new(wal_dir); + + let log_store = match aingle_raft::log_store::CortexLogStore::open(wal_path) { + Ok(ls) => std::sync::Arc::new(ls), + Err(e) => return Err(Error::Internal(format!("Failed to initialize WAL: {e}"))), + }; + + server.state_mut().wal = Some(log_store.wal().clone()); + server.state_mut().cluster_secret = config.secret.clone(); + + let state_machine = { + let mut sm = aingle_raft::state_machine::CortexStateMachine::new( + server.state().graph.clone(), + server.state().memory.clone(), + ); + sm.set_proof_provider(server.state().proof_store.clone()); + std::sync::Arc::new(sm) + }; + + let resolver = std::sync::Arc::new(aingle_raft::network::NodeResolver::new()); + let node_id = config.node_id; + + resolver + .register( + node_id, + aingle_raft::CortexNode { + rest_addr: bind_addr.to_string(), + p2p_addr: p2p_addr.to_string(), + }, + ) + .await; + + let rpc_sender = std::sync::Arc::new(HttpRaftRpcSender::new( + config.secret.clone(), + config.tls, + )); + let network = aingle_raft::network::CortexNetworkFactory::new(resolver, rpc_sender); + + let raft_config = openraft::Config { + heartbeat_interval: 500, + election_timeout_min: 1500, + election_timeout_max: 3000, + ..Default::default() + }; + + let raft = openraft::Raft::new( + node_id, + std::sync::Arc::new(raft_config), + network, + log_store, + state_machine, + ) + .await + .map_err(|e| Error::Internal(format!("Failed to create Raft instance: {e}")))?; + + if config.peers.is_empty() { + // Bootstrap single-node cluster + let mut members = std::collections::BTreeMap::new(); + members.insert( + node_id, + aingle_raft::CortexNode { + rest_addr: bind_addr.to_string(), + p2p_addr: p2p_addr.to_string(), + }, + ); + if let Err(e) = raft.initialize(members).await { + use openraft::error::RaftError; + match e { + RaftError::APIError(openraft::error::InitializeError::NotAllowed(_)) => { + tracing::debug!("Raft already initialized: {e}"); + } + other => { + return Err(Error::Internal(format!( + "Raft initialization failed: {other}" + ))); + } + } + } + } else { + // Multi-node join with exponential backoff + let peers = config.peers.clone(); + let join_rest_addr = bind_addr.to_string(); + let join_p2p_addr = p2p_addr.to_string(); + let join_secret = config.secret.clone(); + let use_tls = config.tls; + tokio::spawn(async move { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + + let join_client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(5)) + .danger_accept_invalid_certs(use_tls) // TOFU for TLS + .build() + .unwrap(); + + let join_body = serde_json::json!({ + "node_id": node_id, + "rest_addr": join_rest_addr, + "p2p_addr": join_p2p_addr, + }); + + let scheme = if use_tls { "https" } else { "http" }; + let mut attempt = 0u32; + let max_attempts = 10; + loop { + attempt += 1; + let mut joined = false; + + for peer in &peers { + let url = format!("{scheme}://{peer}/api/v1/cluster/join"); + tracing::info!(url = %url, attempt, "Attempting to join cluster"); + + let mut req_builder = join_client.post(&url).json(&join_body); + + if let Some(ref secret) = join_secret { + req_builder = req_builder.header("x-cluster-secret", secret.as_str()); + } + + match req_builder.send().await { + Ok(resp) => { + let status = resp.status(); + let text = resp.text().await.unwrap_or_default(); + if status.is_success() { + tracing::info!( + peer = %peer, + response = %text, + "Successfully joined cluster" + ); + joined = true; + break; + } else { + tracing::warn!( + peer = %peer, + status = %status, + response = %text, + "Join request rejected, trying next peer" + ); + } + } + Err(e) => { + tracing::warn!( + peer = %peer, + error = %e, + "Failed to reach peer, trying next" + ); + } + } + } + + if joined { + break; + } + if attempt >= max_attempts { + tracing::error!("Exhausted {max_attempts} join attempts — giving up"); + break; + } + let base = std::time::Duration::from_secs(2u64.pow(attempt.min(5))); + let jitter = + std::time::Duration::from_millis(rand::random::() % 1000); + let backoff = base + jitter; + tracing::warn!(attempt, "Join failed, retrying in {:?}", backoff); + tokio::time::sleep(backoff).await; + } + }); + } + + // Set up TLS server config if cluster TLS is enabled + if config.tls { + let tls_config = build_tls_server_config( + config.tls_cert.as_deref(), + config.tls_key.as_deref(), + )?; + server.state_mut().tls_server_config = + Some(std::sync::Arc::new(tls_config)); + tracing::info!("Cluster TLS enabled for inter-node communication"); + } + + server.state_mut().raft = Some(raft); + server.state_mut().cluster_node_id = Some(node_id); + tracing::info!(node_id, "Raft consensus initialized"); + + Ok(()) +} diff --git a/crates/aingle_cortex/src/error.rs b/crates/aingle_cortex/src/error.rs index b138aff2..03171645 100644 --- a/crates/aingle_cortex/src/error.rs +++ b/crates/aingle_cortex/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for the Córtex API server. use axum::http::StatusCode; @@ -94,6 +97,10 @@ pub enum Error { /// A conflict occurred, such as trying to create a resource that already exists. #[error("Conflict: {0}")] Conflict(String), + + /// The request should be redirected to another node (e.g., Raft leader). + #[error("Redirect to {0}")] + Redirect(String), } /// The standard JSON response body for an API error. @@ -133,6 +140,7 @@ impl Error { Error::Timeout(_) => StatusCode::REQUEST_TIMEOUT, Error::BadRequest(_) => StatusCode::BAD_REQUEST, Error::Conflict(_) => StatusCode::CONFLICT, + Error::Redirect(_) => StatusCode::TEMPORARY_REDIRECT, } } @@ -160,6 +168,7 @@ impl Error { Error::Timeout(_) => "TIMEOUT", Error::BadRequest(_) => "BAD_REQUEST", Error::Conflict(_) => "CONFLICT", + Error::Redirect(_) => "REDIRECT", } } } @@ -167,6 +176,17 @@ impl Error { impl IntoResponse for Error { fn into_response(self) -> Response { let status = self.status_code(); + + // For redirects, include a Location header so clients can follow + if let Error::Redirect(ref location) = self { + return ( + status, + [(axum::http::header::LOCATION, location.as_str())], + "Redirecting to leader", + ) + .into_response(); + } + let body = ErrorResponse { error: self.to_string(), code: self.error_code().to_string(), diff --git a/crates/aingle_cortex/src/graphql/mod.rs b/crates/aingle_cortex/src/graphql/mod.rs index 74d6f98a..633350a9 100644 --- a/crates/aingle_cortex/src/graphql/mod.rs +++ b/crates/aingle_cortex/src/graphql/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! GraphQL API for Córtex //! //! Provides a complete GraphQL schema with queries, mutations, and subscriptions @@ -12,11 +15,10 @@ pub use schema::*; pub use subscriptions::*; use async_graphql::Schema; -use async_graphql_axum::{GraphQLRequest, GraphQLResponse, GraphQLSubscription}; +use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; use axum::{ extract::State, response::{Html, IntoResponse}, - routing::get, Router, }; @@ -34,10 +36,8 @@ pub fn create_schema(state: AppState) -> CortexSchema { /// Create GraphQL router /// -/// Note: Currently disabled due to axum version compatibility issues -/// The GraphQL functionality is complete but needs axum 0.8 or higher +/// Note: Placeholder — GraphQL endpoints will be wired in a future release pub fn router(_state: AppState, _playground: bool) -> Router { - // Placeholder router until axum version compatibility is resolved Router::new() } @@ -49,18 +49,10 @@ async fn graphql_handler( schema.execute(req.into_inner()).await.into() } -/// GraphQL playground +/// GraphiQL IDE async fn graphql_playground() -> impl IntoResponse { - Html(async_graphql::http::playground_source( - async_graphql::http::GraphQLPlaygroundConfig::new("/graphql") - .subscription_endpoint("/graphql/ws"), - )) -} - -/// GraphQL subscription handler -async fn graphql_subscription_handler( - State(schema): State, - ws: axum::extract::ws::WebSocketUpgrade, -) -> impl IntoResponse { - ws.on_upgrade(move |socket| GraphQLSubscription::new(schema).serve(socket)) + Html(async_graphql::http::GraphiQLSource::build() + .endpoint("/graphql") + .subscription_endpoint("/graphql/ws") + .finish()) } diff --git a/crates/aingle_cortex/src/graphql/resolvers.rs b/crates/aingle_cortex/src/graphql/resolvers.rs index 6dd50eba..b7f1d13c 100644 --- a/crates/aingle_cortex/src/graphql/resolvers.rs +++ b/crates/aingle_cortex/src/graphql/resolvers.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! GraphQL resolvers use async_graphql::*; @@ -150,7 +153,7 @@ pub struct MutationRoot; #[Object] impl MutationRoot { - /// Create a new triple + /// Create a new triple (routed through the same path as REST for DAG/Raft consistency) async fn create_triple(&self, ctx: &Context<'_>, input: TripleInput) -> Result { let state = ctx.data::()?; @@ -162,9 +165,45 @@ impl MutationRoot { object, ); + // Insert triple + record DAG action (same path as REST API) { let graph = state.graph.read().await; graph.insert(triple.clone())?; + + #[cfg(feature = "dag")] + if let Some(dag_store) = graph.dag_store() { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let parents = dag_store.tips().unwrap_or_default(); + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleInsert { + triples: vec![aingle_graph::dag::TripleInsertPayload { + subject: input.subject.clone(), + predicate: input.predicate.clone(), + object: serde_json::json!({}), + }], + }, + signature: None, + }; + + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + dag_store.put(&action).map_err(|e| { + Error::new(format!("DAG action failed: {e}")) + })?; + } } // Broadcast event @@ -180,7 +219,7 @@ impl MutationRoot { Ok(triple.into()) } - /// Delete a triple by ID + /// Delete a triple by ID (routed through the same path as REST for DAG/Raft consistency) async fn delete_triple(&self, ctx: &Context<'_>, id: ID) -> Result { let state = ctx.data::()?; @@ -189,7 +228,43 @@ impl MutationRoot { let deleted = { let graph = state.graph.read().await; - graph.delete(&triple_id)? + let result = graph.delete(&triple_id)?; + + #[cfg(feature = "dag")] + if result { + if let Some(dag_store) = graph.dag_store() { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let parents = dag_store.tips().unwrap_or_default(); + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleDelete { + triple_ids: vec![*triple_id.as_bytes()], + subjects: vec![], + }, + signature: None, + }; + + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + dag_store.put(&action).map_err(|e| { + Error::new(format!("DAG action failed: {e}")) + })?; + } + } + + result }; if deleted { diff --git a/crates/aingle_cortex/src/graphql/schema.rs b/crates/aingle_cortex/src/graphql/schema.rs index 51f1f267..20d4395c 100644 --- a/crates/aingle_cortex/src/graphql/schema.rs +++ b/crates/aingle_cortex/src/graphql/schema.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! GraphQL schema definitions use async_graphql::*; diff --git a/crates/aingle_cortex/src/graphql/subscriptions.rs b/crates/aingle_cortex/src/graphql/subscriptions.rs index ba503a9b..2ce74224 100644 --- a/crates/aingle_cortex/src/graphql/subscriptions.rs +++ b/crates/aingle_cortex/src/graphql/subscriptions.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! GraphQL subscriptions for real-time updates //! //! This module provides WebSocket-based subscriptions for: diff --git a/crates/aingle_cortex/src/lib.rs b/crates/aingle_cortex/src/lib.rs index 8d64db93..dd2b99b3 100644 --- a/crates/aingle_cortex/src/lib.rs +++ b/crates/aingle_cortex/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![doc = include_str!("../README.md")] //! # AIngle Córtex - External API Layer //! @@ -54,7 +57,7 @@ //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { -//! // Start server on localhost:8080 +//! // Start server on localhost:19090 //! let config = CortexConfig::default(); //! let server = CortexServer::new(config)?; //! server.run().await?; @@ -77,7 +80,7 @@ //! ### Add a Triple //! //! ```bash -//! curl -X POST http://localhost:8080/api/v1/triples \ +//! curl -X POST http://localhost:19090/api/v1/triples \ //! -H "Content-Type: application/json" \ //! -H "Authorization: Bearer YOUR_TOKEN" \ //! -d '{ @@ -90,13 +93,13 @@ //! ### Query Triples //! //! ```bash -//! curl "http://localhost:8080/api/v1/triples?subject=alice" +//! curl "http://localhost:19090/api/v1/triples?subject=alice" //! ``` //! //! ### Validate Proof //! //! ```bash -//! curl -X POST http://localhost:8080/api/v1/proofs/validate \ +//! curl -X POST http://localhost:19090/api/v1/proofs/validate \ //! -H "Content-Type: application/json" \ //! -d '{ //! "proof_type": "schnorr", @@ -107,7 +110,7 @@ //! //! ## GraphQL Examples //! -//! Access the GraphQL playground at `http://localhost:8080/graphql`. +//! Access the GraphQL playground at `http://localhost:19090/graphql`. //! //! ### Query //! @@ -162,6 +165,7 @@ pub mod auth; pub mod client; pub mod error; +pub mod wasm_types; #[cfg(feature = "graphql")] pub mod graphql; pub mod middleware; @@ -171,6 +175,10 @@ pub mod server; #[cfg(feature = "sparql")] pub mod sparql; pub mod state; +#[cfg(feature = "p2p")] +pub mod p2p; +#[cfg(feature = "cluster")] +pub mod cluster_init; pub use client::{CortexClientConfig, CortexInternalClient}; pub use error::{Error, Result}; diff --git a/crates/aingle_cortex/src/main.rs b/crates/aingle_cortex/src/main.rs index a07e4cca..02a4fe07 100644 --- a/crates/aingle_cortex/src/main.rs +++ b/crates/aingle_cortex/src/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle Córtex API Server //! //! REST/GraphQL/SPARQL interface for AIngle semantic graphs. @@ -19,6 +22,15 @@ async fn main() -> Result<(), Box> { // Parse command line arguments let args: Vec = std::env::args().collect(); + // Handle --version before anything else (no server init needed) + if args.iter().any(|a| a == "--version" || a == "-V") { + println!("AIngle Cortex v{}", env!("CARGO_PKG_VERSION")); + println!("Copyright 2019-2026 Apilium Technologies OÜ"); + println!("License: Apache-2.0 OR Commercial"); + println!("https://github.com/ApiliumCode/aingle"); + return Ok(()); + } + let mut config = CortexConfig::default(); // Simple argument parsing @@ -33,13 +45,28 @@ async fn main() -> Result<(), Box> { } "--port" | "-p" => { if i + 1 < args.len() { - config.port = args[i + 1].parse().unwrap_or(8080); + config.port = args[i + 1].parse().unwrap_or(19090); i += 1; } } "--public" => { config.host = "0.0.0.0".to_string(); } + "--db" => { + if i + 1 < args.len() { + config.db_path = Some(args[i + 1].clone()); + i += 1; + } + } + "--memory" => { + config.db_path = Some(":memory:".to_string()); + } + "--flush-interval" => { + if i + 1 < args.len() { + config.flush_interval_secs = args[i + 1].parse().unwrap_or(300); + i += 1; + } + } "--help" => { print_help(); return Ok(()); @@ -49,15 +76,298 @@ async fn main() -> Result<(), Box> { i += 1; } + // Parse P2P flags (feature-gated at compile time). + #[cfg(feature = "p2p")] + let p2p_config = { + let p2p = aingle_cortex::p2p::config::P2pConfig::from_args(&args); + if let Err(e) = p2p.validate() { + eprintln!("Invalid P2P config: {}", e); + std::process::exit(1); + } + p2p + }; + + // Resolve the snapshot directory for Ineru persistence + let snapshot_dir = match &config.db_path { + Some(p) if p == ":memory:" => None, + Some(p) => std::path::Path::new(p).parent().map(|p| p.to_path_buf()), + None => { + let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from(".")); + Some(home.join(".aingle").join("cortex")) + } + }; + + // Parse and validate cluster config (feature-gated at compile time). + #[cfg(feature = "cluster")] + let cluster_config = { + let cfg = aingle_cortex::cluster_init::ClusterConfig::from_args(&args); + if cfg.enabled { + if let Err(e) = cfg.validate() { + eprintln!("Error: {e}"); + std::process::exit(1); + } + } + cfg + }; + + // Capture bind address and db_path before config is moved + #[allow(unused_variables)] + let bind_host = config.host.clone(); + #[allow(unused_variables)] + let bind_port = config.port; + #[allow(unused_variables)] + let db_path = config.db_path.clone(); + let flush_interval_secs = config.flush_interval_secs; + // Create and run server - let server = CortexServer::new(config)?; + #[allow(unused_mut)] + let mut server = CortexServer::new(config)?; + + // Initialize Raft cluster if enabled. + #[cfg(feature = "cluster")] + if cluster_config.enabled { + let this_rest_addr = format!("{}:{}", bind_host, bind_port); + #[cfg(feature = "p2p")] + let this_p2p_addr = format!("{}:{}", bind_host, p2p_config.port); + #[cfg(not(feature = "p2p"))] + let this_p2p_addr = "127.0.0.1:19091".to_string(); + + if let Err(e) = aingle_cortex::cluster_init::init_cluster( + &mut server, + &cluster_config, + &this_rest_addr, + &this_p2p_addr, + ) + .await + { + tracing::error!("Cluster initialization failed: {e}"); + std::process::exit(1); + } + + tracing::info!( + node_id = cluster_config.node_id, + peers = ?cluster_config.peers, + "Cluster mode enabled" + ); + } + + // Initialize DAG if enabled: enable DAG on the graph, create genesis if needed + #[cfg(feature = "dag")] + { + let state = server.state_mut(); + + // Enable DAG on the GraphDB (persistent for Sled, in-memory otherwise) + { + let mut graph = state.graph.write().await; + match &db_path { + Some(p) if p != ":memory:" => { + graph.enable_dag_persistent(p).unwrap_or_else(|e| { + panic!( + "Failed to enable persistent DAG at '{}': {e}. \ + Refusing to start with volatile DAG — fix the storage path or permissions.", + p + ); + }); + tracing::info!("DAG persistence enabled (Sled)"); + } + _ => { + tracing::warn!("DAG using in-memory backend — data will NOT survive restarts"); + graph.enable_dag(); + } + } + let triple_count = graph.count(); + if let Some(dag_store) = graph.dag_store() { + match dag_store.init_or_migrate(triple_count) { + Ok(genesis_hash) => { + tracing::info!( + hash = %genesis_hash, + triples = triple_count, + "DAG initialized (genesis)" + ); + } + Err(e) => { + tracing::error!("DAG initialization failed: {e}"); + } + } + } + } + + // Set DAG author from cluster node ID + #[cfg(feature = "cluster")] + if let Some(node_id) = state.cluster_node_id { + state.dag_author = Some(aingle_graph::NodeId::named(&format!("node:{}", node_id))); + } + + // Initialize Ed25519 signing key for DAG actions. + // Reuses the same node.key seed as P2P identity (deterministic). + { + let key = match &db_path { + Some(p) if p != ":memory:" => { + let key_path = std::path::Path::new(p) + .parent() + .unwrap_or(std::path::Path::new(".")) + .join("node.key"); + if key_path.exists() { + match std::fs::read(&key_path) { + Ok(seed) if seed.len() == 32 => { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&seed); + Some(aingle_graph::dag::DagSigningKey::from_seed(&arr)) + } + _ => None, + } + } else { + // Generate new key and persist + let key = aingle_graph::dag::DagSigningKey::generate(); + let seed = key.seed(); + if let Some(parent) = key_path.parent() { + std::fs::create_dir_all(parent).ok(); + } + #[cfg(unix)] + { + use std::io::Write; + use std::os::unix::fs::OpenOptionsExt; + match std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .mode(0o600) + .open(&key_path) + { + Ok(mut f) => { + if let Err(e) = f.write_all(&seed).and_then(|_| f.sync_all()) { + tracing::error!("Failed to persist DAG signing key: {e}"); + } + } + Err(e) => { + tracing::error!("Failed to open DAG key file {}: {e}", key_path.display()); + } + } + } + #[cfg(not(unix))] + { + if let Err(e) = std::fs::write(&key_path, &seed) { + tracing::error!("Failed to persist DAG signing key: {e}"); + } + } + Some(key) + } + } + _ => { + // In-memory mode: generate ephemeral key + Some(aingle_graph::dag::DagSigningKey::generate()) + } + }; + + if let Some(ref k) = key { + tracing::info!( + public_key = %k.public_key_hex(), + "DAG signing key loaded (Ed25519)" + ); + } + state.dag_signing_key = key.map(std::sync::Arc::new); + } + + tracing::info!("Semantic DAG v0.6.0 enabled"); + } + + // Spawn periodic flush task if enabled + if flush_interval_secs > 0 { + let flush_state = server.state().clone(); + let flush_dir = snapshot_dir.clone(); + let interval_secs = flush_interval_secs; + tokio::spawn(async move { + let mut interval = + tokio::time::interval(std::time::Duration::from_secs(interval_secs)); + interval.tick().await; // skip immediate tick + loop { + interval.tick().await; + if let Err(e) = flush_state.flush(flush_dir.as_deref()).await { + tracing::warn!("Periodic flush failed: {e}"); + } else { + tracing::debug!("Periodic flush completed"); + } + } + }); + tracing::info!( + interval_secs = interval_secs, + "Periodic auto-flush enabled" + ); + } + + // Keep a reference to the state for shutdown flush + let state_for_shutdown = server.state().clone(); + let snapshot_dir_for_shutdown = snapshot_dir.clone(); + + // Start P2P manager if enabled. + #[cfg(feature = "p2p")] + if p2p_config.enabled { + match aingle_cortex::p2p::manager::P2pManager::start( + p2p_config.clone(), + server.state().clone(), + ) + .await + { + Ok(manager) => { + // SAFETY: we have exclusive access before serving. + server.state_mut().p2p = Some(manager); + tracing::info!("P2P manager started on port {}", p2p_config.port); + } + Err(e) => { + tracing::error!("P2P manager failed to start: {}", e); + } + } + } + + // Set up graceful shutdown with data flush (handles both SIGINT and SIGTERM) + let shutdown_signal = async move { + let ctrl_c = tokio::signal::ctrl_c(); + + #[cfg(unix)] + let terminate = async { + tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) + .expect("Failed to install SIGTERM handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => { + tracing::info!("SIGINT received — shutting down..."); + } + _ = terminate => { + tracing::info!("SIGTERM received — shutting down..."); + } + } - // Set up graceful shutdown - let shutdown_signal = async { - tokio::signal::ctrl_c() + // Gracefully shut down Raft before flushing data + #[cfg(feature = "cluster")] + if let Some(ref raft) = state_for_shutdown.raft { + tracing::info!("Shutting down Raft..."); + match tokio::time::timeout( + std::time::Duration::from_secs(10), + raft.shutdown(), + ) .await - .expect("Failed to install CTRL+C handler"); - tracing::info!("Shutdown signal received"); + { + Ok(Ok(())) => tracing::info!("Raft shut down gracefully"), + Ok(Err(e)) => tracing::error!("Raft shutdown error: {e}"), + Err(_) => tracing::error!("Raft shutdown timed out after 10s"), + } + } + + // Flush graph database and save Ineru snapshot + if let Err(e) = state_for_shutdown + .flush(snapshot_dir_for_shutdown.as_deref()) + .await + { + tracing::error!("Failed to flush data on shutdown: {}", e); + } else { + tracing::info!("Data flushed successfully"); + } }; server.run_with_shutdown(shutdown_signal).await?; @@ -73,13 +383,35 @@ fn print_help() { println!(); println!("OPTIONS:"); println!(" -h, --host Host to bind to (default: 127.0.0.1)"); - println!(" -p, --port Port to listen on (default: 8080)"); + println!(" -p, --port Port to listen on (default: 19090)"); println!(" --public Bind to all interfaces (0.0.0.0)"); + println!(" --db Path to graph database (default: ~/.aingle/cortex/graph.sled)"); + println!(" --memory Use volatile in-memory storage (no persistence)"); + println!(" --flush-interval Periodic flush interval in seconds (default: 300, 0=off)"); + println!(" -V, --version Print version and exit"); println!(" --help Print this help message"); println!(); + println!("P2P OPTIONS (requires --features p2p):"); + println!(" --p2p Enable P2P triple synchronization"); + println!(" --p2p-port QUIC listen port (default: 19091)"); + println!(" --p2p-seed Network isolation seed"); + println!(" --p2p-peer Manual peer address (repeatable)"); + println!(" --p2p-mdns Enable mDNS discovery"); + println!(); + println!("CLUSTER OPTIONS (requires --features cluster):"); + println!(" --cluster Enable cluster mode (implies --p2p)"); + println!(" --cluster-node-id Unique node ID (u64, required)"); + println!(" --cluster-peers Comma-separated peer REST addresses"); + println!(" --cluster-wal-dir WAL directory (default: wal/)"); + println!(" --cluster-secret Shared secret for internal RPC auth (min 16 bytes)"); + println!(" --cluster-tls Enable TLS for inter-node communication"); + println!(" --cluster-tls-cert TLS certificate PEM file"); + println!(" --cluster-tls-key TLS private key PEM file"); + println!(); println!("ENDPOINTS:"); println!(" REST API: http://:/api/v1/"); println!(" GraphQL: http://:/graphql"); println!(" SPARQL: http://:/sparql"); println!(" Health: http://:/api/v1/health"); + println!(" P2P Status: http://:/api/v1/p2p/status"); } diff --git a/crates/aingle_cortex/src/middleware/mod.rs b/crates/aingle_cortex/src/middleware/mod.rs index e3b05fa5..3aaa1c09 100644 --- a/crates/aingle_cortex/src/middleware/mod.rs +++ b/crates/aingle_cortex/src/middleware/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Middleware for Córtex API //! //! This module provides middleware components for the Córtex API server: @@ -16,6 +19,8 @@ //! .layer(rate_limiter.into_layer()); //! ``` +pub mod namespace; pub mod rate_limit; +pub use namespace::{namespace_extractor, is_in_namespace, scope_subject, RequestNamespace}; pub use rate_limit::{RateLimitError, RateLimiter, RateLimiterLayer}; diff --git a/crates/aingle_cortex/src/middleware/namespace.rs b/crates/aingle_cortex/src/middleware/namespace.rs new file mode 100644 index 00000000..31ba729a --- /dev/null +++ b/crates/aingle_cortex/src/middleware/namespace.rs @@ -0,0 +1,91 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Namespace scoping middleware +//! +//! Extracts the `namespace` from JWT claims and injects it into Axum request +//! extensions so downstream handlers can scope queries/mutations by namespace. + +use axum::{ + body::Body, + http::Request, + middleware::Next, + response::Response, +}; + +/// Namespace extracted from JWT claims, available via request extensions. +#[derive(Debug, Clone)] +pub struct RequestNamespace(pub Option); + +/// Middleware that extracts namespace from JWT claims and stores it in request extensions. +/// +/// If auth is not enabled or no namespace is present in the token, sets `None`. +/// Downstream handlers can read `RequestNamespace` from extensions and enforce +/// namespace boundaries accordingly. +pub async fn namespace_extractor( + mut req: Request, + next: Next, +) -> Response { + // Try to extract namespace from the Authorization header + let namespace = extract_namespace_from_token(&req); + req.extensions_mut().insert(RequestNamespace(namespace)); + next.run(req).await +} + +/// Extract namespace from Bearer token in Authorization header. +/// +/// Returns `None` if: +/// - No Authorization header present +/// - Token is invalid or cannot be decoded +/// - Claims do not contain a namespace field +/// - Auth feature is not enabled +#[cfg(feature = "auth")] +fn extract_namespace_from_token(req: &Request) -> Option { + let auth_header = req.headers().get("authorization")?.to_str().ok()?; + let token = auth_header.strip_prefix("Bearer ")?; + + match crate::auth::verify_token(token) { + Ok(claims) => claims.namespace.clone(), + Err(_) => None, + } +} + +#[cfg(not(feature = "auth"))] +fn extract_namespace_from_token(_req: &Request) -> Option { + None +} + +/// Helper: check if a subject belongs to the given namespace. +pub fn is_in_namespace(subject: &str, namespace: &str) -> bool { + subject.starts_with(&format!("{}:", namespace)) +} + +/// Helper: scope a subject to a namespace if not already scoped. +pub fn scope_subject(subject: &str, namespace: &str) -> String { + if subject.starts_with(&format!("{}:", namespace)) { + subject.to_string() + } else { + format!("{}:{}", namespace, subject) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_is_in_namespace() { + assert!(is_in_namespace("mayros:agent:a1", "mayros")); + assert!(!is_in_namespace("other:agent:a1", "mayros")); + assert!(!is_in_namespace("agent:a1", "mayros")); + } + + #[test] + fn test_scope_subject() { + assert_eq!(scope_subject("agent:a1", "mayros"), "mayros:agent:a1"); + assert_eq!( + scope_subject("mayros:agent:a1", "mayros"), + "mayros:agent:a1" + ); + } +} diff --git a/crates/aingle_cortex/src/middleware/rate_limit.rs b/crates/aingle_cortex/src/middleware/rate_limit.rs index 983d3d8f..dcf490b6 100644 --- a/crates/aingle_cortex/src/middleware/rate_limit.rs +++ b/crates/aingle_cortex/src/middleware/rate_limit.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Rate limiting middleware using Token Bucket algorithm //! //! This module implements a token bucket rate limiter that tracks requests per IP address. @@ -23,15 +26,13 @@ //! ``` use axum::{ - extract::Request, + extract::{ConnectInfo, Request}, http::{HeaderValue, StatusCode}, - middleware::Next, response::{IntoResponse, Response}, }; -use axum_client_ip::{InsecureClientIp, SecureClientIp}; use dashmap::DashMap; use std::{ - net::IpAddr, + net::{IpAddr, SocketAddr}, sync::Arc, time::{Duration, Instant}, }; @@ -64,10 +65,10 @@ impl IntoResponse for RateLimitError { ) .into_response(); - // Add Retry-After header + // Add Retry-After header (infallible: From for HeaderValue) response.headers_mut().insert( "Retry-After", - HeaderValue::from_str(&secs.to_string()).unwrap(), + HeaderValue::from(*secs), ); // Add rate limit headers @@ -277,23 +278,22 @@ where let mut inner = self.inner.clone(); Box::pin(async move { - // Extract IP address + // Extract IP address. + // 1. If behind a proxy, try X-Forwarded-For / X-Real-IP headers. + // 2. Fall back to ConnectInfo (direct connection IP). let ip = if limiter.secure_ip { - // Try secure extraction first (X-Forwarded-For, etc.) - req.extensions() - .get::() - .map(|ip| ip.0) - .or_else(|| req.extensions().get::().map(|ip| ip.0)) + extract_proxy_ip(&req) + .or_else(|| extract_connect_ip(&req)) } else { - // Use direct connection IP - req.extensions().get::().map(|ip| ip.0) + extract_connect_ip(&req) + .or_else(|| extract_proxy_ip(&req)) }; let ip = match ip { Some(ip) => ip, None => { - // No IP available - return error - return Ok(RateLimitError::IpNotAvailable.into_response()); + // Last resort: assume localhost for sidecar usage. + IpAddr::V4(std::net::Ipv4Addr::LOCALHOST) } }; @@ -303,15 +303,15 @@ where // Call inner service let mut response = inner.call(req).await?; - // Add rate limit headers + // Add rate limit headers (infallible: From/From) let headers = response.headers_mut(); headers.insert( "X-RateLimit-Limit", - HeaderValue::from_str(&limiter.requests_per_minute.to_string()).unwrap(), + HeaderValue::from(limiter.requests_per_minute), ); headers.insert( "X-RateLimit-Remaining", - HeaderValue::from_str(&remaining.to_string()).unwrap(), + HeaderValue::from(remaining), ); Ok(response) @@ -325,32 +325,34 @@ where } } -/// Axum middleware function (alternative to layer) -pub async fn rate_limit_middleware( - InsecureClientIp(ip): InsecureClientIp, - req: Request, - next: Next, -) -> Result { - // This is a simpler version that can be used with axum::middleware::from_fn - // For production use, prefer the Layer-based approach above - - // You would need to pass the limiter through state or create it here - let limiter = RateLimiter::new(100); - - match limiter.check(ip) { - Ok(remaining) => { - let mut response = next.run(req).await; - - // Add headers - response.headers_mut().insert( - "X-RateLimit-Remaining", - HeaderValue::from_str(&remaining.to_string()).unwrap(), - ); - - Ok(response) +/// Extract client IP from `ConnectInfo` (direct connection). +fn extract_connect_ip(req: &Request) -> Option { + req.extensions() + .get::>() + .map(|ci| ci.0.ip()) +} + +/// Extract client IP from proxy headers (`X-Forwarded-For`, `X-Real-IP`). +fn extract_proxy_ip(req: &Request) -> Option { + // Try X-Forwarded-For first (first IP in the chain is the client) + if let Some(xff) = req.headers().get("x-forwarded-for") { + if let Ok(value) = xff.to_str() { + if let Some(first) = value.split(',').next() { + if let Ok(ip) = first.trim().parse::() { + return Some(ip); + } + } + } + } + // Try X-Real-IP + if let Some(xri) = req.headers().get("x-real-ip") { + if let Ok(value) = xri.to_str() { + if let Ok(ip) = value.trim().parse::() { + return Some(ip); + } } - Err(err) => Err(err), } + None } #[cfg(test)] diff --git a/crates/aingle_cortex/src/p2p/config.rs b/crates/aingle_cortex/src/p2p/config.rs new file mode 100644 index 00000000..68ab7fb3 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/config.rs @@ -0,0 +1,184 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! P2P configuration with CLI flag parsing and validation. + +use std::net::SocketAddr; +use std::path::PathBuf; + +/// Configuration for the P2P subsystem. +#[derive(Debug, Clone)] +pub struct P2pConfig { + /// Whether P2P is enabled (`--p2p` flag). + pub enabled: bool, + /// QUIC listen port (`--p2p-port`, default 19091). + pub port: u16, + /// Network isolation seed (`--p2p-seed`). Nodes with different seeds reject each other. + pub seed: Option, + /// Manually specified peer addresses (`--p2p-peer`, repeatable). + pub manual_peers: Vec, + /// Enable mDNS discovery (`--p2p-mdns`). + pub mdns: bool, + /// Gossip interval in milliseconds (default 5000). + pub gossip_interval_ms: u64, + /// Maximum triples per sync batch (default 5000). + pub sync_batch_size: usize, + /// Maximum connected peers (default 32). + pub max_peers: usize, + /// Directory for persistent data (keypair, etc.). + pub data_dir: PathBuf, + /// Max triples accepted per peer per minute (default 1000). + pub max_triples_per_peer_per_min: usize, + /// Max triples accepted globally per minute (default 10000). + pub max_triples_global_per_min: usize, +} + +impl Default for P2pConfig { + fn default() -> Self { + let data_dir = dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".cortex"); + Self { + enabled: false, + port: 19091, + seed: None, + manual_peers: Vec::new(), + mdns: false, + gossip_interval_ms: 5000, + sync_batch_size: 5000, + max_peers: 32, + data_dir, + max_triples_per_peer_per_min: 1000, + max_triples_global_per_min: 10000, + } + } +} + +impl P2pConfig { + /// Validate configuration values. + pub fn validate(&self) -> Result<(), String> { + if self.port < 1024 { + return Err(format!( + "p2p port must be >= 1024, got {}", + self.port + )); + } + + if let Some(ref seed) = self.seed { + if seed.is_empty() { + return Err("p2p seed must not be empty".to_string()); + } + if !seed.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') { + return Err("p2p seed must be alphanumeric (plus _ and -)".to_string()); + } + } + + if self.sync_batch_size < 100 || self.sync_batch_size > 50000 { + return Err(format!( + "sync_batch_size must be 100..50000, got {}", + self.sync_batch_size + )); + } + + if self.gossip_interval_ms < 1000 { + return Err(format!( + "gossip_interval_ms must be >= 1000, got {}", + self.gossip_interval_ms + )); + } + + Ok(()) + } + + /// Parse P2P flags from CLI arguments. + /// + /// Recognises: `--p2p`, `--p2p-port`, `--p2p-seed`, `--p2p-peer`, `--p2p-mdns`. + pub fn from_args(args: &[String]) -> P2pConfig { + let mut cfg = P2pConfig::default(); + let mut i = 0; + while i < args.len() { + match args[i].as_str() { + "--p2p" => cfg.enabled = true, + "--p2p-mdns" => cfg.mdns = true, + "--p2p-port" => { + if i + 1 < args.len() { + if let Ok(p) = args[i + 1].parse::() { + cfg.port = p; + } + i += 1; + } + } + "--p2p-seed" => { + if i + 1 < args.len() { + cfg.seed = Some(args[i + 1].clone()); + i += 1; + } + } + "--p2p-peer" => { + if i + 1 < args.len() { + if let Ok(addr) = args[i + 1].parse::() { + cfg.manual_peers.push(addr); + } + i += 1; + } + } + _ => {} + } + i += 1; + } + cfg + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn defaults_are_valid() { + assert!(P2pConfig::default().validate().is_ok()); + } + + #[test] + fn rejects_invalid_port() { + let mut cfg = P2pConfig::default(); + cfg.port = 0; + assert!(cfg.validate().is_err()); + + cfg.port = 80; + assert!(cfg.validate().is_err()); + } + + #[test] + fn parses_cli_args() { + let args: Vec = vec![ + "--p2p", + "--p2p-port", + "19091", + "--p2p-seed", + "abc123", + "--p2p-peer", + "1.2.3.4:19091", + ] + .into_iter() + .map(String::from) + .collect(); + + let cfg = P2pConfig::from_args(&args); + assert!(cfg.enabled); + assert_eq!(cfg.port, 19091); + assert_eq!(cfg.seed.as_deref(), Some("abc123")); + assert_eq!(cfg.manual_peers.len(), 1); + assert_eq!( + cfg.manual_peers[0], + "1.2.3.4:19091".parse::().unwrap() + ); + } + + #[test] + fn rejects_empty_seed() { + let mut cfg = P2pConfig::default(); + cfg.seed = Some(String::new()); + assert!(cfg.validate().is_err()); + } +} diff --git a/crates/aingle_cortex/src/p2p/dag_sync.rs b/crates/aingle_cortex/src/p2p/dag_sync.rs new file mode 100644 index 00000000..48f7b4fc --- /dev/null +++ b/crates/aingle_cortex/src/p2p/dag_sync.rs @@ -0,0 +1,118 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! DAG action synchronization over P2P. +//! +//! Extends the gossip loop with tip-based DAG sync: nodes exchange their +//! current DAG tips and request missing actions from peers. + +#[cfg(feature = "dag")] +use aingle_graph::dag::DagActionHash; +#[cfg(feature = "dag")] +use aingle_graph::GraphDB; + +/// Collects the local DAG tips as hex strings for tip exchange. +#[cfg(feature = "dag")] +pub fn collect_local_tips(graph: &GraphDB) -> (Vec, u64) { + if let Some(dag_store) = graph.dag_store() { + let tips = dag_store + .tips_raw() + .unwrap_or_default() + .into_iter() + .map(|h| hex::encode(h)) + .collect::>(); + let count = dag_store.action_count() as u64; + (tips, count) + } else { + (Vec::new(), 0) + } +} + +/// Given remote tips, compute which actions we have that the remote is missing, +/// and return them as serialized bytes ready for sending. +#[cfg(feature = "dag")] +pub fn compute_missing_from_tips( + graph: &GraphDB, + remote_tips: &[String], +) -> Vec> { + let Some(dag_store) = graph.dag_store() else { + return Vec::new(); + }; + + let remote_hashes: Vec = remote_tips + .iter() + .filter_map(|h| { + let bytes = hex::decode(h).ok()?; + if bytes.len() == 32 { + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Some(DagActionHash(arr)) + } else { + None + } + }) + .collect(); + + dag_store + .compute_missing(&remote_hashes) + .unwrap_or_default() + .into_iter() + .map(|action| action.to_bytes()) + .collect() +} + +/// Fetch serialized DAG actions by their hex hashes for sending to a peer. +#[cfg(feature = "dag")] +pub fn fetch_actions_by_hash( + graph: &GraphDB, + hashes: &[String], +) -> Vec> { + let Some(dag_store) = graph.dag_store() else { + return Vec::new(); + }; + + hashes + .iter() + .filter_map(|hex_hash| { + let bytes = hex::decode(hex_hash).ok()?; + if bytes.len() != 32 { + return None; + } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + let action = dag_store.get(&DagActionHash(arr)).ok()??; + Some(action.to_bytes()) + }) + .collect() +} + +/// Ingest received DAG actions into the local store. +#[cfg(feature = "dag")] +pub fn ingest_actions( + graph: &GraphDB, + action_bytes_list: &[Vec], +) -> (usize, usize) { + let Some(dag_store) = graph.dag_store() else { + return (0, action_bytes_list.len()); + }; + + let mut ingested = 0; + let mut errors = 0; + + for action_bytes in action_bytes_list { + use aingle_graph::dag::DagAction; + if let Some(action) = DagAction::from_bytes(action_bytes) { + match dag_store.ingest(&action) { + Ok(_) => ingested += 1, + Err(e) => { + tracing::debug!("DAG ingest error: {e}"); + errors += 1; + } + } + } else { + errors += 1; + } + } + + (ingested, errors) +} diff --git a/crates/aingle_cortex/src/p2p/discovery.rs b/crates/aingle_cortex/src/p2p/discovery.rs new file mode 100644 index 00000000..7778d07b --- /dev/null +++ b/crates/aingle_cortex/src/p2p/discovery.rs @@ -0,0 +1,261 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! mDNS-based peer discovery for Cortex P2P. +//! +//! Feature-gated behind `p2p-mdns`. Provides a no-op stub when disabled. + +use std::net::SocketAddr; +use std::time::Instant; + +/// Discovered peer metadata. +#[derive(Debug, Clone)] +pub struct DiscoveredPeer { + pub node_id: String, + pub addr: SocketAddr, + pub seed_hash: String, + pub last_seen: Instant, +} + +// ── Full implementation (p2p-mdns feature) ─────────────────────── + +#[cfg(feature = "p2p-mdns")] +mod inner { + use super::DiscoveredPeer; + use mdns_sd::{ServiceDaemon, ServiceEvent, ServiceInfo}; + use std::collections::HashMap; + use std::net::{IpAddr, SocketAddr}; + use std::sync::{Arc, RwLock}; + use std::time::{Duration, Instant}; + + const SERVICE_TYPE: &str = "_cortex-sync._udp.local."; + + pub struct P2pDiscovery { + daemon: ServiceDaemon, + node_id: String, + seed_hash: String, + port: u16, + peers: Arc>>, + registered: bool, + running: Arc, + } + + impl P2pDiscovery { + pub fn new(node_id: String, seed_hash: String, port: u16) -> Result { + let daemon = ServiceDaemon::new() + .map_err(|e| format!("mDNS daemon: {}", e))?; + Ok(Self { + daemon, + node_id, + seed_hash, + port, + peers: Arc::new(RwLock::new(HashMap::new())), + registered: false, + running: Arc::new(std::sync::atomic::AtomicBool::new(false)), + }) + } + + pub fn register(&mut self) -> Result<(), String> { + if self.registered { + return Ok(()); + } + + let addresses: Vec = if_addrs::get_if_addrs() + .map_err(|e| format!("get interfaces: {}", e))? + .into_iter() + .filter(|iface| !iface.is_loopback()) + .map(|iface| iface.ip()) + .collect(); + + if addresses.is_empty() { + return Err("no network interfaces".to_string()); + } + + let instance_name = format!( + "cortex-{}", + &self.node_id[..8.min(self.node_id.len())] + ); + + let mut props = HashMap::new(); + props.insert("node_id".to_string(), self.node_id.clone()); + props.insert("version".to_string(), env!("CARGO_PKG_VERSION").to_string()); + props.insert("seed_hash".to_string(), self.seed_hash.clone()); + props.insert("p2p_port".to_string(), self.port.to_string()); + + let info = ServiceInfo::new( + SERVICE_TYPE, + &instance_name, + &format!("{}.local.", instance_name), + &addresses[0].to_string(), + self.port, + props, + ) + .map_err(|e| format!("service info: {}", e))?; + + self.daemon + .register(info) + .map_err(|e| format!("register: {}", e))?; + + self.registered = true; + tracing::info!("mDNS registered {} on port {}", instance_name, self.port); + Ok(()) + } + + pub fn start_browsing(&mut self) -> Result<(), String> { + self.running + .store(true, std::sync::atomic::Ordering::SeqCst); + + let receiver = self + .daemon + .browse(SERVICE_TYPE) + .map_err(|e| format!("browse: {}", e))?; + + let peers = self.peers.clone(); + let running = self.running.clone(); + let our_node_id = self.node_id.clone(); + let our_seed_hash = self.seed_hash.clone(); + + std::thread::spawn(move || { + while running.load(std::sync::atomic::Ordering::SeqCst) { + if let Ok(event) = receiver.recv_timeout(Duration::from_millis(100)) { + if let ServiceEvent::ServiceResolved(info) = event { + let node_id = info + .get_properties() + .get("node_id") + .map(|v| v.val_str().to_string()) + .unwrap_or_default(); + + if node_id == our_node_id || node_id.is_empty() { + continue; + } + + let peer_seed_hash = info + .get_properties() + .get("seed_hash") + .map(|v| v.val_str().to_string()) + .unwrap_or_default(); + + if peer_seed_hash != our_seed_hash { + continue; + } + + let p2p_port: u16 = info + .get_properties() + .get("p2p_port") + .and_then(|v| v.val_str().parse().ok()) + .unwrap_or(info.get_port()); + + if let Some(ip) = info.get_addresses().iter().next() { + let addr = SocketAddr::new(ip.to_ip_addr(), p2p_port); + let peer = DiscoveredPeer { + node_id: node_id.clone(), + addr, + seed_hash: peer_seed_hash, + last_seen: Instant::now(), + }; + if let Ok(mut map) = peers.write() { + map.insert(node_id, peer); + } + } + } + } + } + }); + + tracing::info!("mDNS browsing started"); + Ok(()) + } + + pub fn get_discovered_peers(&self) -> Vec { + self.peers + .read() + .map(|m| m.values().cloned().collect()) + .unwrap_or_default() + } + + pub fn stop(&mut self) { + self.running + .store(false, std::sync::atomic::Ordering::SeqCst); + if self.registered { + let instance_name = format!( + "cortex-{}", + &self.node_id[..8.min(self.node_id.len())] + ); + let _ = self + .daemon + .unregister(&format!("{}.{}", instance_name, SERVICE_TYPE)); + self.registered = false; + } + self.daemon.shutdown().ok(); + tracing::info!("mDNS stopped"); + } + } + + impl Drop for P2pDiscovery { + fn drop(&mut self) { + self.stop(); + } + } +} + +// ── Stub implementation (no mdns feature) ──────────────────────── + +#[cfg(not(feature = "p2p-mdns"))] +mod inner { + use super::DiscoveredPeer; + + pub struct P2pDiscovery { + _node_id: String, + } + + impl P2pDiscovery { + pub fn new(node_id: String, _seed_hash: String, _port: u16) -> Result { + Ok(Self { _node_id: node_id }) + } + + pub fn register(&mut self) -> Result<(), String> { + Ok(()) + } + + pub fn start_browsing(&mut self) -> Result<(), String> { + Ok(()) + } + + pub fn get_discovered_peers(&self) -> Vec { + Vec::new() + } + + pub fn stop(&mut self) {} + } +} + +pub use inner::P2pDiscovery; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stub_returns_empty_peers() { + let d = P2pDiscovery::new("nodeid".into(), "seedhash".into(), 19091).unwrap(); + assert!(d.get_discovered_peers().is_empty()); + } + + #[test] + fn stub_register_is_noop() { + let mut d = P2pDiscovery::new("nodeid".into(), "seedhash".into(), 19091).unwrap(); + assert!(d.register().is_ok()); + } + + #[test] + fn discovered_peer_has_required_fields() { + let peer = DiscoveredPeer { + node_id: "abc123".into(), + addr: "127.0.0.1:19091".parse().unwrap(), + seed_hash: "hash".into(), + last_seen: Instant::now(), + }; + assert_eq!(peer.node_id, "abc123"); + assert_eq!(peer.addr.port(), 19091); + } +} diff --git a/crates/aingle_cortex/src/p2p/gossip.rs b/crates/aingle_cortex/src/p2p/gossip.rs new file mode 100644 index 00000000..067afb80 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/gossip.rs @@ -0,0 +1,665 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Bloom filter gossip adapted for triple synchronization. +//! +//! Ported from `aingle_minimal::gossip` with `Hash` replaced by `[u8; 32]` +//! (compatible with `TripleId.0`). + +use std::collections::{BinaryHeap, HashSet, VecDeque}; +use std::time::{Duration, Instant}; + +// ── Constants ──────────────────────────────────────────────────── + +/// Number of bits in the bloom filter. +const BLOOM_FILTER_BITS: usize = 1024; +/// Number of u64 words in the bloom filter. +const BLOOM_FILTER_WORDS: usize = BLOOM_FILTER_BITS / 64; +/// Number of hash functions. +const BLOOM_HASH_COUNT: usize = 3; +/// Maximum tokens in the rate-limit bucket. +const MAX_BUCKET_TOKENS: f64 = 100.0; +/// Minimum backoff between gossip attempts. +const MIN_BACKOFF: Duration = Duration::from_millis(100); +/// Maximum backoff between gossip attempts. +const MAX_BACKOFF: Duration = Duration::from_secs(300); +/// Backoff multiplier on failure. +const BACKOFF_MULTIPLIER: f64 = 2.0; + +// ── BloomFilter ────────────────────────────────────────────────── + +/// Memory-efficient bloom filter operating on `[u8; 32]` keys. +/// +/// Uses packed `u64` words (128 bytes for 1024 bits). +#[derive(Debug, Clone)] +pub struct BloomFilter { + bits: [u64; BLOOM_FILTER_WORDS], + hash_count: usize, + item_count: usize, + bit_count: usize, +} + +impl BloomFilter { + pub fn new() -> Self { + Self { + bits: [0u64; BLOOM_FILTER_WORDS], + hash_count: BLOOM_HASH_COUNT, + item_count: 0, + bit_count: BLOOM_FILTER_BITS, + } + } + + pub fn with_capacity(bits: usize, hash_count: usize) -> Self { + let bit_count = bits.min(BLOOM_FILTER_BITS); + Self { + bits: [0u64; BLOOM_FILTER_WORDS], + hash_count, + item_count: 0, + bit_count, + } + } + + /// Insert a 32-byte key. + #[inline] + pub fn insert(&mut self, key: &[u8; 32]) { + for i in 0..self.hash_count { + let index = self.hash_index(key, i); + let word_idx = index / 64; + let bit_idx = index % 64; + self.bits[word_idx] |= 1u64 << bit_idx; + } + self.item_count += 1; + } + + /// Check membership (may have false positives). + #[inline] + pub fn may_contain(&self, key: &[u8; 32]) -> bool { + for i in 0..self.hash_count { + let index = self.hash_index(key, i); + let word_idx = index / 64; + let bit_idx = index % 64; + if (self.bits[word_idx] & (1u64 << bit_idx)) == 0 { + return false; + } + } + true + } + + pub fn clear(&mut self) { + self.bits = [0u64; BLOOM_FILTER_WORDS]; + self.item_count = 0; + } + + #[inline] + pub fn len(&self) -> usize { + self.item_count + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.item_count == 0 + } + + pub fn estimated_false_positive_rate(&self) -> f64 { + if self.item_count == 0 { + return 0.0; + } + let m = self.bit_count as f64; + let k = self.hash_count as f64; + let n = self.item_count as f64; + (1.0 - (-k * n / m).exp()).powf(k) + } + + #[inline] + fn hash_index(&self, key: &[u8; 32], seed: usize) -> usize { + let base = u64::from_le_bytes([ + key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7], + ]); + let mixed = base + .wrapping_mul(0x9e3779b97f4a7c15u64.wrapping_add(seed as u64)) + .wrapping_add(seed as u64); + let mixed = mixed ^ (mixed >> 33); + let mixed = mixed.wrapping_mul(0xff51afd7ed558ccdu64); + (mixed as usize) % self.bit_count + } + + /// Serialize to 128 bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::with_capacity(BLOOM_FILTER_WORDS * 8); + for word in &self.bits { + bytes.extend_from_slice(&word.to_le_bytes()); + } + bytes + } + + /// Deserialize from bytes. + pub fn from_bytes(bytes: &[u8]) -> Self { + let mut bits = [0u64; BLOOM_FILTER_WORDS]; + for (i, chunk) in bytes.chunks(8).take(BLOOM_FILTER_WORDS).enumerate() { + let mut arr = [0u8; 8]; + arr[..chunk.len()].copy_from_slice(chunk); + bits[i] = u64::from_le_bytes(arr); + } + Self { + bits, + hash_count: BLOOM_HASH_COUNT, + item_count: 0, + bit_count: BLOOM_FILTER_BITS, + } + } +} + +impl Default for BloomFilter { + fn default() -> Self { + Self::new() + } +} + +// ── TokenBucket ────────────────────────────────────────────────── + +/// Token-bucket rate limiter for gossip traffic. +#[derive(Debug)] +pub struct TokenBucket { + tokens: f64, + max_tokens: f64, + refill_rate: f64, + last_refill: Instant, +} + +impl TokenBucket { + /// Create a bucket sized for `rate_mbps` megabits/sec. + pub fn new(rate_mbps: f64) -> Self { + let refill_rate = (rate_mbps * 125_000.0) / 1024.0; + Self { + tokens: MAX_BUCKET_TOKENS, + max_tokens: MAX_BUCKET_TOKENS, + refill_rate, + last_refill: Instant::now(), + } + } + + pub fn with_params(max_tokens: f64, refill_rate: f64) -> Self { + Self { + tokens: max_tokens, + max_tokens, + refill_rate, + last_refill: Instant::now(), + } + } + + pub fn try_consume(&mut self, tokens: f64) -> bool { + self.refill(); + if self.tokens >= tokens { + self.tokens -= tokens; + true + } else { + false + } + } + + pub fn has_tokens(&mut self, tokens: f64) -> bool { + self.refill(); + self.tokens >= tokens + } + + pub fn available(&mut self) -> f64 { + self.refill(); + self.tokens + } + + fn refill(&mut self) { + let now = Instant::now(); + let elapsed = now.duration_since(self.last_refill).as_secs_f64(); + if elapsed > 0.0 { + self.tokens = (self.tokens + elapsed * self.refill_rate).min(self.max_tokens); + self.last_refill = now; + } + } + + pub fn time_until_available(&mut self, tokens: f64) -> Duration { + self.refill(); + if self.tokens >= tokens { + Duration::ZERO + } else { + let needed = tokens - self.tokens; + Duration::from_secs_f64(needed / self.refill_rate) + } + } +} + +// ── MessagePriority & Queue ────────────────────────────────────── + +/// Priority levels for gossip messages. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum MessagePriority { + Low = 0, + Normal = 1, + High = 2, + Critical = 3, +} + +#[derive(Debug)] +pub struct PrioritizedMessage { + pub message: T, + pub priority: MessagePriority, + pub queued_at: Instant, + sequence: u64, +} + +impl PartialEq for PrioritizedMessage { + fn eq(&self, other: &Self) -> bool { + self.priority == other.priority && self.sequence == other.sequence + } +} +impl Eq for PrioritizedMessage {} + +impl PartialOrd for PrioritizedMessage { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PrioritizedMessage { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + match self.priority.cmp(&other.priority) { + std::cmp::Ordering::Equal => other.sequence.cmp(&self.sequence), + ord => ord, + } + } +} + +/// Priority queue for gossip messages. +#[derive(Debug)] +pub struct MessageQueue { + heap: BinaryHeap>, + sequence: u64, + max_size: usize, +} + +impl MessageQueue { + pub fn new(max_size: usize) -> Self { + Self { + heap: BinaryHeap::with_capacity(max_size), + sequence: 0, + max_size, + } + } + + pub fn push(&mut self, message: T, priority: MessagePriority) -> bool { + if self.heap.len() >= self.max_size { + return false; + } + self.sequence += 1; + self.heap.push(PrioritizedMessage { + message, + priority, + queued_at: Instant::now(), + sequence: self.sequence, + }); + true + } + + pub fn pop(&mut self) -> Option { + self.heap.pop().map(|pm| pm.message) + } + + pub fn peek(&self) -> Option<&T> { + self.heap.peek().map(|pm| &pm.message) + } + + pub fn len(&self) -> usize { + self.heap.len() + } + + pub fn is_empty(&self) -> bool { + self.heap.is_empty() + } + + pub fn clear(&mut self) { + self.heap.clear(); + } +} + +impl Default for MessageQueue { + fn default() -> Self { + Self::new(100) + } +} + +// ── PeerGossipState ────────────────────────────────────────────── + +/// Per-peer gossip state with adaptive backoff. +#[derive(Debug, Clone)] +pub struct PeerGossipState { + pub failures: u32, + pub successes: u32, + pub current_backoff: Duration, + pub last_attempt: Instant, + pub known_ids: BloomFilter, +} + +impl PeerGossipState { + pub fn new() -> Self { + Self { + failures: 0, + successes: 0, + current_backoff: MIN_BACKOFF, + last_attempt: Instant::now(), + known_ids: BloomFilter::new(), + } + } + + pub fn record_success(&mut self) { + self.failures = 0; + self.successes = self.successes.saturating_add(1); + self.current_backoff = Duration::from_millis( + (self.current_backoff.as_millis() as f64 / BACKOFF_MULTIPLIER) as u64, + ) + .max(MIN_BACKOFF); + self.last_attempt = Instant::now(); + } + + pub fn record_failure(&mut self) { + self.successes = 0; + self.failures = self.failures.saturating_add(1); + self.current_backoff = Duration::from_millis( + (self.current_backoff.as_millis() as f64 * BACKOFF_MULTIPLIER) as u64, + ) + .min(MAX_BACKOFF); + self.last_attempt = Instant::now(); + } + + pub fn should_gossip(&self) -> bool { + self.last_attempt.elapsed() >= self.current_backoff + } +} + +impl Default for PeerGossipState { + fn default() -> Self { + Self::new() + } +} + +// ── TripleGossipManager ────────────────────────────────────────── + +/// Gossip manager adapted for triple IDs (`[u8; 32]`). +#[derive(Debug)] +pub struct TripleGossipManager { + /// Pending IDs to announce. + pending_announcements: VecDeque<[u8; 32]>, + /// Local bloom filter of known IDs. + local_filter: BloomFilter, + /// Recent IDs (dedup set). + recent_ids: HashSet<[u8; 32]>, + max_recent: usize, + /// Gossip round counter. + round: u64, +} + +impl TripleGossipManager { + pub fn new() -> Self { + Self { + pending_announcements: VecDeque::with_capacity(100), + local_filter: BloomFilter::new(), + recent_ids: HashSet::with_capacity(1000), + max_recent: 1000, + round: 0, + } + } + + /// Register a new local triple for announcement. + pub fn announce(&mut self, id: [u8; 32]) { + self.local_filter.insert(&id); + + if self.recent_ids.len() >= self.max_recent { + self.recent_ids.clear(); + } + self.recent_ids.insert(id); + self.pending_announcements.push_back(id); + } + + /// Check if we already know about an ID. + pub fn is_known(&self, id: &[u8; 32]) -> bool { + self.recent_ids.contains(id) || self.local_filter.may_contain(id) + } + + /// Remove an ID from the recent set (cannot remove from bloom filter). + pub fn remove_known(&mut self, id: &[u8; 32]) { + self.recent_ids.remove(id); + } + + /// Register a known ID (e.g. received from peer). + pub fn add_known(&mut self, id: [u8; 32]) { + self.local_filter.insert(&id); + if self.recent_ids.len() < self.max_recent { + self.recent_ids.insert(id); + } + } + + /// Find IDs that exist in `our_ids` but are missing from `peer_filter`. + pub fn find_missing( + &self, + peer_filter: &BloomFilter, + our_ids: &[[u8; 32]], + ) -> Vec<[u8; 32]> { + our_ids + .iter() + .filter(|id| !peer_filter.may_contain(id)) + .copied() + .collect() + } + + /// Get a reference to the local bloom filter. + pub fn get_bloom_filter(&self) -> &BloomFilter { + &self.local_filter + } + + /// Drain pending announcements (up to `limit`). + pub fn take_announcements(&mut self, limit: usize) -> Vec<[u8; 32]> { + let count = limit.min(self.pending_announcements.len()); + self.pending_announcements.drain(..count).collect() + } + + pub fn gossip_complete(&mut self) { + self.round += 1; + } + + pub fn round(&self) -> u64 { + self.round + } + + /// Current statistics. + pub fn stats(&self) -> GossipStats { + GossipStats { + round: self.round, + pending_announcements: self.pending_announcements.len(), + known_ids: self.recent_ids.len(), + bloom_filter_items: self.local_filter.len(), + bloom_filter_fpr: self.local_filter.estimated_false_positive_rate(), + } + } +} + +impl Default for TripleGossipManager { + fn default() -> Self { + Self::new() + } +} + +/// Gossip statistics. +#[derive(Debug, Clone, serde::Serialize)] +pub struct GossipStats { + pub round: u64, + pub pending_announcements: usize, + pub known_ids: usize, + pub bloom_filter_items: usize, + pub bloom_filter_fpr: f64, +} + +#[cfg(test)] +mod tests { + use super::*; + + // ── BloomFilter tests ──────────────────────────────────── + + #[test] + fn bloom_insert_contains() { + let mut filter = BloomFilter::new(); + let key = [1u8; 32]; + assert!(!filter.may_contain(&key)); + filter.insert(&key); + assert!(filter.may_contain(&key)); + assert_eq!(filter.len(), 1); + } + + #[test] + fn bloom_serialization() { + let mut filter = BloomFilter::new(); + let key = [1u8; 32]; + filter.insert(&key); + let bytes = filter.to_bytes(); + let restored = BloomFilter::from_bytes(&bytes); + assert!(restored.may_contain(&key)); + } + + #[test] + fn bloom_false_positive_rate() { + let mut filter = BloomFilter::new(); + assert_eq!(filter.estimated_false_positive_rate(), 0.0); + for i in 0..10u8 { + filter.insert(&[i; 32]); + } + let fpr = filter.estimated_false_positive_rate(); + assert!(fpr > 0.0 && fpr < 1.0); + } + + #[test] + fn bloom_to_bytes_size() { + let filter = BloomFilter::new(); + assert_eq!(filter.to_bytes().len(), 128); + } + + #[test] + fn bloom_round_trip_many() { + let mut filter = BloomFilter::new(); + for i in 0..50u8 { + filter.insert(&[i; 32]); + } + let bytes = filter.to_bytes(); + let restored = BloomFilter::from_bytes(&bytes); + for i in 0..50u8 { + assert!(restored.may_contain(&[i; 32])); + } + } + + // ── TokenBucket tests ──────────────────────────────────── + + #[test] + fn token_bucket_consume() { + let mut bucket = TokenBucket::new(1.0); + assert!(bucket.try_consume(1.0)); + // Drain + for _ in 0..200 { + bucket.try_consume(1.0); + } + assert!(!bucket.try_consume(100.0)); + } + + #[test] + fn token_bucket_refill() { + let mut bucket = TokenBucket::with_params(10.0, 1000.0); + for _ in 0..15 { + bucket.try_consume(1.0); + } + std::thread::sleep(Duration::from_millis(20)); + assert!(bucket.has_tokens(1.0)); + } + + // ── MessageQueue tests ─────────────────────────────────── + + #[test] + fn message_queue_priority_ordering() { + let mut queue: MessageQueue = MessageQueue::new(10); + queue.push("low".into(), MessagePriority::Low); + queue.push("critical".into(), MessagePriority::Critical); + queue.push("normal".into(), MessagePriority::Normal); + queue.push("high".into(), MessagePriority::High); + + assert_eq!(queue.pop(), Some("critical".into())); + assert_eq!(queue.pop(), Some("high".into())); + assert_eq!(queue.pop(), Some("normal".into())); + assert_eq!(queue.pop(), Some("low".into())); + } + + // ── PeerGossipState tests ──────────────────────────────── + + #[test] + fn peer_gossip_state_backoff() { + let mut state = PeerGossipState::new(); + std::thread::sleep(MIN_BACKOFF + Duration::from_millis(10)); + assert!(state.should_gossip()); + + let initial = state.current_backoff; + state.record_failure(); + assert!(state.current_backoff > initial); + assert!(!state.should_gossip()); + + std::thread::sleep(state.current_backoff + Duration::from_millis(10)); + assert!(state.should_gossip()); + + let before = state.current_backoff; + state.record_success(); + assert!(state.current_backoff < before); + } + + // ── TripleGossipManager tests ──────────────────────────── + + #[test] + fn announce_adds_to_filter() { + let mut mgr = TripleGossipManager::new(); + let id = [42u8; 32]; + mgr.announce(id); + assert!(mgr.is_known(&id)); + } + + #[test] + fn find_missing_returns_delta() { + let mgr = TripleGossipManager::new(); + let a = [1u8; 32]; + let b = [2u8; 32]; + let c = [3u8; 32]; + let d = [4u8; 32]; + + let mut peer_filter = BloomFilter::new(); + peer_filter.insert(&a); + peer_filter.insert(&b); + + let missing = mgr.find_missing(&peer_filter, &[a, b, c, d]); + assert_eq!(missing.len(), 2); + assert!(missing.contains(&c)); + assert!(missing.contains(&d)); + } + + #[test] + fn remove_known_from_recent() { + let mut mgr = TripleGossipManager::new(); + let id = [42u8; 32]; + mgr.announce(id); + assert!(mgr.recent_ids.contains(&id)); + mgr.remove_known(&id); + assert!(!mgr.recent_ids.contains(&id)); + // Still in bloom filter (cannot remove), so is_known may still return true + } + + #[test] + fn stats_reflect_state() { + let mut mgr = TripleGossipManager::new(); + mgr.announce([1u8; 32]); + mgr.announce([2u8; 32]); + let stats = mgr.stats(); + assert_eq!(stats.pending_announcements, 2); + assert_eq!(stats.bloom_filter_items, 2); + assert_eq!(stats.known_ids, 2); + assert_eq!(stats.round, 0); + } +} diff --git a/crates/aingle_cortex/src/p2p/identity.rs b/crates/aingle_cortex/src/p2p/identity.rs new file mode 100644 index 00000000..cf964682 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/identity.rs @@ -0,0 +1,155 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Node identity backed by Ed25519 keypair with persistent storage. + +use aingle_graph::{Triple, TripleId}; +use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey}; +use rand::RngCore; +use std::path::Path; + +/// Persistent node identity for P2P authentication. +pub struct NodeIdentity { + signing_key: SigningKey, +} + +impl NodeIdentity { + /// Load an existing keypair from `{data_dir}/node.key`, or generate and persist a new one. + pub fn load_or_generate(data_dir: &Path) -> std::io::Result { + let key_path = data_dir.join("node.key"); + + if key_path.exists() { + let seed = std::fs::read(&key_path)?; + if seed.len() != 32 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + "node.key must be exactly 32 bytes", + )); + } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&seed); + Ok(Self { + signing_key: SigningKey::from_bytes(&arr), + }) + } else { + let mut rng = rand::rng(); + let mut seed = [0u8; 32]; + rng.fill_bytes(&mut seed); + + std::fs::create_dir_all(data_dir)?; + + // Write with restrictive permissions (Unix 0o600). + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + use std::io::Write; + let mut f = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .mode(0o600) + .open(&key_path)?; + f.write_all(&seed)?; + f.sync_all()?; + } + #[cfg(not(unix))] + { + let mut f = std::fs::File::create(&key_path)?; + std::io::Write::write_all(&mut f, &seed)?; + f.sync_all()?; + } + + Ok(Self { + signing_key: SigningKey::from_bytes(&seed), + }) + } + } + + /// Hex-encoded public key (64 characters). + pub fn node_id(&self) -> String { + hex::encode(self.signing_key.verifying_key().to_bytes()) + } + + /// Raw 32-byte public key. + pub fn public_key(&self) -> [u8; 32] { + self.signing_key.verifying_key().to_bytes() + } + + /// Sign arbitrary data with Ed25519. + pub fn sign(&self, data: &[u8]) -> [u8; 64] { + self.signing_key.sign(data).to_bytes() + } +} + +/// Verify an Ed25519 signature against a public key. +pub fn verify(pubkey: &[u8; 32], data: &[u8], sig: &[u8; 64]) -> bool { + let Ok(vk) = VerifyingKey::from_bytes(pubkey) else { + return false; + }; + let signature = ed25519_dalek::Signature::from_bytes(sig); + vk.verify(data, &signature).is_ok() +} + +/// Convenience wrapper: compute the TripleId hash bytes for a triple. +pub fn triple_hash(triple: &Triple) -> [u8; 32] { + TripleId::from_triple(triple).0 +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + #[test] + fn generate_creates_key_file() { + let dir = TempDir::new().unwrap(); + let _ = NodeIdentity::load_or_generate(dir.path()).unwrap(); + assert!(dir.path().join("node.key").exists()); + } + + #[test] + fn load_returns_same_identity() { + let dir = TempDir::new().unwrap(); + let id1 = NodeIdentity::load_or_generate(dir.path()).unwrap(); + let id2 = NodeIdentity::load_or_generate(dir.path()).unwrap(); + assert_eq!(id1.node_id(), id2.node_id()); + } + + #[test] + fn sign_and_verify() { + let dir = TempDir::new().unwrap(); + let id = NodeIdentity::load_or_generate(dir.path()).unwrap(); + let data = b"hello cortex p2p"; + let sig = id.sign(data); + assert!(verify(&id.public_key(), data, &sig)); + } + + #[test] + fn verify_rejects_bad_signature() { + let dir = TempDir::new().unwrap(); + let id = NodeIdentity::load_or_generate(dir.path()).unwrap(); + let data = b"hello cortex p2p"; + let mut sig = id.sign(data); + sig[0] ^= 0xff; + assert!(!verify(&id.public_key(), data, &sig)); + } + + #[cfg(unix)] + #[test] + fn file_permissions_are_restrictive() { + use std::os::unix::fs::MetadataExt; + let dir = TempDir::new().unwrap(); + let _ = NodeIdentity::load_or_generate(dir.path()).unwrap(); + let meta = std::fs::metadata(dir.path().join("node.key")).unwrap(); + assert_eq!(meta.mode() & 0o777, 0o600); + } + + #[test] + fn node_id_is_64_hex_chars() { + let dir = TempDir::new().unwrap(); + let id = NodeIdentity::load_or_generate(dir.path()).unwrap(); + let nid = id.node_id(); + assert_eq!(nid.len(), 64); + assert!(nid.chars().all(|c| c.is_ascii_hexdigit())); + } +} diff --git a/crates/aingle_cortex/src/p2p/manager.rs b/crates/aingle_cortex/src/p2p/manager.rs new file mode 100644 index 00000000..44c00d8a --- /dev/null +++ b/crates/aingle_cortex/src/p2p/manager.rs @@ -0,0 +1,1215 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! P2P manager — orchestrates identity, transport, gossip, sync, and discovery. + +use crate::p2p::config::P2pConfig; +use crate::p2p::discovery::P2pDiscovery; +use crate::p2p::gossip::{BloomFilter, GossipStats, TripleGossipManager}; +use crate::p2p::message::{P2pMessage, TombstoneWire, TripleWire}; +use crate::p2p::peer_store::{PeerSource, PeerStore, StoredPeer}; +use crate::p2p::rate_limiter::IngressRateLimiter; +use crate::p2p::sync_manager::{SyncStats, TripleSyncManager}; +use crate::p2p::transport::{P2pTransport, P2pTransportConfig}; +use crate::state::{AppState, Event}; + +use aingle_graph::TripleId; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::time::{Duration, Instant}; +use tokio::sync::RwLock; + +// ── Manual peer reconnection (A2) ──────────────────────────────── + +/// Tracks reconnection state for a manually-configured peer. +struct ManualPeerTracker { + addr: SocketAddr, + retries: u32, + max_retries: u32, + current_backoff: Duration, + last_attempt: Instant, + abandoned: bool, +} + +impl ManualPeerTracker { + fn new(addr: SocketAddr) -> Self { + Self { + addr, + retries: 0, + max_retries: 10, + current_backoff: Duration::from_secs(5), + last_attempt: Instant::now(), + abandoned: false, + } + } + + fn should_retry(&self) -> bool { + !self.abandoned && self.last_attempt.elapsed() >= self.current_backoff + } + + fn record_failure(&mut self) { + self.retries += 1; + self.last_attempt = Instant::now(); + self.current_backoff = Duration::from_secs( + (self.current_backoff.as_secs() * 2).min(300), + ); + if self.retries >= self.max_retries { + self.abandoned = true; + } + } + + fn record_success(&mut self) { + self.retries = 0; + self.current_backoff = Duration::from_secs(5); + self.abandoned = false; + self.last_attempt = Instant::now(); + } +} + +// ── Health check tracking (A4) ─────────────────────────────────── + +/// Tracks outstanding pings for health checking. +struct PingTracker { + outstanding: HashMap, + timeout: Duration, +} + +impl PingTracker { + fn new(timeout: Duration) -> Self { + Self { + outstanding: HashMap::new(), + timeout, + } + } + + fn record_ping(&mut self, addr: SocketAddr, timestamp_ms: u64) { + self.outstanding.insert(addr, (timestamp_ms, Instant::now())); + } + + fn record_pong(&mut self, addr: &SocketAddr, _timestamp_ms: u64) { + self.outstanding.remove(addr); + } + + fn timed_out_peers(&self) -> Vec { + self.outstanding + .iter() + .filter(|(_, (_, sent))| sent.elapsed() >= self.timeout) + .map(|(addr, _)| *addr) + .collect() + } + + fn clear(&mut self, addr: &SocketAddr) { + self.outstanding.remove(addr); + } +} + +// ── Health events passed between tasks ─────────────────────────── + +enum HealthEvent { + PongReceived { addr: SocketAddr, timestamp_ms: u64 }, +} + +// ── P2P Manager ────────────────────────────────────────────────── + +/// Orchestrator for the entire P2P subsystem. +pub struct P2pManager { + config: P2pConfig, + node_id: String, + gossip: Arc>, + sync: Arc>, + transport: Arc>, + discovery: Arc>, + running: Arc, + tasks: Vec>, +} + +/// Serializable P2P status. +#[derive(Debug, Clone, serde::Serialize)] +pub struct P2pStatus { + pub node_id: String, + pub enabled: bool, + pub port: u16, + pub peer_count: usize, + pub connected_peers: Vec, + pub gossip_stats: GossipStats, + pub sync_stats: SyncStats, +} + +/// Per-peer status DTO. +#[derive(Debug, Clone, serde::Serialize)] +pub struct PeerStatusDto { + pub addr: String, + pub connected: bool, +} + +impl P2pManager { + /// Start the P2P subsystem: load identity, bind transport, connect manual peers, + /// start discovery, and spawn background tasks. + pub async fn start(config: P2pConfig, app_state: AppState) -> Result, String> { + // 1. Load or generate node identity. + let identity = crate::p2p::identity::NodeIdentity::load_or_generate(&config.data_dir) + .map_err(|e| format!("identity: {}", e))?; + let node_id = identity.node_id(); + + // 2. Compute seed hash. + let seed_hash = if let Some(ref seed) = config.seed { + hex::encode(blake3::hash(seed.as_bytes()).as_bytes()) + } else { + String::new() + }; + + // 3. Create gossip & sync managers. + let gossip = Arc::new(RwLock::new(TripleGossipManager::new())); + let sync = Arc::new(RwLock::new(TripleSyncManager::new(Duration::from_millis( + config.gossip_interval_ms, + )))); + + // 4. Create transport and start. + let transport_config = P2pTransportConfig { + port: config.port, + max_connections: config.max_peers * 2, + ..Default::default() + }; + let mut transport_inner = + P2pTransport::new(transport_config, node_id.clone(), seed_hash.clone()); + transport_inner.start().await?; + + // 5. Rebuild local IDs from graph. + { + let graph = app_state.graph.read().await; + sync.write().await.rebuild_local_ids(&graph); + } + + // A3: Load persistent peer store and merge with manual peers. + let peer_store = Arc::new(RwLock::new( + PeerStore::load(&config.data_dir, config.max_peers * 2), + )); + + // 6. Connect to manual peers + persisted peers. + let triple_count = { + let s = sync.read().await; + s.local_ids().len() as u64 + }; + + // Connect manual peers + for peer_addr in &config.manual_peers { + match transport_inner.connect(*peer_addr, triple_count).await { + Ok(()) => { + tracing::info!("P2P connected to manual peer {}", peer_addr); + // Register in sync manager so gossip can reach this peer. + sync.write().await.get_peer_state(peer_addr); + let now_ms = now_millis(); + let mut ps = peer_store.write().await; + ps.add(StoredPeer { + addr: *peer_addr, + node_id: None, + last_connected_ms: now_ms, + source: PeerSource::Manual, + }); + let _ = ps.save(); + } + Err(e) => tracing::warn!("P2P failed to connect to {}: {}", peer_addr, e), + } + } + + // Connect persisted peers not already connected + { + let ps = peer_store.read().await; + for stored in ps.all() { + if !transport_inner.is_connected(&stored.addr) + && !config.manual_peers.contains(&stored.addr) + { + match transport_inner.connect(stored.addr, triple_count).await { + Ok(()) => { + tracing::info!("P2P reconnected to persisted peer {}", stored.addr); + sync.write().await.get_peer_state(&stored.addr); + } + Err(e) => { + tracing::debug!("P2P persisted peer {} unreachable: {}", stored.addr, e); + } + } + } + } + } + + let transport = Arc::new(RwLock::new(transport_inner)); + + // A6: Create ingress rate limiter. + let rate_limiter = Arc::new(RwLock::new(IngressRateLimiter::new( + config.max_triples_per_peer_per_min, + config.max_triples_global_per_min, + ))); + + // 7. Discovery. + let mut disc = + P2pDiscovery::new(node_id.clone(), seed_hash.clone(), config.port)?; + if config.mdns { + disc.register()?; + disc.start_browsing()?; + } + let discovery = Arc::new(RwLock::new(disc)); + + let running = Arc::new(AtomicBool::new(true)); + let mut tasks = Vec::new(); + + // A4: Health event channel between Task 3 (sender) and Task 6 (receiver). + let (health_tx, health_rx) = tokio::sync::mpsc::channel::(256); + + // ── Task 0: Accept incoming connections ─────────────── + // + // Accepts new QUIC connections without holding the transport lock. + // Clones the endpoint (Arc-based, cheap), accepts + handshakes + // outside the lock, then briefly write-locks to store the connection. + { + let transport = transport.clone(); + let sync = sync.clone(); + let running = running.clone(); + let accept_seed_hash = seed_hash.clone(); + let peer_store = peer_store.clone(); + + // Get endpoint clone and node_id once. + let (accept_endpoint, accept_node_id) = { + let t = transport.read().await; + (t.endpoint_clone(), t.node_id_str().to_string()) + }; + + if let Some(endpoint) = accept_endpoint { + tasks.push(tokio::spawn(async move { + loop { + if !running.load(Ordering::Relaxed) { + break; + } + + // Wait for incoming connection WITHOUT holding any lock. + let incoming = match endpoint.accept().await { + Some(inc) => inc, + None => break, // endpoint closed + }; + + let connection = match incoming.await { + Ok(conn) => conn, + Err(e) => { + tracing::debug!("P2P accept handshake error: {}", e); + continue; + } + }; + + let remote = connection.remote_address(); + + // Read Hello from the new connection. + let hello = match P2pTransport::recv_from_conn(&connection).await { + Ok(msg) => msg, + Err(e) => { + tracing::debug!("P2P accept recv error from {}: {}", remote, e); + connection.close(1u32.into(), b"read_error"); + continue; + } + }; + + match hello { + P2pMessage::Hello { seed_hash: peer_seed, node_id: peer_nid, .. } => { + let accepted = peer_seed == accept_seed_hash; + let ack = P2pMessage::HelloAck { + node_id: accept_node_id.clone(), + accepted, + reason: if accepted { None } else { Some("seed_mismatch".into()) }, + }; + if P2pTransport::send_on_conn(&connection, &ack).await.is_err() { + continue; + } + + if accepted { + tracing::info!("P2P accepted connection from {} ({})", remote, &peer_nid[..8.min(peer_nid.len())]); + // Store connection (brief write lock). + transport.write().await.store_connection(remote, connection); + // Register in sync manager for gossip. + sync.write().await.get_peer_state(&remote); + // Record in peer store. + let mut ps = peer_store.write().await; + ps.add(StoredPeer { + addr: remote, + node_id: Some(peer_nid), + last_connected_ms: now_millis(), + source: PeerSource::RestApi, + }); + let _ = ps.save(); + } else { + tracing::warn!("P2P rejected connection from {}: seed mismatch", remote); + connection.close(1u32.into(), b"seed_mismatch"); + } + } + _ => { + connection.close(1u32.into(), b"expected_hello"); + } + } + } + })); + } + } + + // ── Task 1: Event listener ─────────────────────────── + { + let gossip = gossip.clone(); + let sync = sync.clone(); + let transport = transport.clone(); + let running = running.clone(); + let broadcaster = app_state.broadcaster.clone(); + let graph = app_state.graph.clone(); + + tasks.push(tokio::spawn(async move { + let mut rx = broadcaster.subscribe(); + loop { + if !running.load(Ordering::Relaxed) { + break; + } + match rx.recv().await { + Ok(Event::TripleAdded { hash, .. }) => { + if let Some(id) = TripleId::from_hex(&hash) { + gossip.write().await.announce(id.0); + sync.write().await.add_local_id(id.0); + } + } + // A1: Handle triple deletions — tombstone propagation. + Ok(Event::TripleDeleted { hash }) => { + if let Some(id) = TripleId::from_hex(&hash) { + // 1. Remove from gossip recent_ids + gossip.write().await.remove_known(&id.0); + // 2. Remove from sync local_ids + let mut s = sync.write().await; + s.remove_local_id(&id.0); + // 3. Add tombstone + let ts_ms = now_millis(); + s.add_tombstone(id.0, ts_ms); + drop(s); + // 4. Broadcast AnnounceDelete to all connected peers + let peers = transport.read().await.connected_peers(); + let msg = P2pMessage::AnnounceDelete { + triple_id: hash, + tombstone_ts: ts_ms, + }; + let t = transport.read().await; + for peer_addr in &peers { + let _ = t.send(peer_addr, &msg).await; + } + } + } + Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => { + tracing::warn!("P2P event listener lagged by {} events, rebuilding", n); + let g = graph.read().await; + sync.write().await.rebuild_local_ids(&g); + } + Err(tokio::sync::broadcast::error::RecvError::Closed) => break, + _ => {} + } + } + })); + } + + // ── Task 2: Gossip loop + tombstone sync + DAG tip sync ─ + { + let sync = sync.clone(); + let transport = transport.clone(); + let running = running.clone(); + let interval = config.gossip_interval_ms; + #[cfg(feature = "dag")] + let graph2 = app_state.graph.clone(); + + tasks.push(tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_millis(interval)).await; + if !running.load(Ordering::Relaxed) { + break; + } + + let peers = sync.read().await.peers_needing_sync(); + for peer_addr in peers { + // Send bloom filter + let filter = sync.read().await.build_local_filter(); + let triple_count = sync.read().await.local_ids().len() as u64; + let msg = P2pMessage::BloomSync { + filter_bytes: filter.to_bytes(), + triple_count, + }; + let t = transport.read().await; + if let Err(e) = t.send(&peer_addr, &msg).await { + tracing::debug!("gossip send to {}: {}", peer_addr, e); + } + + // DAG tip sync: exchange tips with each peer + #[cfg(feature = "dag")] + { + let g = graph2.read().await; + let (tips, action_count) = + crate::p2p::dag_sync::collect_local_tips(&g); + if !tips.is_empty() { + let dag_msg = P2pMessage::DagTipSync { + tips, + action_count, + }; + let _ = t.send(&peer_addr, &dag_msg).await; + } + } + + // A1: Also send active tombstones + let tombstones = sync.read().await.active_tombstones(); + if !tombstones.is_empty() { + let wires: Vec = tombstones + .iter() + .map(|(id, ts)| TombstoneWire { + triple_id: hex::encode(id), + deleted_at_ms: *ts, + }) + .collect(); + let ts_msg = P2pMessage::TombstoneSync { tombstones: wires }; + let _ = t.send(&peer_addr, &ts_msg).await; + } + } + } + })); + } + + // ── Task 3: Incoming message handler ───────────────── + { + let gossip = gossip.clone(); + let sync = sync.clone(); + let transport = transport.clone(); + let running = running.clone(); + let graph = app_state.graph.clone(); + let seed_hash2 = seed_hash.clone(); + let rate_limiter = rate_limiter.clone(); + let peer_store = peer_store.clone(); + let health_tx = health_tx.clone(); + + tasks.push(tokio::spawn(async move { + loop { + if !running.load(Ordering::Relaxed) { + break; + } + // A5: Still uses polling recv (accept_loop would require + // transport refactoring beyond scope; see note below). + // Reduced poll interval from 50ms to 10ms for better latency. + tokio::time::sleep(Duration::from_millis(10)).await; + + let maybe = { + let t = transport.read().await; + t.recv().await + }; + + let (addr, msg) = match maybe { + Ok(Some(pair)) => pair, + _ => continue, + }; + + match msg { + P2pMessage::Hello { + seed_hash: peer_seed, + node_id: peer_node_id, + .. + } => { + let accepted = peer_seed == seed_hash2; + let ack = P2pMessage::HelloAck { + node_id: String::new(), + accepted, + reason: if accepted { + None + } else { + Some("seed_mismatch".into()) + }, + }; + let t = transport.read().await; + let _ = t.send(&addr, &ack).await; + if accepted { + // A3: Record in peer store + let now_ms = now_millis(); + let mut ps = peer_store.write().await; + ps.add(StoredPeer { + addr, + node_id: Some(peer_node_id.clone()), + last_connected_ms: now_ms, + source: PeerSource::RestApi, + }); + let _ = ps.save(); + } else { + tracing::warn!( + "P2P rejected {} from {}: seed mismatch", + &peer_node_id[..8.min(peer_node_id.len())], + addr + ); + } + } + P2pMessage::BloomSync { + filter_bytes, + .. + } => { + let peer_filter = BloomFilter::from_bytes(&filter_bytes); + let local_ids: Vec<[u8; 32]> = + sync.read().await.local_ids().to_vec(); + let missing = + gossip.read().await.find_missing(&peer_filter, &local_ids); + + if !missing.is_empty() { + let g = graph.read().await; + let mut wires = Vec::new(); + for id_bytes in &missing { + let tid = TripleId::new(*id_bytes); + if let Ok(Some(triple)) = g.get(&tid) { + wires.push(TripleWire::from_triple(&triple)); + } + } + if !wires.is_empty() { + let send_msg = P2pMessage::SendTriples { triples: wires }; + let t = transport.read().await; + let _ = t.send(&addr, &send_msg).await; + } + } + } + P2pMessage::RequestTriples { ids } => { + let g = graph.read().await; + let mut wires = Vec::new(); + for hex_id in &ids { + if let Some(tid) = TripleId::from_hex(hex_id) { + if let Ok(Some(triple)) = g.get(&tid) { + wires.push(TripleWire::from_triple(&triple)); + } + } + } + if !wires.is_empty() { + let send_msg = P2pMessage::SendTriples { triples: wires }; + let t = transport.read().await; + let _ = t.send(&addr, &send_msg).await; + } + } + P2pMessage::SendTriples { triples } => { + // A6: Apply backpressure before processing. + let total = triples.len(); + let allowed = rate_limiter.write().await.check(&addr, total); + if allowed < total { + tracing::warn!( + "P2P rate limited {} triples from {} (allowed {}/{})", + total - allowed, + addr, + allowed, + total + ); + } + + let converted: Vec<_> = triples + .iter() + .take(allowed) + .filter_map(|tw| tw.to_triple()) + .collect(); + let g = graph.read().await; + let result = sync + .write() + .await + .store_received_triples(converted, &g); + sync.write() + .await + .record_sync_result(addr, true, result.inserted); + if result.inserted > 0 { + tracing::info!( + "P2P synced {} triples from {} ({} dup, {} err)", + result.inserted, + addr, + result.duplicates, + result.errors + ); + // A3: Update last connected + let mut ps = peer_store.write().await; + ps.update_last_connected(&addr, now_millis()); + let _ = ps.save(); + } + } + P2pMessage::Announce { triple_id } => { + if let Some(tid) = TripleId::from_hex(&triple_id) { + let known = gossip.read().await.is_known(&tid.0); + if !known { + let req = P2pMessage::RequestTriples { + ids: vec![triple_id], + }; + let t = transport.read().await; + let _ = t.send(&addr, &req).await; + } + } + } + // A1: Handle incoming deletion announcement. + P2pMessage::AnnounceDelete { triple_id, tombstone_ts } => { + if let Some(tid) = TripleId::from_hex(&triple_id) { + let mut s = sync.write().await; + if !s.has_tombstone(&tid.0) { + s.remove_local_id(&tid.0); + s.add_tombstone(tid.0, tombstone_ts); + drop(s); + gossip.write().await.remove_known(&tid.0); + // Delete from local graph + let g = graph.read().await; + let _ = g.delete(&tid); + tracing::debug!( + "P2P applied tombstone for {} from {}", + &triple_id[..8.min(triple_id.len())], + addr + ); + } + } + } + // A1: Handle batch tombstone sync. + P2pMessage::TombstoneSync { tombstones } => { + for tw in &tombstones { + if let Some(tid) = TripleId::from_hex(&tw.triple_id) { + let mut s = sync.write().await; + if !s.has_tombstone(&tid.0) { + s.remove_local_id(&tid.0); + s.add_tombstone(tid.0, tw.deleted_at_ms); + drop(s); + gossip.write().await.remove_known(&tid.0); + let g = graph.read().await; + let _ = g.delete(&tid); + } + } + } + } + P2pMessage::Ping { timestamp_ms } => { + let count = sync.read().await.local_ids().len() as u64; + let pong = P2pMessage::Pong { + timestamp_ms, + triple_count: count, + }; + let t = transport.read().await; + let _ = t.send(&addr, &pong).await; + } + // A4: Forward pong to health task via channel. + P2pMessage::Pong { timestamp_ms, .. } => { + let _ = health_tx + .send(HealthEvent::PongReceived { + addr, + timestamp_ms, + }) + .await; + } + // DAG sync message handlers + #[cfg(feature = "dag")] + P2pMessage::DagTipSync { tips, action_count } => { + let g = graph.read().await; + let actions = + crate::p2p::dag_sync::compute_missing_from_tips(&g, &tips); + if !actions.is_empty() { + tracing::debug!( + "DAG tip sync from {} — {} tips, sending {} actions", + addr, + tips.len(), + actions.len() + ); + let send_msg = P2pMessage::SendDagActions { actions }; + let t = transport.read().await; + let _ = t.send(&addr, &send_msg).await; + } + let _ = action_count; // reserved for future adaptive sync + } + #[cfg(feature = "dag")] + P2pMessage::RequestDagActions { hashes } => { + let g = graph.read().await; + let actions = + crate::p2p::dag_sync::fetch_actions_by_hash(&g, &hashes); + if !actions.is_empty() { + let send_msg = P2pMessage::SendDagActions { actions }; + let t = transport.read().await; + let _ = t.send(&addr, &send_msg).await; + } + } + #[cfg(feature = "dag")] + P2pMessage::SendDagActions { actions } => { + let g = graph.read().await; + let (ingested, errors) = + crate::p2p::dag_sync::ingest_actions(&g, &actions); + if ingested > 0 { + tracing::info!( + "P2P DAG synced {} actions from {} ({} errors)", + ingested, + addr, + errors + ); + } + } + other => { + // Raft RPC is routed over HTTP, not P2P QUIC. + // Log any unexpected messages instead of silently dropping. + tracing::debug!( + from = %addr, + msg_type = ?std::mem::discriminant(&other), + "Ignoring unexpected P2P message variant" + ); + } + } + } + })); + } + + // ── Task 4: mDNS discovery reconnect ───────────────── + if config.mdns { + let transport = transport.clone(); + let discovery = discovery.clone(); + let running = running.clone(); + let sync = sync.clone(); + let peer_store = peer_store.clone(); + + tasks.push(tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(30)).await; + if !running.load(Ordering::Relaxed) { + break; + } + + let discovered = discovery.read().await.get_discovered_peers(); + for peer in discovered { + let connected = transport.read().await.is_connected(&peer.addr); + if !connected { + let triple_count = sync.read().await.local_ids().len() as u64; + let result = transport + .write() + .await + .connect(peer.addr, triple_count) + .await; + if let Ok(()) = result { + tracing::info!( + "P2P discovered and connected to {}", + peer.node_id + ); + sync.write().await.get_peer_state(&peer.addr); + // A3: Record mDNS peer + let mut ps = peer_store.write().await; + ps.add(StoredPeer { + addr: peer.addr, + node_id: Some(peer.node_id.clone()), + last_connected_ms: now_millis(), + source: PeerSource::Mdns, + }); + let _ = ps.save(); + } + } + } + } + })); + } + + // ── Task 5: Manual peer reconnection (A2) ──────────── + { + let transport = transport.clone(); + let sync = sync.clone(); + let running = running.clone(); + let manual_peers: Vec = config.manual_peers.clone(); + + tasks.push(tokio::spawn(async move { + let mut trackers: Vec = manual_peers + .into_iter() + .map(ManualPeerTracker::new) + .collect(); + + loop { + tokio::time::sleep(Duration::from_secs(5)).await; + if !running.load(Ordering::Relaxed) { + break; + } + + for tracker in &mut trackers { + if tracker.abandoned { + continue; + } + let connected = transport.read().await.is_connected(&tracker.addr); + if connected { + if tracker.retries > 0 { + tracker.record_success(); + } + continue; + } + if !tracker.should_retry() { + continue; + } + let triple_count = sync.read().await.local_ids().len() as u64; + let result = transport + .write() + .await + .connect(tracker.addr, triple_count) + .await; + match result { + Ok(()) => { + tracing::info!("P2P reconnected to manual peer {}", tracker.addr); + sync.write().await.get_peer_state(&tracker.addr); + tracker.record_success(); + } + Err(e) => { + tracing::debug!( + "P2P reconnect to {} failed (attempt {}): {}", + tracker.addr, + tracker.retries + 1, + e + ); + tracker.record_failure(); + } + } + } + } + })); + } + + // ── Task 6: Health checks — Ping/Pong (A4) ─────────── + { + let transport = transport.clone(); + let running = running.clone(); + let mut health_rx = health_rx; + + tasks.push(tokio::spawn(async move { + let mut tracker = PingTracker::new(Duration::from_secs(10)); + + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(30)) => { + if !running.load(Ordering::Relaxed) { + break; + } + + // Check timeouts — disconnect unresponsive peers + let timed_out = tracker.timed_out_peers(); + for addr in &timed_out { + tracing::warn!("P2P peer {} timed out (no pong), disconnecting", addr); + transport.write().await.disconnect(addr); + tracker.clear(addr); + } + + // Send pings to all connected peers + let peers = transport.read().await.connected_peers(); + let ts = now_millis(); + let ping = P2pMessage::Ping { timestamp_ms: ts }; + let t = transport.read().await; + for peer_addr in &peers { + if t.send(peer_addr, &ping).await.is_ok() { + tracker.record_ping(*peer_addr, ts); + } + } + } + event = health_rx.recv() => { + match event { + Some(HealthEvent::PongReceived { addr, timestamp_ms }) => { + tracker.record_pong(&addr, timestamp_ms); + } + None => break, + } + } + } + } + })); + } + + // ── Task 7: Periodic cleanup (A7) ──────────────────── + { + let sync = sync.clone(); + let peer_store = peer_store.clone(); + let running = running.clone(); + + tasks.push(tokio::spawn(async move { + loop { + tokio::time::sleep(Duration::from_secs(300)).await; + if !running.load(Ordering::Relaxed) { + break; + } + + // 1. Remove inactive sync states (15 min) + sync.write() + .await + .cleanup_inactive(Duration::from_secs(900)); + + // 2. Remove expired tombstones (A1) + sync.write().await.cleanup_expired_tombstones(); + + // 3. Cleanup stale peers (24h) and save (A3) + let mut ps = peer_store.write().await; + ps.cleanup_stale(24 * 3600 * 1000); + let _ = ps.save(); + + tracing::debug!("P2P cleanup cycle completed"); + } + })); + } + + tracing::info!( + "P2P manager started: node={}, port={}", + &node_id[..16.min(node_id.len())], + config.port + ); + + Ok(Arc::new(Self { + config, + node_id, + gossip, + sync, + transport, + discovery, + running, + tasks, + })) + } + + /// Stop the P2P subsystem. + pub async fn stop(&self) { + self.running.store(false, Ordering::SeqCst); + for task in &self.tasks { + task.abort(); + } + self.transport.write().await.stop(); + self.discovery.write().await.stop(); + tracing::info!("P2P manager stopped"); + } + + /// Current P2P status. + pub async fn status(&self) -> P2pStatus { + let transport = self.transport.read().await; + let peers: Vec = transport + .connected_peers() + .iter() + .map(|addr| PeerStatusDto { + addr: addr.to_string(), + connected: true, + }) + .collect(); + + P2pStatus { + node_id: self.node_id.clone(), + enabled: self.config.enabled, + port: self.config.port, + peer_count: peers.len(), + connected_peers: peers, + gossip_stats: self.gossip.read().await.stats(), + sync_stats: self.sync.read().await.stats(), + } + } + + /// Connect to a new peer at runtime. + pub async fn add_peer(&self, addr: SocketAddr) -> Result<(), String> { + let triple_count = self.sync.read().await.local_ids().len() as u64; + self.transport + .write() + .await + .connect(addr, triple_count) + .await + } + + /// Disconnect a peer. + pub async fn remove_peer(&self, addr: SocketAddr) { + self.transport.write().await.disconnect(&addr); + } + + /// Node ID (hex public key). + pub fn node_id(&self) -> &str { + &self.node_id + } +} + +/// Current time in milliseconds since UNIX epoch. +fn now_millis() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn peer_status_dto_fields() { + let dto = PeerStatusDto { + addr: "127.0.0.1:19091".into(), + connected: true, + }; + assert!(dto.connected); + } + + #[test] + fn p2p_status_serialize() { + let status = P2pStatus { + node_id: "abc".into(), + enabled: true, + port: 19091, + peer_count: 0, + connected_peers: vec![], + gossip_stats: GossipStats { + round: 0, + pending_announcements: 0, + known_ids: 0, + bloom_filter_items: 0, + bloom_filter_fpr: 0.0, + }, + sync_stats: SyncStats { + peer_count: 0, + local_ids: 0, + total_successful_syncs: 0, + total_failed_syncs: 0, + }, + }; + let json = serde_json::to_string(&status).unwrap(); + assert!(json.contains("node_id")); + } + + #[tokio::test] + async fn manager_starts_and_stops() { + let mut config = P2pConfig::default(); + config.enabled = true; + config.port = 0; // OS-assigned + config.data_dir = tempfile::TempDir::new().unwrap().into_path(); + + let state = AppState::new().unwrap(); + let manager = P2pManager::start(config, state).await.unwrap(); + assert!(!manager.node_id().is_empty()); + + let status = manager.status().await; + assert!(status.enabled); + assert_eq!(status.peer_count, 0); + + manager.stop().await; + } + + // ── A2: Manual peer tracker tests ──────────────────────── + + #[test] + fn manual_peer_tracker_new() { + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + let tracker = ManualPeerTracker::new(addr); + assert_eq!(tracker.retries, 0); + assert!(!tracker.abandoned); + assert_eq!(tracker.current_backoff, Duration::from_secs(5)); + } + + #[test] + fn tracker_should_retry_after_backoff() { + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + let mut tracker = ManualPeerTracker::new(addr); + tracker.last_attempt = Instant::now() - Duration::from_secs(6); + assert!(tracker.should_retry()); + } + + #[test] + fn tracker_record_failure_doubles_backoff() { + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + let mut tracker = ManualPeerTracker::new(addr); + let initial = tracker.current_backoff; + tracker.record_failure(); + assert_eq!(tracker.current_backoff, initial * 2); + assert_eq!(tracker.retries, 1); + } + + #[test] + fn tracker_record_failure_caps_at_max() { + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + let mut tracker = ManualPeerTracker::new(addr); + for _ in 0..20 { + tracker.record_failure(); + } + assert!(tracker.current_backoff <= Duration::from_secs(300)); + } + + #[test] + fn tracker_abandoned_after_max_retries() { + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + let mut tracker = ManualPeerTracker::new(addr); + for _ in 0..10 { + tracker.record_failure(); + } + assert!(tracker.abandoned); + assert!(!tracker.should_retry()); + } + + #[test] + fn tracker_record_success_resets() { + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + let mut tracker = ManualPeerTracker::new(addr); + tracker.record_failure(); + tracker.record_failure(); + tracker.record_success(); + assert_eq!(tracker.retries, 0); + assert_eq!(tracker.current_backoff, Duration::from_secs(5)); + assert!(!tracker.abandoned); + } + + // ── A4: Ping tracker tests ─────────────────────────────── + + #[test] + fn ping_tracker_new_empty() { + let tracker = PingTracker::new(Duration::from_secs(10)); + assert!(tracker.outstanding.is_empty()); + assert!(tracker.timed_out_peers().is_empty()); + } + + #[test] + fn ping_tracker_record_and_pong() { + let mut tracker = PingTracker::new(Duration::from_secs(10)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + tracker.record_ping(addr, 1000); + assert_eq!(tracker.outstanding.len(), 1); + tracker.record_pong(&addr, 1000); + assert!(tracker.outstanding.is_empty()); + } + + #[test] + fn ping_tracker_timed_out_detection() { + let mut tracker = PingTracker::new(Duration::from_millis(10)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + tracker.outstanding.insert(addr, (1000, Instant::now() - Duration::from_millis(50))); + let timed_out = tracker.timed_out_peers(); + assert_eq!(timed_out.len(), 1); + assert_eq!(timed_out[0], addr); + } + + #[test] + fn ping_tracker_clear_removes_entry() { + let mut tracker = PingTracker::new(Duration::from_secs(10)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + tracker.record_ping(addr, 1000); + tracker.clear(&addr); + assert!(tracker.outstanding.is_empty()); + } + + #[test] + fn ping_tracker_no_false_timeout() { + let mut tracker = PingTracker::new(Duration::from_secs(60)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + tracker.record_ping(addr, 1000); + // Just recorded, shouldn't be timed out + assert!(tracker.timed_out_peers().is_empty()); + } + + // ── A7: Cleanup tests ──────────────────────────────────── + + #[test] + fn cleanup_removes_inactive_sync_states() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + sm.get_peer_state(&addr); + std::thread::sleep(Duration::from_millis(10)); + sm.cleanup_inactive(Duration::from_millis(1)); + assert_eq!(sm.stats().peer_count, 0); + } + + #[test] + fn cleanup_removes_expired_tombstones() { + let mut sm = TripleSyncManager::with_tombstone_ttl( + Duration::from_secs(60), + Duration::from_millis(10), + ); + sm.add_tombstone([1u8; 32], 0); // very old + sm.cleanup_expired_tombstones(); + assert!(!sm.has_tombstone(&[1u8; 32])); + } + + #[test] + fn cleanup_interval_configurable() { + // Verify that the cleanup interval (5min in Task 7) can be configured + // by checking the tombstone TTL is customizable + let sm = TripleSyncManager::with_tombstone_ttl( + Duration::from_secs(60), + Duration::from_secs(7200), + ); + assert_eq!(sm.tombstone_ttl, Duration::from_secs(7200)); + } +} diff --git a/crates/aingle_cortex/src/p2p/message.rs b/crates/aingle_cortex/src/p2p/message.rs new file mode 100644 index 00000000..043a20a6 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/message.rs @@ -0,0 +1,477 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! P2P wire protocol messages adapted for triple synchronization. + +use aingle_graph::{NodeId, Predicate, Triple, TripleMeta, Value}; +use serde::{Deserialize, Serialize}; + +/// Maximum message size (4 MB). +pub const MAX_MESSAGE_SIZE: usize = 4 * 1024 * 1024; + +/// Maximum IDs in a single `RequestTriples` message. +pub const MAX_REQUEST_IDS: usize = 100; + +/// Maximum triples in a single `SendTriples` batch. +pub const MAX_BATCH_TRIPLES: usize = 5000; + +/// Protocol messages exchanged between Cortex P2P nodes. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum P2pMessage { + /// Handshake: announce identity and network membership. + Hello { + node_id: String, + /// blake3 hash of the seed (never the seed itself). + seed_hash: String, + version: String, + triple_count: u64, + }, + /// Handshake acknowledgement. + HelloAck { + node_id: String, + accepted: bool, + reason: Option, + }, + /// Bloom filter for set reconciliation. + BloomSync { + filter_bytes: Vec, + triple_count: u64, + }, + /// Request triples by their hex IDs. + RequestTriples { + ids: Vec, + }, + /// Batch of triples. + SendTriples { + triples: Vec, + }, + /// Lightweight announcement of a new triple. + Announce { + triple_id: String, + }, + /// Keep-alive ping. + Ping { + timestamp_ms: u64, + }, + /// Keep-alive pong. + Pong { + timestamp_ms: u64, + triple_count: u64, + }, + /// Announce a triple deletion (tombstone propagation). + AnnounceDelete { + triple_id: String, + tombstone_ts: u64, + }, + /// Batch tombstone synchronization. + TombstoneSync { + tombstones: Vec, + }, + // ── DAG sync messages (feature: dag) ──────────────────────── + /// Exchange of DAG tip hashes for sync. + #[cfg(feature = "dag")] + DagTipSync { + tips: Vec, + action_count: u64, + }, + /// Request specific DAG actions by hash. + #[cfg(feature = "dag")] + RequestDagActions { + hashes: Vec, + }, + /// Batch of serialized DAG actions. + #[cfg(feature = "dag")] + SendDagActions { + actions: Vec>, + }, + // ── Raft / Cluster messages (feature: cluster) ────────────── + /// Raft AppendEntries RPC (serialized openraft request). + #[cfg(feature = "cluster")] + RaftAppendEntries { payload: Vec }, + /// Raft AppendEntries response. + #[cfg(feature = "cluster")] + RaftAppendEntriesResponse { payload: Vec }, + /// Raft Vote RPC. + #[cfg(feature = "cluster")] + RaftVote { payload: Vec }, + /// Raft Vote response. + #[cfg(feature = "cluster")] + RaftVoteResponse { payload: Vec }, + /// Raft InstallSnapshot RPC. + #[cfg(feature = "cluster")] + RaftInstallSnapshot { payload: Vec }, + /// Raft InstallSnapshot response. + #[cfg(feature = "cluster")] + RaftInstallSnapshotResponse { payload: Vec }, + /// Cluster membership join request. + #[cfg(feature = "cluster")] + ClusterJoin { + node_id: u64, + rest_addr: String, + p2p_addr: String, + }, + /// Cluster membership acknowledgement. + #[cfg(feature = "cluster")] + ClusterJoinAck { + accepted: bool, + leader_id: Option, + leader_addr: Option, + }, +} + +/// Wire format for a tombstone marker. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TombstoneWire { + pub triple_id: String, + pub deleted_at_ms: u64, +} + +/// Serializable wire format for a triple (no internal indices). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TripleWire { + pub subject: String, + pub predicate: String, + pub object: serde_json::Value, + pub created_at: Option, + pub author: Option, + pub source: Option, +} + +impl TripleWire { + /// Convert an `aingle_graph::Triple` into a wire representation. + pub fn from_triple(triple: &Triple) -> Self { + let subject = match &triple.subject { + NodeId::Named(s) => s.clone(), + NodeId::Hash(h) => format!("hash:{}", hex::encode(h)), + NodeId::Blank(id) => format!("_:b{}", id), + }; + + let predicate = triple.predicate.as_str().to_string(); + + let object = value_to_json(&triple.object); + + Self { + subject, + predicate, + object, + created_at: Some(triple.meta.created_at.to_rfc3339()), + author: triple.meta.author.as_ref().map(|n| match n { + NodeId::Named(s) => s.clone(), + NodeId::Hash(h) => format!("hash:{}", hex::encode(h)), + NodeId::Blank(id) => format!("_:b{}", id), + }), + source: triple.meta.source.clone(), + } + } + + /// Convert back into an `aingle_graph::Triple`. Returns `None` on parse failure. + pub fn to_triple(&self) -> Option { + let subject = NodeId::named(&self.subject); + let predicate = Predicate::named(&self.predicate); + let object = json_to_value(&self.object); + + let mut meta = TripleMeta::new(); + if let Some(ref ts) = self.created_at { + if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(ts) { + meta.created_at = dt.with_timezone(&chrono::Utc); + } + } + if let Some(ref author) = self.author { + meta = meta.with_author(NodeId::named(author)); + } + if let Some(ref source) = self.source { + meta = meta.with_source(source.as_str()); + } + + Some(Triple::with_meta(subject, predicate, object, meta)) + } +} + +/// Serialize a `P2pMessage` with a 4-byte big-endian length prefix + JSON payload. +impl P2pMessage { + pub fn to_bytes(&self) -> Vec { + let json = serde_json::to_vec(self).expect("P2pMessage is always serializable"); + let len = json.len() as u32; + let mut buf = Vec::with_capacity(4 + json.len()); + buf.extend_from_slice(&len.to_be_bytes()); + buf.extend_from_slice(&json); + buf + } + + /// Deserialize from `[4-byte len][JSON]`. + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 4 { + return Err("message too short".to_string()); + } + let len = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; + if len > MAX_MESSAGE_SIZE { + return Err(format!("message too large: {} bytes", len)); + } + if bytes.len() < 4 + len { + return Err("incomplete message".to_string()); + } + serde_json::from_slice(&bytes[4..4 + len]).map_err(|e| format!("json parse error: {}", e)) + } +} + +// ── helpers ────────────────────────────────────────────────────── + +fn value_to_json(v: &Value) -> serde_json::Value { + match v { + Value::String(s) => serde_json::Value::String(s.clone()), + Value::Integer(n) => serde_json::json!(n), + Value::Float(f) => serde_json::json!(f), + Value::Boolean(b) => serde_json::json!(b), + Value::DateTime(s) => serde_json::json!({ "type": "datetime", "value": s }), + Value::Node(nid) => match nid { + NodeId::Named(s) => serde_json::json!({ "type": "node", "value": s }), + NodeId::Hash(h) => { + serde_json::json!({ "type": "node", "value": format!("hash:{}", hex::encode(h)) }) + } + NodeId::Blank(id) => { + serde_json::json!({ "type": "node", "value": format!("_:b{}", id) }) + } + }, + Value::Json(j) => j.clone(), + Value::Null => serde_json::Value::Null, + Value::Typed { value, datatype } => { + serde_json::json!({ "type": "typed", "value": value, "datatype": datatype }) + } + Value::LangString { value, lang } => { + serde_json::json!({ "type": "lang", "value": value, "lang": lang }) + } + Value::Bytes(b) => serde_json::json!({ "type": "bytes", "value": hex::encode(b) }), + } +} + +fn json_to_value(j: &serde_json::Value) -> Value { + match j { + serde_json::Value::String(s) => Value::String(s.clone()), + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + Value::Integer(i) + } else if let Some(f) = n.as_f64() { + Value::Float(f) + } else { + Value::String(n.to_string()) + } + } + serde_json::Value::Bool(b) => Value::Boolean(*b), + serde_json::Value::Null => Value::Null, + serde_json::Value::Object(map) => { + if let Some(t) = map.get("type").and_then(|v| v.as_str()) { + let val = map + .get("value") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + match t { + "node" => Value::Node(NodeId::named(val)), + "datetime" => Value::DateTime(val.to_string()), + "typed" => { + let dt = map + .get("datatype") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + Value::Typed { + value: val.to_string(), + datatype: dt.to_string(), + } + } + "lang" => { + let lang = map + .get("lang") + .and_then(|v| v.as_str()) + .unwrap_or_default(); + Value::LangString { + value: val.to_string(), + lang: lang.to_string(), + } + } + "bytes" => { + let decoded = hex::decode(val).unwrap_or_default(); + Value::Bytes(decoded) + } + _ => Value::Json(j.clone()), + } + } else { + Value::Json(j.clone()) + } + } + serde_json::Value::Array(_) => Value::Json(j.clone()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hello_roundtrip() { + let msg = P2pMessage::Hello { + node_id: "abc123".into(), + seed_hash: "def456".into(), + version: "0.3.8".into(), + triple_count: 42, + }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + assert!(matches!(parsed, P2pMessage::Hello { triple_count: 42, .. })); + } + + #[test] + fn bloom_sync_roundtrip() { + let filter = vec![0xffu8; 128]; + let msg = P2pMessage::BloomSync { + filter_bytes: filter.clone(), + triple_count: 100, + }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + match parsed { + P2pMessage::BloomSync { filter_bytes, triple_count } => { + assert_eq!(filter_bytes.len(), 128); + assert_eq!(triple_count, 100); + } + _ => panic!("wrong variant"), + } + } + + #[test] + fn request_triples_roundtrip() { + let msg = P2pMessage::RequestTriples { + ids: vec!["aabb".into(), "ccdd".into()], + }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + match parsed { + P2pMessage::RequestTriples { ids } => assert_eq!(ids.len(), 2), + _ => panic!("wrong variant"), + } + } + + #[test] + fn send_triples_roundtrip() { + let tw = TripleWire { + subject: "test:a".into(), + predicate: "test:b".into(), + object: serde_json::json!("hello"), + created_at: None, + author: None, + source: None, + }; + let msg = P2pMessage::SendTriples { + triples: vec![tw], + }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + match parsed { + P2pMessage::SendTriples { triples } => assert_eq!(triples.len(), 1), + _ => panic!("wrong variant"), + } + } + + #[test] + fn announce_roundtrip() { + let msg = P2pMessage::Announce { + triple_id: "deadbeef".into(), + }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + assert!(matches!(parsed, P2pMessage::Announce { .. })); + } + + #[test] + fn ping_pong_roundtrip() { + let ping = P2pMessage::Ping { timestamp_ms: 1000 }; + let bytes = ping.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + assert!(matches!(parsed, P2pMessage::Ping { timestamp_ms: 1000 })); + + let pong = P2pMessage::Pong { + timestamp_ms: 1000, + triple_count: 50, + }; + let bytes = pong.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + assert!(matches!( + parsed, + P2pMessage::Pong { + timestamp_ms: 1000, + triple_count: 50 + } + )); + } + + #[test] + fn rejects_oversized_message() { + // Craft a length prefix > MAX_MESSAGE_SIZE. + let len = (MAX_MESSAGE_SIZE as u32 + 1).to_be_bytes(); + let mut bytes = vec![]; + bytes.extend_from_slice(&len); + bytes.extend_from_slice(&[0u8; 10]); + assert!(P2pMessage::from_bytes(&bytes).is_err()); + } + + #[test] + fn tombstone_wire_roundtrip() { + let tw = TombstoneWire { + triple_id: "abc123".into(), + deleted_at_ms: 1700000000000, + }; + let json = serde_json::to_vec(&tw).unwrap(); + let back: TombstoneWire = serde_json::from_slice(&json).unwrap(); + assert_eq!(back.triple_id, "abc123"); + assert_eq!(back.deleted_at_ms, 1700000000000); + } + + #[test] + fn announce_delete_roundtrip() { + let msg = P2pMessage::AnnounceDelete { + triple_id: "deadbeef".into(), + tombstone_ts: 1700000000000, + }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + match parsed { + P2pMessage::AnnounceDelete { triple_id, tombstone_ts } => { + assert_eq!(triple_id, "deadbeef"); + assert_eq!(tombstone_ts, 1700000000000); + } + _ => panic!("wrong variant"), + } + } + + #[test] + fn tombstone_sync_roundtrip() { + let tombstones = vec![ + TombstoneWire { triple_id: "aa".into(), deleted_at_ms: 100 }, + TombstoneWire { triple_id: "bb".into(), deleted_at_ms: 200 }, + ]; + let msg = P2pMessage::TombstoneSync { tombstones }; + let bytes = msg.to_bytes(); + let parsed = P2pMessage::from_bytes(&bytes).unwrap(); + match parsed { + P2pMessage::TombstoneSync { tombstones } => { + assert_eq!(tombstones.len(), 2); + assert_eq!(tombstones[0].triple_id, "aa"); + assert_eq!(tombstones[1].deleted_at_ms, 200); + } + _ => panic!("wrong variant"), + } + } + + #[test] + fn triple_wire_conversion() { + let triple = Triple::new( + NodeId::named("test:subject"), + Predicate::named("test:predicate"), + Value::String("world".into()), + ); + let wire = TripleWire::from_triple(&triple); + let back = wire.to_triple().unwrap(); + + assert_eq!(back.subject, triple.subject); + assert_eq!(back.predicate, triple.predicate); + assert_eq!(back.object, triple.object); + } +} diff --git a/crates/aingle_cortex/src/p2p/mod.rs b/crates/aingle_cortex/src/p2p/mod.rs new file mode 100644 index 00000000..58c103c7 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! P2P networking for Cortex triple synchronization. +//! +//! Enables multi-node knowledge graph sync via QUIC transport, +//! bloom filter gossip, and optional mDNS discovery. + +pub mod config; +#[cfg(feature = "dag")] +pub mod dag_sync; +pub mod discovery; +pub mod gossip; +pub mod identity; +pub mod manager; +pub mod message; +pub mod peer_store; +pub mod rate_limiter; +pub mod sync_manager; +pub mod transport; diff --git a/crates/aingle_cortex/src/p2p/peer_store.rs b/crates/aingle_cortex/src/p2p/peer_store.rs new file mode 100644 index 00000000..0d6ef963 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/peer_store.rs @@ -0,0 +1,218 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Persistent peer storage backed by a JSON file. +//! +//! Stores known peers in `{data_dir}/known_peers.json` so they survive restarts. +//! Peers are infrastructure metadata, not knowledge graph data. + +use serde::{Deserialize, Serialize}; +use std::net::SocketAddr; +use std::path::PathBuf; + +/// How a peer was originally discovered. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum PeerSource { + Manual, + Mdns, + RestApi, +} + +/// A peer entry persisted to disk. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StoredPeer { + pub addr: SocketAddr, + pub node_id: Option, + pub last_connected_ms: u64, + pub source: PeerSource, +} + +/// JSON-backed persistent peer list. +pub struct PeerStore { + path: PathBuf, + peers: Vec, + max_peers: usize, +} + +impl PeerStore { + /// Load from disk or create empty. + pub fn load(data_dir: &std::path::Path, max_peers: usize) -> Self { + let path = data_dir.join("known_peers.json"); + let peers = if path.exists() { + match std::fs::read_to_string(&path) { + Ok(content) => serde_json::from_str(&content).unwrap_or_default(), + Err(_) => Vec::new(), + } + } else { + Vec::new() + }; + Self { path, peers, max_peers } + } + + /// Write the current peer list to disk. + pub fn save(&self) -> Result<(), String> { + if let Some(parent) = self.path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| format!("create peer store dir: {}", e))?; + } + let json = serde_json::to_string_pretty(&self.peers) + .map_err(|e| format!("serialize peers: {}", e))?; + let mut file = std::fs::File::create(&self.path) + .map_err(|e| format!("create peer store: {}", e))?; + std::io::Write::write_all(&mut file, json.as_bytes()) + .map_err(|e| format!("write peer store: {}", e))?; + file.sync_all() + .map_err(|e| format!("fsync peer store: {}", e))?; + Ok(()) + } + + /// Add a peer. Deduplicates by address. Enforces max_peers limit. + pub fn add(&mut self, peer: StoredPeer) { + // Deduplicate + if self.peers.iter().any(|p| p.addr == peer.addr) { + return; + } + // Enforce capacity + if self.peers.len() >= self.max_peers { + // Remove oldest (by last_connected_ms) + if let Some(oldest_idx) = self.peers.iter().enumerate() + .min_by_key(|(_, p)| p.last_connected_ms) + .map(|(i, _)| i) + { + self.peers.remove(oldest_idx); + } + } + self.peers.push(peer); + } + + /// Remove a peer by address. + pub fn remove(&mut self, addr: &SocketAddr) { + self.peers.retain(|p| p.addr != *addr); + } + + /// Get all stored peers. + pub fn all(&self) -> &[StoredPeer] { + &self.peers + } + + /// Update last_connected timestamp for a peer. + pub fn update_last_connected(&mut self, addr: &SocketAddr, ts_ms: u64) { + if let Some(peer) = self.peers.iter_mut().find(|p| p.addr == *addr) { + peer.last_connected_ms = ts_ms; + } + } + + /// Remove peers not connected for more than `max_age_ms` milliseconds. + pub fn cleanup_stale(&mut self, max_age_ms: u64) { + let now_ms = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + self.peers.retain(|p| { + p.last_connected_ms == 0 || now_ms.saturating_sub(p.last_connected_ms) < max_age_ms + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn temp_store(max_peers: usize) -> PeerStore { + let dir = tempfile::TempDir::new().unwrap(); + PeerStore::load(dir.path(), max_peers) + } + + fn addr(port: u16) -> SocketAddr { + format!("127.0.0.1:{}", port).parse().unwrap() + } + + fn stored_peer(port: u16, ts: u64) -> StoredPeer { + StoredPeer { + addr: addr(port), + node_id: None, + last_connected_ms: ts, + source: PeerSource::Manual, + } + } + + #[test] + fn peer_store_empty_on_first_load() { + let store = temp_store(100); + assert!(store.all().is_empty()); + } + + #[test] + fn peer_store_add_and_save() { + let dir = tempfile::TempDir::new().unwrap(); + let mut store = PeerStore::load(dir.path(), 100); + store.add(stored_peer(9000, 1000)); + assert_eq!(store.all().len(), 1); + assert!(store.save().is_ok()); + assert!(dir.path().join("known_peers.json").exists()); + } + + #[test] + fn peer_store_load_persisted_data() { + let dir = tempfile::TempDir::new().unwrap(); + { + let mut store = PeerStore::load(dir.path(), 100); + store.add(stored_peer(9000, 1000)); + store.add(stored_peer(9001, 2000)); + store.save().unwrap(); + } + let store2 = PeerStore::load(dir.path(), 100); + assert_eq!(store2.all().len(), 2); + } + + #[test] + fn peer_store_remove_existing() { + let mut store = temp_store(100); + store.add(stored_peer(9000, 1000)); + store.add(stored_peer(9001, 2000)); + store.remove(&addr(9000)); + assert_eq!(store.all().len(), 1); + assert_eq!(store.all()[0].addr, addr(9001)); + } + + #[test] + fn peer_store_deduplicates_same_addr() { + let mut store = temp_store(100); + store.add(stored_peer(9000, 1000)); + store.add(stored_peer(9000, 2000)); + assert_eq!(store.all().len(), 1); + } + + #[test] + fn peer_store_cleanup_stale() { + let mut store = temp_store(100); + // Add a peer with timestamp 0 (never connected) — should be kept + store.add(stored_peer(9000, 0)); + // Add a peer with an old timestamp + store.add(stored_peer(9001, 1)); + store.cleanup_stale(1000); // 1 second max age + // peer with ts=0 is kept (never-connected sentinel), old one removed + assert_eq!(store.all().len(), 1); + assert_eq!(store.all()[0].addr, addr(9000)); + } + + #[test] + fn peer_store_max_peers_enforced() { + let mut store = temp_store(2); + store.add(stored_peer(9000, 100)); + store.add(stored_peer(9001, 200)); + assert_eq!(store.all().len(), 2); + // Adding a third should evict the oldest (port 9000, ts=100) + store.add(stored_peer(9002, 300)); + assert_eq!(store.all().len(), 2); + assert!(!store.all().iter().any(|p| p.addr == addr(9000))); + } + + #[test] + fn peer_store_update_last_connected() { + let mut store = temp_store(100); + store.add(stored_peer(9000, 1000)); + store.update_last_connected(&addr(9000), 5000); + assert_eq!(store.all()[0].last_connected_ms, 5000); + } +} diff --git a/crates/aingle_cortex/src/p2p/rate_limiter.rs b/crates/aingle_cortex/src/p2p/rate_limiter.rs new file mode 100644 index 00000000..3aa5d48f --- /dev/null +++ b/crates/aingle_cortex/src/p2p/rate_limiter.rs @@ -0,0 +1,125 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Ingress rate limiter for P2P triple reception. +//! +//! Per-peer and global token-bucket rate limiting to prevent DoS +//! via excessive `SendTriples` messages. + +use crate::p2p::gossip::TokenBucket; +use std::collections::HashMap; +use std::net::SocketAddr; + +/// Rate limiter for incoming triples. +pub struct IngressRateLimiter { + per_peer: HashMap, + global: TokenBucket, + per_peer_rate: f64, + per_peer_max: f64, +} + +impl IngressRateLimiter { + /// Create a new limiter. + /// + /// `per_peer_per_min`: max triples per peer per minute. + /// `global_per_min`: max triples globally per minute. + pub fn new(per_peer_per_min: usize, global_per_min: usize) -> Self { + let per_peer_max = per_peer_per_min as f64; + let per_peer_rate = per_peer_max / 60.0; + let global_max = global_per_min as f64; + let global_rate = global_max / 60.0; + + Self { + per_peer: HashMap::new(), + global: TokenBucket::with_params(global_max, global_rate), + per_peer_rate, + per_peer_max, + } + } + + /// Check how many triples from `addr` are allowed out of `count`. + /// + /// Returns the number of allowed triples (0..=count). + pub fn check(&mut self, addr: &SocketAddr, count: usize) -> usize { + let bucket = self.per_peer.entry(*addr).or_insert_with(|| { + TokenBucket::with_params(self.per_peer_max, self.per_peer_rate) + }); + + let mut allowed = 0; + for _ in 0..count { + if bucket.try_consume(1.0) && self.global.try_consume(1.0) { + allowed += 1; + } else { + break; + } + } + allowed + } + + /// Remove rate-limiting state for a disconnected peer. + pub fn cleanup_peer(&mut self, addr: &SocketAddr) { + self.per_peer.remove(addr); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn addr(port: u16) -> SocketAddr { + format!("127.0.0.1:{}", port).parse().unwrap() + } + + #[test] + fn ingress_limiter_allows_within_limit() { + let mut limiter = IngressRateLimiter::new(100, 1000); + let allowed = limiter.check(&addr(9000), 50); + assert_eq!(allowed, 50); + } + + #[test] + fn ingress_limiter_blocks_over_limit() { + let mut limiter = IngressRateLimiter::new(10, 1000); + let allowed = limiter.check(&addr(9000), 20); + assert_eq!(allowed, 10); + } + + #[test] + fn ingress_limiter_per_peer_independence() { + let mut limiter = IngressRateLimiter::new(10, 1000); + let a1 = limiter.check(&addr(9000), 10); + let a2 = limiter.check(&addr(9001), 10); + assert_eq!(a1, 10); + assert_eq!(a2, 10); + } + + #[test] + fn ingress_limiter_global_limit_shared() { + let mut limiter = IngressRateLimiter::new(100, 15); + let a1 = limiter.check(&addr(9000), 10); + let a2 = limiter.check(&addr(9001), 10); + assert_eq!(a1, 10); + assert_eq!(a2, 5); // global cap hit + } + + #[test] + fn ingress_limiter_refills_over_time() { + // Use a high rate so refill is fast: 600/min = 10/sec + let mut limiter = IngressRateLimiter::new(600, 60000); + let a1 = limiter.check(&addr(9000), 10); + assert_eq!(a1, 10); + // After waiting 200ms at 10/sec, we get ~2 tokens + std::thread::sleep(std::time::Duration::from_millis(200)); + let a3 = limiter.check(&addr(9000), 1); + assert!(a3 > 0); + } + + #[test] + fn ingress_limiter_cleanup_peer() { + let mut limiter = IngressRateLimiter::new(10, 1000); + limiter.check(&addr(9000), 5); + assert!(limiter.per_peer.contains_key(&addr(9000))); + limiter.cleanup_peer(&addr(9000)); + assert!(!limiter.per_peer.contains_key(&addr(9000))); + } +} diff --git a/crates/aingle_cortex/src/p2p/sync_manager.rs b/crates/aingle_cortex/src/p2p/sync_manager.rs new file mode 100644 index 00000000..a55ba231 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/sync_manager.rs @@ -0,0 +1,458 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Triple synchronization manager. +//! +//! Tracks per-peer sync state and coordinates bloom-filter-based reconciliation +//! against the local `GraphDB`. + +use crate::p2p::gossip::BloomFilter; +use aingle_graph::{GraphDB, Triple, TripleId}; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::time::{Duration, Instant}; + +/// Maximum IDs to request in a single reconciliation round. +const MAX_REQUEST_SIZE: usize = 100; + +/// Per-peer sync tracking. +#[derive(Debug, Clone)] +pub struct PeerSyncState { + pub last_sync: Instant, + pub peer_filter: Option, + pub pending_requests: Vec<[u8; 32]>, + pub successful_syncs: u32, + pub failed_syncs: u32, +} + +impl PeerSyncState { + pub fn new() -> Self { + Self { + last_sync: Instant::now(), + peer_filter: None, + pending_requests: Vec::new(), + successful_syncs: 0, + failed_syncs: 0, + } + } + + pub fn should_sync(&self, interval: Duration) -> bool { + self.last_sync.elapsed() >= interval + } + + pub fn record_success(&mut self) { + self.last_sync = Instant::now(); + self.successful_syncs += 1; + self.failed_syncs = 0; + } + + pub fn record_failure(&mut self) { + self.last_sync = Instant::now(); + self.failed_syncs += 1; + } +} + +impl Default for PeerSyncState { + fn default() -> Self { + Self::new() + } +} + +/// Manages triple synchronization with peers. +pub struct TripleSyncManager { + peer_states: HashMap, + sync_interval: Duration, + /// All known local triple IDs. + local_ids: Vec<[u8; 32]>, + max_local_ids: usize, + /// Tombstones: hash -> deletion timestamp_ms. + pub(crate) tombstones: HashMap<[u8; 32], u64>, + /// Tombstone time-to-live (default 24h). + pub(crate) tombstone_ttl: Duration, +} + +impl TripleSyncManager { + pub fn new(sync_interval: Duration) -> Self { + Self { + peer_states: HashMap::new(), + sync_interval, + local_ids: Vec::with_capacity(1000), + max_local_ids: 100_000, + tombstones: HashMap::new(), + tombstone_ttl: Duration::from_secs(24 * 3600), + } + } + + /// Create with a custom tombstone TTL. + pub fn with_tombstone_ttl(sync_interval: Duration, ttl: Duration) -> Self { + let mut mgr = Self::new(sync_interval); + mgr.tombstone_ttl = ttl; + mgr + } + + /// Register a new local triple ID. + pub fn add_local_id(&mut self, id: [u8; 32]) { + if self.local_ids.len() >= self.max_local_ids { + self.local_ids.drain(..self.max_local_ids / 2); + } + self.local_ids.push(id); + } + + /// Rebuild the local ID list by scanning the full graph. + pub fn rebuild_local_ids(&mut self, graph: &GraphDB) { + self.local_ids.clear(); + if let Ok(triples) = graph.find(aingle_graph::TriplePattern::any()) { + for triple in &triples { + self.local_ids.push(TripleId::from_triple(triple).0); + } + } + } + + /// Get a snapshot of all local IDs. + pub fn local_ids(&self) -> &[[u8; 32]] { + &self.local_ids + } + + /// Return peers whose sync interval has elapsed. + pub fn peers_needing_sync(&self) -> Vec { + self.peer_states + .iter() + .filter(|(_, s)| s.should_sync(self.sync_interval)) + .map(|(addr, _)| *addr) + .collect() + } + + /// Build a bloom filter from all local IDs. + pub fn build_local_filter(&self) -> BloomFilter { + let mut filter = BloomFilter::new(); + for id in &self.local_ids { + filter.insert(id); + } + filter + } + + /// Given a peer's bloom filter, return IDs we have that the peer is missing (capped). + pub fn process_peer_filter(&self, peer_filter: &BloomFilter) -> Vec<[u8; 32]> { + let mut missing = Vec::new(); + for id in &self.local_ids { + if !peer_filter.may_contain(id) { + missing.push(*id); + if missing.len() >= MAX_REQUEST_SIZE { + break; + } + } + } + missing + } + + /// Insert triples received from a peer into the graph. Duplicates are counted, not errors. + pub fn store_received_triples( + &mut self, + triples: Vec, + graph: &GraphDB, + ) -> StoreResult { + let mut result = StoreResult::default(); + for triple in triples { + let id = TripleId::from_triple(&triple); + match graph.insert(triple) { + Ok(_) => { + self.add_local_id(id.0); + result.inserted += 1; + } + Err(e) => { + let msg = format!("{}", e); + if msg.contains("duplicate") || msg.contains("exists") || msg.contains("already") { + result.duplicates += 1; + } else { + result.errors += 1; + } + } + } + } + result + } + + /// Record the outcome of a sync round for a given peer. + pub fn record_sync_result(&mut self, peer: SocketAddr, success: bool, _triples_synced: usize) { + let state = self.peer_states.entry(peer).or_default(); + if success { + state.record_success(); + } else { + state.record_failure(); + } + } + + /// Get or create state entry for a peer. + pub fn get_peer_state(&mut self, addr: &SocketAddr) -> &mut PeerSyncState { + self.peer_states.entry(*addr).or_default() + } + + /// Remove peers that haven't synced within `timeout`. + pub fn cleanup_inactive(&mut self, timeout: Duration) { + self.peer_states + .retain(|_, s| s.last_sync.elapsed() < timeout); + } + + /// Remove a local ID (used when a triple is deleted). + pub fn remove_local_id(&mut self, id: &[u8; 32]) { + self.local_ids.retain(|existing| existing != id); + } + + /// Add a tombstone marker for a deleted triple. + pub fn add_tombstone(&mut self, id: [u8; 32], ts_ms: u64) { + self.tombstones.insert(id, ts_ms); + } + + /// Check if a tombstone exists for the given ID. + pub fn has_tombstone(&self, id: &[u8; 32]) -> bool { + self.tombstones.contains_key(id) + } + + /// Remove expired tombstones (older than TTL). + pub fn cleanup_expired_tombstones(&mut self) { + let now_ms = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + let ttl_ms = self.tombstone_ttl.as_millis() as u64; + self.tombstones.retain(|_, ts| now_ms.saturating_sub(*ts) < ttl_ms); + } + + /// Return all active tombstones as (hash, timestamp_ms) pairs. + pub fn active_tombstones(&self) -> Vec<([u8; 32], u64)> { + self.tombstones.iter().map(|(k, v)| (*k, *v)).collect() + } + + /// Aggregate sync statistics. + pub fn stats(&self) -> SyncStats { + let mut total_successful = 0; + let mut total_failed = 0; + for s in self.peer_states.values() { + total_successful += s.successful_syncs; + total_failed += s.failed_syncs; + } + SyncStats { + peer_count: self.peer_states.len(), + local_ids: self.local_ids.len(), + total_successful_syncs: total_successful, + total_failed_syncs: total_failed, + } + } +} + +/// Result of a `store_received_triples` operation. +#[derive(Debug, Default)] +pub struct StoreResult { + pub inserted: usize, + pub duplicates: usize, + pub errors: usize, +} + +/// Aggregate sync statistics. +#[derive(Debug, Clone, serde::Serialize)] +pub struct SyncStats { + pub peer_count: usize, + pub local_ids: usize, + pub total_successful_syncs: u32, + pub total_failed_syncs: u32, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_empty() { + let sm = TripleSyncManager::new(Duration::from_secs(60)); + assert!(sm.local_ids().is_empty()); + assert!(sm.peers_needing_sync().is_empty()); + } + + #[test] + fn add_local_id() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + sm.add_local_id([1u8; 32]); + assert_eq!(sm.local_ids().len(), 1); + } + + #[test] + fn build_local_filter_contains_ids() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let id = [5u8; 32]; + sm.add_local_id(id); + let filter = sm.build_local_filter(); + assert!(filter.may_contain(&id)); + assert!(!filter.may_contain(&[99u8; 32])); + } + + #[test] + fn process_peer_filter_finds_missing() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let a = [1u8; 32]; + let b = [2u8; 32]; + let c = [3u8; 32]; + sm.add_local_id(a); + sm.add_local_id(b); + sm.add_local_id(c); + + let mut peer = BloomFilter::new(); + peer.insert(&a); + + let missing = sm.process_peer_filter(&peer); + assert!(missing.contains(&b)); + assert!(missing.contains(&c)); + assert!(!missing.contains(&a)); + } + + #[test] + fn peers_needing_sync_respects_interval() { + let mut sm = TripleSyncManager::new(Duration::from_millis(10)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + sm.get_peer_state(&addr); + + std::thread::sleep(Duration::from_millis(20)); + let peers = sm.peers_needing_sync(); + assert_eq!(peers.len(), 1); + } + + #[test] + fn store_received_triples_inserts() { + let graph = GraphDB::memory().unwrap(); + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + + let triple = Triple::new( + aingle_graph::NodeId::named("test:a"), + aingle_graph::Predicate::named("test:rel"), + aingle_graph::Value::String("val".into()), + ); + + let result = sm.store_received_triples(vec![triple], &graph); + assert_eq!(result.inserted, 1); + assert_eq!(result.duplicates, 0); + } + + #[test] + fn store_received_triples_skips_duplicates() { + let graph = GraphDB::memory().unwrap(); + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + + let triple = Triple::new( + aingle_graph::NodeId::named("test:a"), + aingle_graph::Predicate::named("test:rel"), + aingle_graph::Value::String("val".into()), + ); + + // Insert once directly. + let _ = graph.insert(triple.clone()); + + // Attempt to insert again via sync. + let result = sm.store_received_triples(vec![triple], &graph); + assert_eq!(result.inserted, 0); + // Depending on GraphDB error message, counted as duplicate or error. + assert!(result.duplicates > 0 || result.errors > 0); + } + + #[test] + fn record_sync_result_updates_state() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + sm.record_sync_result(addr, true, 10); + sm.record_sync_result(addr, true, 5); + + let stats = sm.stats(); + assert_eq!(stats.total_successful_syncs, 2); + } + + #[test] + fn cleanup_inactive_removes_old_peers() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + sm.get_peer_state(&addr); + + std::thread::sleep(Duration::from_millis(10)); + sm.cleanup_inactive(Duration::from_millis(1)); + assert_eq!(sm.stats().peer_count, 0); + } + + #[test] + fn remove_local_id_works() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let a = [1u8; 32]; + let b = [2u8; 32]; + sm.add_local_id(a); + sm.add_local_id(b); + assert_eq!(sm.local_ids().len(), 2); + sm.remove_local_id(&a); + assert_eq!(sm.local_ids().len(), 1); + assert_eq!(sm.local_ids()[0], b); + } + + #[test] + fn add_tombstone_and_check() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let id = [42u8; 32]; + assert!(!sm.has_tombstone(&id)); + sm.add_tombstone(id, 1700000000000); + assert!(sm.has_tombstone(&id)); + } + + #[test] + fn cleanup_expired_tombstones() { + let mut sm = TripleSyncManager::with_tombstone_ttl( + Duration::from_secs(60), + Duration::from_millis(50), + ); + let id = [1u8; 32]; + // Use a timestamp far in the past + sm.add_tombstone(id, 0); + sm.cleanup_expired_tombstones(); + assert!(!sm.has_tombstone(&id)); + } + + #[test] + fn tombstone_ttl_configurable() { + let sm = TripleSyncManager::with_tombstone_ttl( + Duration::from_secs(60), + Duration::from_secs(3600), + ); + assert_eq!(sm.tombstone_ttl, Duration::from_secs(3600)); + } + + #[test] + fn active_tombstones_returns_all() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + sm.add_tombstone([1u8; 32], 100); + sm.add_tombstone([2u8; 32], 200); + let active = sm.active_tombstones(); + assert_eq!(active.len(), 2); + } + + #[test] + fn duplicate_tombstone_is_noop() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + let id = [1u8; 32]; + sm.add_tombstone(id, 100); + sm.add_tombstone(id, 200); + assert_eq!(sm.active_tombstones().len(), 1); + // Second write overwrites timestamp + let ts = sm.tombstones.get(&id).unwrap(); + assert_eq!(*ts, 200); + } + + #[test] + fn stats_are_accurate() { + let mut sm = TripleSyncManager::new(Duration::from_secs(60)); + sm.add_local_id([1u8; 32]); + sm.add_local_id([2u8; 32]); + + let addr: SocketAddr = "127.0.0.1:9000".parse().unwrap(); + sm.record_sync_result(addr, true, 1); + sm.record_sync_result(addr, false, 0); + + let stats = sm.stats(); + assert_eq!(stats.local_ids, 2); + assert_eq!(stats.peer_count, 1); + assert_eq!(stats.total_successful_syncs, 1); + assert_eq!(stats.total_failed_syncs, 1); + } +} diff --git a/crates/aingle_cortex/src/p2p/transport.rs b/crates/aingle_cortex/src/p2p/transport.rs new file mode 100644 index 00000000..68b42c09 --- /dev/null +++ b/crates/aingle_cortex/src/p2p/transport.rs @@ -0,0 +1,494 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! QUIC transport layer for P2P communication. +//! +//! Ported from `aingle_minimal::quic` with cortex-specific ALPN and +//! integrated seed-based handshake. + +use crate::p2p::message::{P2pMessage, MAX_MESSAGE_SIZE}; +use quinn::{ClientConfig, Connection, Endpoint, ServerConfig}; +use rustls::pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer}; +use std::collections::HashMap; +use std::net::SocketAddr; +use std::sync::Arc; +use std::time::Duration; + +/// Transport-layer configuration. +#[derive(Debug, Clone)] +pub struct P2pTransportConfig { + pub bind_addr: String, + pub port: u16, + pub keep_alive: Duration, + pub idle_timeout: Duration, + pub max_connections: usize, +} + +impl Default for P2pTransportConfig { + fn default() -> Self { + Self { + bind_addr: "0.0.0.0".to_string(), + port: 19091, + keep_alive: Duration::from_secs(15), + idle_timeout: Duration::from_secs(60), + max_connections: 64, + } + } +} + +/// QUIC-based P2P transport with length-prefixed JSON messages. +pub struct P2pTransport { + config: P2pTransportConfig, + endpoint: Option, + connections: HashMap, + node_id: String, + /// blake3(seed) for handshake verification. + seed_hash: String, + version: String, +} + +impl P2pTransport { + pub fn new(config: P2pTransportConfig, node_id: String, seed_hash: String) -> Self { + Self { + config, + endpoint: None, + connections: HashMap::new(), + node_id, + seed_hash, + version: env!("CARGO_PKG_VERSION").to_string(), + } + } + + /// Bind the QUIC endpoint. + pub async fn start(&mut self) -> Result<(), String> { + let addr: SocketAddr = format!("{}:{}", self.config.bind_addr, self.config.port) + .parse() + .map_err(|e| format!("invalid address: {}", e))?; + + let server_config = self.generate_server_config()?; + + let endpoint = Endpoint::server(server_config, addr) + .map_err(|e| format!("failed to create QUIC endpoint: {}", e))?; + + tracing::info!("P2P transport started on {}", addr); + self.endpoint = Some(endpoint); + Ok(()) + } + + /// Connect to a remote peer and perform the seed-based handshake. + pub async fn connect(&mut self, addr: SocketAddr, triple_count: u64) -> Result<(), String> { + let endpoint = self.endpoint.as_ref().ok_or("transport not started")?; + + if self.connections.len() >= self.config.max_connections { + return Err("max connections reached".to_string()); + } + + let client_config = self.generate_client_config()?; + + let connection = endpoint + .connect_with(client_config, addr, "cortex-peer") + .map_err(|e| format!("connect init failed: {}", e))? + .await + .map_err(|e| format!("connection failed: {}", e))?; + + // Handshake: send Hello. + let hello = P2pMessage::Hello { + node_id: self.node_id.clone(), + seed_hash: self.seed_hash.clone(), + version: self.version.clone(), + triple_count, + }; + Self::send_on_connection(&connection, &hello).await?; + + // Receive HelloAck. + let ack = Self::recv_from_connection(&connection).await?; + match ack { + P2pMessage::HelloAck { accepted, reason, .. } => { + if !accepted { + connection.close(1u32.into(), b"rejected"); + return Err(format!( + "handshake rejected: {}", + reason.unwrap_or_default() + )); + } + } + _ => { + connection.close(1u32.into(), b"bad handshake"); + return Err("unexpected handshake response".to_string()); + } + } + + tracing::debug!("P2P connected to {}", addr); + self.connections.insert(addr, connection); + Ok(()) + } + + /// Accept one incoming connection, verify seed, and complete handshake. + pub async fn accept(&mut self) -> Result, String> { + let endpoint = self.endpoint.as_ref().ok_or("transport not started")?; + + let incoming = match endpoint.accept().await { + Some(inc) => inc, + None => return Ok(None), + }; + + let connection = incoming + .await + .map_err(|e| format!("accept failed: {}", e))?; + + let remote = connection.remote_address(); + + // Read the Hello. + let hello = Self::recv_from_connection(&connection).await?; + + match &hello { + P2pMessage::Hello { seed_hash, node_id, .. } => { + let accepted = seed_hash == &self.seed_hash; + let reason = if accepted { + None + } else { + Some("seed_mismatch".to_string()) + }; + + let ack = P2pMessage::HelloAck { + node_id: self.node_id.clone(), + accepted, + reason, + }; + Self::send_on_connection(&connection, &ack).await?; + + if accepted { + tracing::info!("P2P accepted connection from {} ({})", remote, &node_id[..8.min(node_id.len())]); + self.connections.insert(remote, connection); + Ok(Some((remote, hello))) + } else { + connection.close(1u32.into(), b"seed_mismatch"); + Ok(None) + } + } + _ => { + connection.close(1u32.into(), b"expected_hello"); + Ok(None) + } + } + } + + /// Send a message to a connected peer. + pub async fn send(&self, addr: &SocketAddr, msg: &P2pMessage) -> Result<(), String> { + let connection = self + .connections + .get(addr) + .ok_or_else(|| format!("no connection to {}", addr))?; + Self::send_on_connection(connection, msg).await + } + + /// Receive the next message from any connected peer (non-blocking attempt). + pub async fn recv(&self) -> Result, String> { + for (addr, connection) in &self.connections { + if let Ok(msg) = Self::recv_from_connection(connection).await { + return Ok(Some((*addr, msg))); + } + } + Ok(None) + } + + /// Close a single peer connection. + pub fn disconnect(&mut self, addr: &SocketAddr) { + if let Some(conn) = self.connections.remove(addr) { + conn.close(0u32.into(), b"disconnected"); + } + } + + /// Check if connected to a specific peer. + pub fn is_connected(&self, addr: &SocketAddr) -> bool { + self.connections.contains_key(addr) + } + + pub fn connected_peers(&self) -> Vec { + self.connections.keys().copied().collect() + } + + pub fn connection_count(&self) -> usize { + self.connections.len() + } + + /// Clone the QUIC endpoint (cheap, Arc-based) for use outside the lock. + pub fn endpoint_clone(&self) -> Option { + self.endpoint.clone() + } + + /// Get the seed hash for handshake verification. + pub fn seed_hash(&self) -> &str { + &self.seed_hash + } + + /// Get the node ID. + pub fn node_id_str(&self) -> &str { + &self.node_id + } + + /// Store an externally-accepted connection. + pub fn store_connection(&mut self, addr: SocketAddr, conn: Connection) { + self.connections.insert(addr, conn); + } + + /// Send a message on a raw connection (not stored in self.connections). + pub async fn send_on_conn(conn: &Connection, msg: &P2pMessage) -> Result<(), String> { + Self::send_on_connection(conn, msg).await + } + + /// Receive a message from a raw connection. + pub async fn recv_from_conn(conn: &Connection) -> Result { + Self::recv_from_connection(conn).await + } + + /// Close all connections and the endpoint. + pub fn stop(&mut self) { + for (_, conn) in self.connections.drain() { + conn.close(0u32.into(), b"shutdown"); + } + if let Some(ep) = self.endpoint.take() { + ep.close(0u32.into(), b"shutdown"); + } + tracing::info!("P2P transport stopped"); + } + + // ── internal helpers ───────────────────────────────────── + + async fn send_on_connection(conn: &Connection, msg: &P2pMessage) -> Result<(), String> { + let payload = msg.to_bytes(); + let mut stream = conn + .open_uni() + .await + .map_err(|e| format!("open stream: {}", e))?; + + stream + .write_all(&payload) + .await + .map_err(|e| format!("write: {}", e))?; + + stream.finish().map_err(|e| format!("finish: {}", e))?; + Ok(()) + } + + async fn recv_from_connection(conn: &Connection) -> Result { + let mut stream = conn + .accept_uni() + .await + .map_err(|e| format!("accept stream: {}", e))?; + + let mut len_buf = [0u8; 4]; + stream + .read_exact(&mut len_buf) + .await + .map_err(|e| format!("read len: {}", e))?; + let len = u32::from_be_bytes(len_buf) as usize; + + if len > MAX_MESSAGE_SIZE { + return Err(format!("message too large: {} bytes", len)); + } + + let mut payload = vec![0u8; len]; + stream + .read_exact(&mut payload) + .await + .map_err(|e| format!("read payload: {}", e))?; + + serde_json::from_slice(&payload).map_err(|e| format!("deserialize: {}", e)) + } + + fn generate_server_config(&self) -> Result { + let cert = rcgen::generate_simple_self_signed(vec![self.node_id.clone()]) + .map_err(|e| format!("cert gen: {}", e))?; + + let cert_der = CertificateDer::from(cert.cert.der().to_vec()); + let key_der = + PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der())); + + let mut server_crypto = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(vec![cert_der], key_der) + .map_err(|e| format!("tls config: {}", e))?; + + server_crypto.alpn_protocols = vec![b"cortex-p2p".to_vec()]; + + let mut server_config = ServerConfig::with_crypto(Arc::new( + quinn::crypto::rustls::QuicServerConfig::try_from(server_crypto) + .map_err(|e| format!("quic crypto: {}", e))?, + )); + + let mut transport = quinn::TransportConfig::default(); + transport.keep_alive_interval(Some(self.config.keep_alive)); + transport.max_idle_timeout(Some( + self.config + .idle_timeout + .try_into() + .map_err(|e| format!("timeout: {}", e))?, + )); + transport.max_concurrent_uni_streams(100u32.into()); + transport.max_concurrent_bidi_streams(100u32.into()); + server_config.transport_config(Arc::new(transport)); + + Ok(server_config) + } + + fn generate_client_config(&self) -> Result { + let mut crypto = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(Arc::new(LoggingCertVerifier)) + .with_no_client_auth(); + + crypto.alpn_protocols = vec![b"cortex-p2p".to_vec()]; + + let mut client_config = ClientConfig::new(Arc::new( + quinn::crypto::rustls::QuicClientConfig::try_from(crypto) + .map_err(|e| format!("quic client crypto: {}", e))?, + )); + + let mut transport = quinn::TransportConfig::default(); + transport.keep_alive_interval(Some(self.config.keep_alive)); + transport.max_idle_timeout(Some( + self.config + .idle_timeout + .try_into() + .map_err(|e| format!("timeout: {}", e))?, + )); + client_config.transport_config(Arc::new(transport)); + + Ok(client_config) + } +} + +/// Certificate verifier that accepts any cert (TOFU model) and logs fingerprints. +#[derive(Debug)] +struct LoggingCertVerifier; + +impl rustls::client::danger::ServerCertVerifier for LoggingCertVerifier { + fn verify_server_cert( + &self, + end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> std::result::Result { + let fingerprint = blake3::hash(end_entity.as_ref()); + tracing::info!( + "P2P peer cert fingerprint for {:?}: {}", + server_name, + hex::encode(fingerprint.as_bytes()) + ); + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> std::result::Result { + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn config_defaults() { + let cfg = P2pTransportConfig::default(); + assert_eq!(cfg.port, 19091); + assert_eq!(cfg.max_connections, 64); + } + + #[test] + fn transport_new_has_no_connections() { + let t = P2pTransport::new( + P2pTransportConfig::default(), + "abc".into(), + "hash".into(), + ); + assert_eq!(t.connection_count(), 0); + assert!(t.connected_peers().is_empty()); + } + + #[test] + fn is_connected_false_initially() { + let t = P2pTransport::new( + P2pTransportConfig::default(), + "abc".into(), + "hash".into(), + ); + let addr: SocketAddr = "127.0.0.1:19091".parse().unwrap(); + assert!(!t.is_connected(&addr)); + } + + #[tokio::test] + async fn start_and_stop() { + let mut t = P2pTransport::new( + P2pTransportConfig { + port: 0, // OS-assigned port + ..Default::default() + }, + "test-node".into(), + "test-hash".into(), + ); + // port 0 lets OS pick a free port + assert!(t.start().await.is_ok()); + t.stop(); + assert!(t.endpoint.is_none()); + } + + #[tokio::test] + async fn connect_to_nonexistent_fails() { + let mut t = P2pTransport::new( + P2pTransportConfig { + port: 0, + ..Default::default() + }, + "test-node".into(), + "test-hash".into(), + ); + t.start().await.unwrap(); + let addr: SocketAddr = "127.0.0.1:1".parse().unwrap(); + assert!(t.connect(addr, 0).await.is_err()); + t.stop(); + } + + #[tokio::test] + async fn disconnect_nonexistent_is_noop() { + let mut t = P2pTransport::new( + P2pTransportConfig::default(), + "abc".into(), + "hash".into(), + ); + let addr: SocketAddr = "127.0.0.1:19091".parse().unwrap(); + t.disconnect(&addr); // should not panic + } +} diff --git a/crates/aingle_cortex/src/proofs/backend.rs b/crates/aingle_cortex/src/proofs/backend.rs new file mode 100644 index 00000000..07e7ffd5 --- /dev/null +++ b/crates/aingle_cortex/src/proofs/backend.rs @@ -0,0 +1,229 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Pluggable storage backends for the proof store. +//! +//! Two implementations: +//! - [`MemoryProofBackend`] — in-memory HashMap (tests / ephemeral) +//! - [`SledProofBackend`] — persistent Sled tree (production) + +use std::collections::HashMap; +use std::sync::RwLock; + +/// Raw key-value backend for proof storage. +pub trait ProofBackend: Send + Sync { + /// Store a proof by ID. + fn put(&self, id: &str, value: &[u8]) -> Result<(), String>; + /// Get a proof by ID. + fn get(&self, id: &str) -> Result>, String>; + /// Delete a proof by ID. Returns true if it existed. + fn delete(&self, id: &str) -> Result; + /// Return all stored (id, bytes) pairs. + fn list_all(&self) -> Result)>, String>; + /// Flush pending writes to durable storage. + fn flush(&self) -> Result<(), String> { + Ok(()) + } +} + +// ============================================================================ +// In-memory backend +// ============================================================================ + +/// In-memory proof backend backed by a `HashMap`. +pub struct MemoryProofBackend { + data: RwLock>>, +} + +impl MemoryProofBackend { + pub fn new() -> Self { + Self { + data: RwLock::new(HashMap::new()), + } + } +} + +impl Default for MemoryProofBackend { + fn default() -> Self { + Self::new() + } +} + +impl ProofBackend for MemoryProofBackend { + fn put(&self, id: &str, value: &[u8]) -> Result<(), String> { + let mut data = self + .data + .write() + .map_err(|_| "MemoryProofBackend lock poisoned".to_string())?; + data.insert(id.to_string(), value.to_vec()); + Ok(()) + } + + fn get(&self, id: &str) -> Result>, String> { + let data = self + .data + .read() + .map_err(|_| "MemoryProofBackend lock poisoned".to_string())?; + Ok(data.get(id).cloned()) + } + + fn delete(&self, id: &str) -> Result { + let mut data = self + .data + .write() + .map_err(|_| "MemoryProofBackend lock poisoned".to_string())?; + Ok(data.remove(id).is_some()) + } + + fn list_all(&self) -> Result)>, String> { + let data = self + .data + .read() + .map_err(|_| "MemoryProofBackend lock poisoned".to_string())?; + Ok(data + .iter() + .map(|(k, v)| (k.clone(), v.clone())) + .collect()) + } +} + +// ============================================================================ +// Sled backend +// ============================================================================ + +/// Persistent proof backend using a Sled named tree. +/// +/// Opens (or creates) a `"proofs"` tree inside the given Sled database path. +/// Since `sled::open` is reference-counted, calling it with the same path +/// as the graph store shares the same underlying database instance. +pub struct SledProofBackend { + tree: sled::Tree, +} + +impl SledProofBackend { + /// Open or create a proofs tree inside the Sled database at `path`. + pub fn open(path: &str) -> Result { + let db = + sled::open(path).map_err(|e| format!("sled open error (proofs): {e}"))?; + let tree = db + .open_tree("proofs") + .map_err(|e| format!("sled open_tree(proofs) error: {e}"))?; + Ok(Self { tree }) + } +} + +impl ProofBackend for SledProofBackend { + fn put(&self, id: &str, value: &[u8]) -> Result<(), String> { + self.tree + .insert(id.as_bytes(), value) + .map_err(|e| format!("sled proofs insert error: {e}"))?; + Ok(()) + } + + fn get(&self, id: &str) -> Result>, String> { + match self.tree.get(id.as_bytes()) { + Ok(Some(bytes)) => Ok(Some(bytes.to_vec())), + Ok(None) => Ok(None), + Err(e) => Err(format!("sled proofs get error: {e}")), + } + } + + fn delete(&self, id: &str) -> Result { + match self.tree.remove(id.as_bytes()) { + Ok(Some(_)) => Ok(true), + Ok(None) => Ok(false), + Err(e) => Err(format!("sled proofs delete error: {e}")), + } + } + + fn list_all(&self) -> Result)>, String> { + let mut results = Vec::new(); + for item in self.tree.iter() { + let (k, v) = + item.map_err(|e| format!("sled proofs scan error: {e}"))?; + let key = String::from_utf8(k.to_vec()) + .map_err(|e| format!("sled proofs key decode error: {e}"))?; + results.push((key, v.to_vec())); + } + Ok(results) + } + + fn flush(&self) -> Result<(), String> { + self.tree + .flush() + .map_err(|e| format!("sled proofs flush error: {e}"))?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memory_backend_crud() { + let backend = MemoryProofBackend::new(); + + backend.put("p1", b"data1").unwrap(); + assert_eq!(backend.get("p1").unwrap(), Some(b"data1".to_vec())); + + backend.put("p1", b"data2").unwrap(); + assert_eq!(backend.get("p1").unwrap(), Some(b"data2".to_vec())); + + assert!(backend.delete("p1").unwrap()); + assert_eq!(backend.get("p1").unwrap(), None); + assert!(!backend.delete("p1").unwrap()); + } + + #[test] + fn test_memory_backend_list_all() { + let backend = MemoryProofBackend::new(); + backend.put("a", b"v1").unwrap(); + backend.put("b", b"v2").unwrap(); + + let all = backend.list_all().unwrap(); + assert_eq!(all.len(), 2); + } + + #[test] + fn test_sled_backend_crud() { + let dir = tempfile::TempDir::new().unwrap(); + let backend = SledProofBackend::open(dir.path().to_str().unwrap()).unwrap(); + + backend.put("p1", b"data1").unwrap(); + assert_eq!(backend.get("p1").unwrap(), Some(b"data1".to_vec())); + + assert!(backend.delete("p1").unwrap()); + assert_eq!(backend.get("p1").unwrap(), None); + } + + #[test] + fn test_sled_backend_persistence() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + { + let backend = SledProofBackend::open(path).unwrap(); + backend.put("k1", b"v1").unwrap(); + backend.flush().unwrap(); + } + + { + let backend = SledProofBackend::open(path).unwrap(); + assert_eq!(backend.get("k1").unwrap(), Some(b"v1".to_vec())); + } + } + + #[test] + fn test_sled_backend_list_all() { + let dir = tempfile::TempDir::new().unwrap(); + let backend = SledProofBackend::open(dir.path().to_str().unwrap()).unwrap(); + + backend.put("a", b"v1").unwrap(); + backend.put("b", b"v2").unwrap(); + backend.put("c", b"v3").unwrap(); + + let all = backend.list_all().unwrap(); + assert_eq!(all.len(), 3); + } +} diff --git a/crates/aingle_cortex/src/proofs/mod.rs b/crates/aingle_cortex/src/proofs/mod.rs index 53418acd..a34dcd02 100644 --- a/crates/aingle_cortex/src/proofs/mod.rs +++ b/crates/aingle_cortex/src/proofs/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Cryptographic proof storage and verification //! //! This module provides storage, retrieval, and verification of zero-knowledge proofs @@ -50,9 +53,11 @@ //! assert!(result.valid); //! ``` +pub mod backend; pub mod store; pub mod verification; +pub use backend::ProofBackend; pub use store::{ProofId, ProofMetadata, ProofStore, ProofType, StoredProof, SubmitProofRequest}; pub use verification::{ProofVerifier, VerificationError, VerificationResult}; diff --git a/crates/aingle_cortex/src/proofs/store.rs b/crates/aingle_cortex/src/proofs/store.rs index a1ec46de..03ee91a1 100644 --- a/crates/aingle_cortex/src/proofs/store.rs +++ b/crates/aingle_cortex/src/proofs/store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Proof storage and management //! //! Provides in-memory storage of zero-knowledge proofs with LRU caching @@ -10,6 +13,7 @@ use std::sync::Arc; use tokio::sync::RwLock; use uuid::Uuid; +use super::backend::{MemoryProofBackend, ProofBackend, SledProofBackend}; use super::verification::{VerificationError, VerificationResult}; use super::ProofVerifier; @@ -129,8 +133,8 @@ pub struct SubmitProofResponse { /// Proof storage with verification cache pub struct ProofStore { - /// Stored proofs (proof_id -> proof) - proofs: Arc>>, + /// Pluggable storage backend (memory or sled) + backend: Arc, /// Verification cache (proof_id -> result) verification_cache: Arc>>, /// Proof verifier @@ -214,15 +218,48 @@ pub struct ProofStoreStats { } impl ProofStore { - /// Create a new proof store + /// Create a new proof store with in-memory backend pub fn new() -> Self { - Self::with_cache_size(1000) + Self::with_backend(Arc::new(MemoryProofBackend::new()), 1000) } - /// Create a new proof store with custom cache size + /// Create a new proof store with custom cache size (in-memory backend) pub fn with_cache_size(cache_size: usize) -> Self { + Self::with_backend(Arc::new(MemoryProofBackend::new()), cache_size) + } + + /// Create a persistent proof store backed by Sled at the given path. + pub fn with_sled(path: &str) -> Result { + let backend = Arc::new(SledProofBackend::open(path)?); + + // Rebuild stats from persisted data before constructing the store, + // so we don't need to acquire the tokio RwLock from sync context. + let mut initial_stats = ProofStoreStats::default(); + if let Ok(all) = backend.list_all() { + initial_stats.total_proofs = all.len(); + for (_, bytes) in &all { + if let Ok(proof) = serde_json::from_slice::(bytes) { + *initial_stats + .proofs_by_type + .entry(proof.proof_type.to_string()) + .or_insert(0) += 1; + initial_stats.total_size_bytes += proof.size_bytes(); + } + } + } + + Ok(Self { + backend, + verification_cache: Arc::new(RwLock::new(LruCache::new(1000))), + verifier: ProofVerifier::new(), + stats: Arc::new(RwLock::new(initial_stats)), + }) + } + + /// Create a proof store with a custom backend. + pub fn with_backend(backend: Arc, cache_size: usize) -> Self { Self { - proofs: Arc::new(RwLock::new(HashMap::new())), + backend, verification_cache: Arc::new(RwLock::new(LruCache::new(cache_size))), verifier: ProofVerifier::new(), stats: Arc::new(RwLock::new(ProofStoreStats::default())), @@ -240,13 +277,16 @@ impl ProofStore { let stored_proof = StoredProof::new(request.proof_type.clone(), proof_bytes, metadata); let proof_id = stored_proof.id.clone(); - // Store proof - let mut proofs = self.proofs.write().await; - proofs.insert(proof_id.clone(), stored_proof); + // Serialize and store via backend + let serialized = serde_json::to_vec(&stored_proof) + .map_err(|e| VerificationError::InvalidProofData(e.to_string()))?; + self.backend + .put(&proof_id, &serialized) + .map_err(|e| VerificationError::InvalidProofData(e))?; // Update stats let mut stats = self.stats.write().await; - stats.total_proofs = proofs.len(); + stats.total_proofs += 1; *stats .proofs_by_type .entry(request.proof_type.to_string()) @@ -270,17 +310,21 @@ impl ProofStore { /// Retrieve a proof by ID pub async fn get(&self, proof_id: &ProofId) -> Option { - let proofs = self.proofs.read().await; - proofs.get(proof_id).cloned() + match self.backend.get(proof_id) { + Ok(Some(bytes)) => serde_json::from_slice(&bytes).ok(), + _ => None, + } } /// List all proofs (with optional type filter) pub async fn list(&self, proof_type: Option) -> Vec { - let proofs = self.proofs.read().await; - proofs - .values() + let all = match self.backend.list_all() { + Ok(items) => items, + Err(_) => return Vec::new(), + }; + all.into_iter() + .filter_map(|(_, bytes)| serde_json::from_slice::(&bytes).ok()) .filter(|p| proof_type.as_ref().is_none_or(|t| &p.proof_type == t)) - .cloned() .collect() } @@ -314,11 +358,13 @@ impl ProofStore { // Verify using the verifier let result = self.verifier.verify(&proof).await?; - // Update proof's verified status - { - let mut proofs = self.proofs.write().await; - if let Some(stored) = proofs.get_mut(proof_id) { + // Update proof's verified status in backend + if let Ok(Some(bytes)) = self.backend.get(proof_id) { + if let Ok(mut stored) = serde_json::from_slice::(&bytes) { stored.mark_verified(result.valid); + if let Ok(updated) = serde_json::to_vec(&stored) { + let _ = self.backend.put(proof_id, &updated); + } } } @@ -354,23 +400,32 @@ impl ProofStore { /// Delete a proof pub async fn delete(&self, proof_id: &ProofId) -> bool { - let mut proofs = self.proofs.write().await; - let removed = proofs.remove(proof_id); + // Get proof data for stats update before deleting + let proof = match self.backend.get(proof_id) { + Ok(Some(bytes)) => serde_json::from_slice::(&bytes).ok(), + _ => None, + }; - if let Some(proof) = removed { - // Update stats - let mut stats = self.stats.write().await; - stats.total_proofs = proofs.len(); - let type_key = proof.proof_type.to_string(); - if let Some(count) = stats.proofs_by_type.get_mut(&type_key) { - *count = count.saturating_sub(1); + let removed = self.backend.delete(proof_id).unwrap_or(false); + + if removed { + if let Some(proof) = proof { + // Update stats + let mut stats = self.stats.write().await; + stats.total_proofs = stats.total_proofs.saturating_sub(1); + let type_key = proof.proof_type.to_string(); + if let Some(count) = stats.proofs_by_type.get_mut(&type_key) { + *count = count.saturating_sub(1); + } + stats.total_size_bytes = stats.total_size_bytes.saturating_sub(proof.size_bytes()); } - stats.total_size_bytes = stats.total_size_bytes.saturating_sub(proof.size_bytes()); // Remove from cache - let mut cache = self.verification_cache.write().await; - cache.map.remove(proof_id); - cache.order.retain(|id| id != proof_id); + { + let mut cache = self.verification_cache.write().await; + cache.map.remove(proof_id); + cache.order.retain(|id| id != proof_id); + } true } else { @@ -386,21 +441,98 @@ impl ProofStore { /// Clear all proofs pub async fn clear(&self) { - let mut proofs = self.proofs.write().await; - proofs.clear(); - - let mut cache = self.verification_cache.write().await; - cache.map.clear(); - cache.order.clear(); - - let mut stats = self.stats.write().await; - *stats = ProofStoreStats::default(); + // Delete all from backend + if let Ok(all) = self.backend.list_all() { + for (id, _) in all { + let _ = self.backend.delete(&id); + } + } + { + let mut cache = self.verification_cache.write().await; + cache.map.clear(); + cache.order.clear(); + } + { + let mut stats = self.stats.write().await; + *stats = ProofStoreStats::default(); + } } /// Get count of proofs pub async fn count(&self) -> usize { - let proofs = self.proofs.read().await; - proofs.len() + self.backend + .list_all() + .map(|all| all.len()) + .unwrap_or(0) + } + + /// Flush proof backend to durable storage. + pub fn flush(&self) -> Result<(), String> { + self.backend.flush() + } + + /// Export all proofs as snapshot entries (sync, for Raft snapshots). + #[cfg(feature = "cluster")] + pub fn export_proofs_sync(&self) -> Vec { + let all = match self.backend.list_all() { + Ok(items) => items, + Err(_) => return Vec::new(), + }; + all.into_iter() + .filter_map(|(_, bytes)| { + let proof: StoredProof = serde_json::from_slice(&bytes).ok()?; + Some(aingle_raft::state_machine::ProofSnapshot { + id: proof.id, + proof_type: proof.proof_type.to_string(), + data: proof.data, + created_at: proof.created_at.to_rfc3339(), + verified: proof.verified, + verified_at: proof.verified_at.map(|dt| dt.to_rfc3339()), + metadata: serde_json::to_value(&proof.metadata).unwrap_or_default(), + }) + }) + .collect() + } + + /// Import proofs from a snapshot, replacing existing data (sync, for Raft snapshots). + #[cfg(feature = "cluster")] + pub fn import_proofs_sync(&self, proofs: &[aingle_raft::state_machine::ProofSnapshot]) { + // Clear existing proofs + if let Ok(all) = self.backend.list_all() { + for (id, _) in all { + let _ = self.backend.delete(&id); + } + } + + for snap in proofs { + let metadata: ProofMetadata = + serde_json::from_value(snap.metadata.clone()).unwrap_or_default(); + let proof_type: ProofType = + serde_json::from_value(serde_json::Value::String(snap.proof_type.clone())) + .unwrap_or(ProofType::Knowledge); + let created_at = chrono::DateTime::parse_from_rfc3339(&snap.created_at) + .map(|dt| dt.with_timezone(&chrono::Utc)) + .unwrap_or_else(|_| chrono::Utc::now()); + let verified_at = snap.verified_at.as_ref().and_then(|s| { + chrono::DateTime::parse_from_rfc3339(s) + .ok() + .map(|dt| dt.with_timezone(&chrono::Utc)) + }); + + let stored = StoredProof { + id: snap.id.clone(), + proof_type, + data: snap.data.clone(), + created_at, + verified: snap.verified, + verified_at, + metadata, + }; + + if let Ok(bytes) = serde_json::to_vec(&stored) { + let _ = self.backend.put(&snap.id, &bytes); + } + } } } @@ -570,4 +702,51 @@ mod tests { assert!(!proof.verified); assert!(proof.verified_at.is_none()); } + + #[tokio::test] + async fn test_sled_proof_store_persistence() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + let proof_id; + { + let store = ProofStore::with_sled(path).unwrap(); + let request = SubmitProofRequest { + proof_type: ProofType::Schnorr, + proof_data: serde_json::json!({"key": "value"}), + metadata: None, + }; + proof_id = store.submit(request).await.unwrap(); + assert_eq!(store.count().await, 1); + store.flush().unwrap(); + } + { + let store = ProofStore::with_sled(path).unwrap(); + assert_eq!(store.count().await, 1); + let proof = store.get(&proof_id).await.unwrap(); + assert_eq!(proof.proof_type, ProofType::Schnorr); + let stats = store.stats().await; + assert_eq!(stats.total_proofs, 1); + assert_eq!(stats.proofs_by_type.get("schnorr"), Some(&1)); + } + } + + #[tokio::test] + async fn test_sled_proof_store_delete_persists() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + { + let store = ProofStore::with_sled(path).unwrap(); + let request = SubmitProofRequest { + proof_type: ProofType::Membership, + proof_data: serde_json::json!({"proof": true}), + metadata: None, + }; + let id = store.submit(request).await.unwrap(); + store.delete(&id).await; + store.flush().unwrap(); + } + // Reopen — deletion should persist + let store2 = ProofStore::with_sled(path).unwrap(); + assert_eq!(store2.count().await, 0); + } } diff --git a/crates/aingle_cortex/src/proofs/verification.rs b/crates/aingle_cortex/src/proofs/verification.rs index 3a24ebc6..24842c97 100644 --- a/crates/aingle_cortex/src/proofs/verification.rs +++ b/crates/aingle_cortex/src/proofs/verification.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Proof verification using aingle_zk //! //! This module integrates with aingle_zk to verify different types of @@ -86,6 +89,61 @@ impl VerificationResult { } } +/// Reconstruct a `ZkProof` from a `StoredProof` whose `data` field contains +/// only the raw `proof_data` JSON (without the ZkProof envelope). +fn reconstruct_zk_proof(proof: &StoredProof) -> Result { + let mut proof_data: serde_json::Value = serde_json::from_slice(&proof.data) + .map_err(|e| VerificationError::DeserializationError(e.to_string()))?; + + // Inject the ProofData serde tag ("type") if the client omitted it. + // ProofData uses #[serde(tag = "type")] so deserialisation requires it. + if let serde_json::Value::Object(ref mut map) = proof_data { + if !map.contains_key("type") { + let tag = cortex_to_proof_data_tag(&proof.proof_type); + map.insert("type".into(), serde_json::Value::String(tag.into())); + } + } + + let zk_type = cortex_to_zk_proof_type(&proof.proof_type); + + let envelope = serde_json::json!({ + "proof_type": serde_json::to_value(&zk_type) + .map_err(|e| VerificationError::DeserializationError(e.to_string()))?, + "proof_data": proof_data, + "timestamp": proof.created_at.timestamp() as u64, + "metadata": null + }); + + serde_json::from_value(envelope) + .map_err(|e| VerificationError::DeserializationError( + format!("Failed to reconstruct ZkProof envelope: {e}") + )) +} + +/// Map Cortex `ProofType` → `aingle_zk::ProofType`. +fn cortex_to_zk_proof_type(pt: &ProofType) -> aingle_zk::ProofType { + match pt { + ProofType::Schnorr | ProofType::Knowledge | ProofType::HashOpening => { + aingle_zk::ProofType::KnowledgeProof + } + ProofType::Equality => aingle_zk::ProofType::EqualityProof, + ProofType::Membership => aingle_zk::ProofType::MembershipProof, + ProofType::NonMembership => aingle_zk::ProofType::NonMembershipProof, + ProofType::Range => aingle_zk::ProofType::RangeProof, + } +} + +/// Map Cortex `ProofType` → `ProofData` serde tag value. +fn cortex_to_proof_data_tag(pt: &ProofType) -> &'static str { + match pt { + ProofType::Schnorr | ProofType::Knowledge => "Knowledge", + ProofType::HashOpening => "HashOpening", + ProofType::Equality => "Equality", + ProofType::Membership | ProofType::NonMembership => "Membership", + ProofType::Range => "Knowledge", + } +} + /// Proof verifier that integrates with aingle_zk pub struct ProofVerifier { /// Configuration for verification @@ -140,9 +198,13 @@ impl ProofVerifier { ))); } - // Deserialize the proof data into aingle_zk::ZkProof + // Deserialize the proof data into aingle_zk::ZkProof. + // The stored data may be just the raw proof_data (without the ZkProof + // envelope) when submitted via the REST API, since submit() only + // persists request.proof_data. Try full envelope first, then + // reconstruct from StoredProof.proof_type + raw proof data. let zk_proof: aingle_zk::ZkProof = serde_json::from_slice(&proof.data) - .map_err(|e| VerificationError::DeserializationError(e.to_string()))?; + .or_else(|_| reconstruct_zk_proof(proof))?; // Verify based on proof type let valid = match proof.proof_type { @@ -430,6 +492,54 @@ mod tests { assert!(verifier.config.strict_mode); } + /// Simulates the real REST API path: submit() stores only proof_data + /// (without the ZkProof envelope), and verify() must reconstruct it. + #[tokio::test] + async fn test_verify_proof_stored_without_envelope() { + let verifier = ProofVerifier::new(); + + // Create a valid hash-opening proof via aingle_zk + let commitment = aingle_zk::HashCommitment::commit(b"test data"); + let zk_proof = aingle_zk::ZkProof::hash_opening(&commitment); + + // Store ONLY the proof_data portion (what submit() actually does) + let proof_data_only = serde_json::to_vec(&zk_proof.proof_data).unwrap(); + let stored = StoredProof::new( + ProofType::HashOpening, + proof_data_only, + ProofMetadata::default(), + ); + + // This is the exact path that was failing with 422 + let result = verifier.verify(&stored).await; + assert!(result.is_ok(), "verify() should reconstruct ZkProof envelope: {:?}", result.err()); + assert!(result.unwrap().valid); + } + + /// Verify reconstruction works when client sends raw fields without serde tag. + #[tokio::test] + async fn test_verify_proof_without_type_tag() { + let verifier = ProofVerifier::new(); + + // Client sends proof_data as raw JSON without the ProofData "type" tag + let commitment = aingle_zk::HashCommitment::commit(b"test data"); + let raw = serde_json::json!({ + "commitment": commitment.hash, + "salt": commitment.salt, + }); + let raw_bytes = serde_json::to_vec(&raw).unwrap(); + + let stored = StoredProof::new( + ProofType::HashOpening, + raw_bytes, + ProofMetadata::default(), + ); + + let result = verifier.verify(&stored).await; + assert!(result.is_ok(), "verify() should inject type tag: {:?}", result.err()); + assert!(result.unwrap().valid); + } + #[tokio::test] async fn test_proof_size_limit() { let config = VerifierConfig { diff --git a/crates/aingle_cortex/src/rest/audit.rs b/crates/aingle_cortex/src/rest/audit.rs new file mode 100644 index 00000000..4bb26069 --- /dev/null +++ b/crates/aingle_cortex/src/rest/audit.rs @@ -0,0 +1,353 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Audit log for tracking API actions +//! +//! Provides an append-only, file-backed audit log with REST endpoints +//! for querying and aggregating audit entries. + +use axum::{ + extract::{Query, State}, + Json, Router, + routing::get, +}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::io::{BufRead, Write}; +use std::path::PathBuf; + +use crate::state::AppState; + +/// A single audit log entry. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AuditEntry { + /// ISO 8601 timestamp + pub timestamp: String, + /// User ID (from JWT sub) + pub user_id: String, + /// Namespace scope + pub namespace: Option, + /// Action performed (create, read, delete, query, validate, etc.) + pub action: String, + /// Resource path or identifier + pub resource: String, + /// Additional details + #[serde(skip_serializing_if = "Option::is_none")] + pub details: Option, + /// Unique request ID for correlation + #[serde(skip_serializing_if = "Option::is_none")] + pub request_id: Option, +} + +/// Audit log with optional JSONL file backing. +pub struct AuditLog { + entries: Vec, + max_entries: usize, + /// Optional file path for JSONL persistence. + log_path: Option, +} + +impl AuditLog { + pub fn new(max_entries: usize) -> Self { + Self { + entries: Vec::new(), + max_entries, + log_path: None, + } + } + + /// Create a file-backed audit log. Reads existing entries from the JSONL file on disk. + pub fn with_path(max_entries: usize, path: PathBuf) -> Self { + let mut entries = Vec::new(); + + // Read existing entries from JSONL file + if let Ok(file) = std::fs::File::open(&path) { + let reader = std::io::BufReader::new(file); + for line in reader.lines() { + if let Ok(line) = line { + if let Ok(entry) = serde_json::from_str::(&line) { + entries.push(entry); + } + } + } + // Keep only the last max_entries + if entries.len() > max_entries { + entries = entries.split_off(entries.len() - max_entries); + } + } + + Self { + entries, + max_entries, + log_path: Some(path), + } + } + + /// Record a new audit entry. + pub fn record(&mut self, entry: AuditEntry) { + // Append to file if file-backed + if let Some(ref path) = self.log_path { + match serde_json::to_string(&entry) { + Ok(json) => { + match std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + { + Ok(mut file) => { + if let Err(e) = writeln!(file, "{}", json) { + log::error!("Audit log write failed: {e}"); + } else if let Err(e) = file.sync_all() { + log::warn!("Audit log fsync failed: {e}"); + } + } + Err(e) => { + log::error!("Audit log open failed ({}): {e}", path.display()); + } + } + } + Err(e) => { + log::error!("Audit entry serialization failed: {e}"); + } + } + } + + // Evict oldest entries if at capacity + if self.entries.len() >= self.max_entries { + let drain_count = self.max_entries / 10; // evict 10% + self.entries.drain(0..drain_count); + } + self.entries.push(entry); + } + + /// Query entries with filters. + pub fn query( + &self, + user_id: Option<&str>, + namespace: Option<&str>, + action: Option<&str>, + from: Option<&str>, + to: Option<&str>, + limit: usize, + ) -> Vec<&AuditEntry> { + self.entries + .iter() + .rev() // newest first + .filter(|e| { + if let Some(uid) = user_id { + if e.user_id != uid { + return false; + } + } + if let Some(ns) = namespace { + if e.namespace.as_deref() != Some(ns) { + return false; + } + } + if let Some(act) = action { + if e.action != act { + return false; + } + } + if let Some(f) = from { + if e.timestamp.as_str() < f { + return false; + } + } + if let Some(t) = to { + if e.timestamp.as_str() > t { + return false; + } + } + true + }) + .take(limit) + .collect() + } + + /// Get aggregate stats. + pub fn stats(&self) -> AuditStats { + let mut actions: HashMap = HashMap::new(); + let mut users: HashMap = HashMap::new(); + let mut namespaces: HashMap = HashMap::new(); + + for entry in &self.entries { + *actions.entry(entry.action.clone()).or_insert(0) += 1; + *users.entry(entry.user_id.clone()).or_insert(0) += 1; + if let Some(ref ns) = entry.namespace { + *namespaces.entry(ns.clone()).or_insert(0) += 1; + } + } + + AuditStats { + total_entries: self.entries.len(), + actions_by_type: actions, + entries_by_user: users, + entries_by_namespace: namespaces, + } + } + + pub fn len(&self) -> usize { + self.entries.len() + } +} + +impl Default for AuditLog { + fn default() -> Self { + Self::new(10_000) + } +} + +/// Aggregate audit statistics. +#[derive(Debug, Serialize)] +pub struct AuditStats { + pub total_entries: usize, + pub actions_by_type: HashMap, + pub entries_by_user: HashMap, + pub entries_by_namespace: HashMap, +} + +// ============================================================================ +// REST Endpoints +// ============================================================================ + +/// Query parameters for the audit endpoint. +#[derive(Debug, Deserialize)] +pub struct AuditQueryParams { + pub user_id: Option, + pub namespace: Option, + pub action: Option, + pub from: Option, + pub to: Option, + #[serde(default = "default_limit")] + pub limit: usize, +} + +fn default_limit() -> usize { + 100 +} + +/// GET /api/v1/audit +pub async fn get_audit_log( + State(state): State, + Query(params): Query, +) -> Json> { + let log = state.audit_log.read().await; + let entries = log.query( + params.user_id.as_deref(), + params.namespace.as_deref(), + params.action.as_deref(), + params.from.as_deref(), + params.to.as_deref(), + params.limit, + ); + Json(entries.into_iter().cloned().collect()) +} + +/// GET /api/v1/audit/stats +pub async fn get_audit_stats( + State(state): State, +) -> Json { + let log = state.audit_log.read().await; + Json(log.stats()) +} + +/// Create the audit router. +pub fn audit_router() -> Router { + Router::new() + .route("/api/v1/audit", get(get_audit_log)) + .route("/api/v1/audit/stats", get(get_audit_stats)) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn make_entry(action: &str, user: &str, ns: Option<&str>) -> AuditEntry { + AuditEntry { + timestamp: chrono::Utc::now().to_rfc3339(), + user_id: user.to_string(), + namespace: ns.map(|s| s.to_string()), + action: action.to_string(), + resource: "/api/v1/triples".to_string(), + details: None, + request_id: None, + } + } + + #[test] + fn test_audit_log_record_and_query() { + let mut log = AuditLog::new(100); + log.record(make_entry("create", "user1", Some("mayros"))); + log.record(make_entry("read", "user2", Some("other"))); + log.record(make_entry("delete", "user1", Some("mayros"))); + + assert_eq!(log.len(), 3); + + let results = log.query(Some("user1"), None, None, None, None, 100); + assert_eq!(results.len(), 2); + + let results = log.query(None, Some("mayros"), None, None, None, 100); + assert_eq!(results.len(), 2); + + let results = log.query(None, None, Some("delete"), None, None, 100); + assert_eq!(results.len(), 1); + } + + #[test] + fn test_audit_log_eviction() { + let mut log = AuditLog::new(10); + for i in 0..15 { + log.record(make_entry(&format!("action-{}", i), "user1", None)); + } + // Should have evicted some entries + assert!(log.len() <= 15); + assert!(log.len() > 0); + } + + #[test] + fn test_audit_log_file_backed_roundtrip() { + let dir = std::env::temp_dir().join(format!("audit_test_{}", std::process::id())); + let _ = std::fs::create_dir_all(&dir); + let path = dir.join("audit.jsonl"); + + // Clean up any leftover file + let _ = std::fs::remove_file(&path); + + // Record entries to a file-backed log + { + let mut log = AuditLog::with_path(100, path.clone()); + log.record(make_entry("create", "user1", Some("ns1"))); + log.record(make_entry("read", "user2", Some("ns2"))); + assert_eq!(log.len(), 2); + } + // Drop log — entries should persist on disk + + // Recreate from file — entries should be restored + { + let log = AuditLog::with_path(100, path.clone()); + assert_eq!(log.len(), 2); + let results = log.query(Some("user1"), None, None, None, None, 100); + assert_eq!(results.len(), 1); + assert_eq!(results[0].action, "create"); + } + + // Clean up + let _ = std::fs::remove_file(&path); + let _ = std::fs::remove_dir(&dir); + } + + #[test] + fn test_audit_stats() { + let mut log = AuditLog::new(100); + log.record(make_entry("create", "user1", Some("ns1"))); + log.record(make_entry("create", "user1", Some("ns1"))); + log.record(make_entry("read", "user2", Some("ns2"))); + + let stats = log.stats(); + assert_eq!(stats.total_entries, 3); + assert_eq!(*stats.actions_by_type.get("create").unwrap(), 2); + assert_eq!(*stats.actions_by_type.get("read").unwrap(), 1); + assert_eq!(*stats.entries_by_user.get("user1").unwrap(), 2); + } +} diff --git a/crates/aingle_cortex/src/rest/cluster.rs b/crates/aingle_cortex/src/rest/cluster.rs new file mode 100644 index 00000000..5408972d --- /dev/null +++ b/crates/aingle_cortex/src/rest/cluster.rs @@ -0,0 +1,413 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Cluster management REST endpoints. +//! +//! ## Endpoints +//! +//! - `GET /api/v1/cluster/status` — Node role, term, leader, members +//! - `POST /api/v1/cluster/join` — Request to join cluster +//! - `POST /api/v1/cluster/leave` — Graceful leave +//! - `GET /api/v1/cluster/members` — List members with replication lag +//! - `GET /api/v1/cluster/wal/stats` — WAL statistics +//! - `POST /api/v1/cluster/wal/verify` — Verify WAL hash chain integrity + +use axum::{ + extract::State, + http::{HeaderMap, StatusCode}, + Json, +}; +use serde::{Deserialize, Serialize}; + +use crate::error::{Error, Result}; +use crate::rest::cluster_utils::validate_cluster_auth; +use crate::state::AppState; + +#[cfg(feature = "cluster")] +use openraft::type_config::async_runtime::watch::WatchReceiver; + +/// Cluster status response. +#[derive(Debug, Serialize)] +pub struct ClusterStatus { + pub node_id: u64, + pub role: String, + pub term: u64, + pub leader_id: Option, + pub leader_addr: Option, + pub members: Vec, + pub wal_last_seq: u64, + pub last_applied: u64, + pub commit_index: u64, +} + +/// Information about a single cluster member. +#[derive(Debug, Serialize)] +pub struct ClusterMember { + pub node_id: u64, + pub rest_addr: String, + pub p2p_addr: String, + pub role: String, + pub last_heartbeat: String, + pub replication_lag: u64, +} + +/// Request to join the cluster. +#[derive(Debug, Deserialize)] +pub struct JoinRequest { + pub node_id: u64, + pub rest_addr: String, + pub p2p_addr: String, +} + +/// Join response. +#[derive(Debug, Serialize)] +pub struct JoinResponse { + pub accepted: bool, + pub leader_id: Option, + pub leader_addr: Option, + pub message: String, +} + +/// WAL statistics response. +#[derive(Debug, Serialize)] +pub struct WalStatsResponse { + pub segment_count: usize, + pub total_size_bytes: u64, + pub last_seq: u64, + pub next_seq: u64, +} + +/// WAL verification response. +#[derive(Debug, Serialize)] +pub struct WalVerifyResponse { + pub valid: bool, + pub entries_checked: u64, + pub first_invalid_seq: Option, +} + +/// GET /api/v1/cluster/status +pub async fn cluster_status( + State(state): State, +) -> Result> { + let wal_last_seq = { + #[cfg(feature = "cluster")] + { + state.wal.as_ref().map(|w| w.last_seq()).unwrap_or(0) + } + #[cfg(not(feature = "cluster"))] + { 0u64 } + }; + + // Extract live Raft metrics when available + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let metrics = raft.metrics().borrow_watched().clone(); + + let role = format!("{:?}", metrics.state); + let term = metrics.current_term; + let leader_id = metrics.current_leader; + + let last_applied = metrics + .last_applied + .as_ref() + .map(|lid| lid.index) + .unwrap_or(0); + + let commit_index = metrics + .last_log_index + .unwrap_or(0); + + // Build member list from membership config + let membership = metrics.membership_config.membership(); + let members: Vec = membership + .nodes() + .map(|(nid, node)| ClusterMember { + node_id: *nid, + rest_addr: node.rest_addr.clone(), + p2p_addr: node.p2p_addr.clone(), + role: if Some(*nid) == leader_id { + "leader".to_string() + } else { + "follower".to_string() + }, + last_heartbeat: "N/A".to_string(), + replication_lag: 0, + }) + .collect(); + + // Resolve leader address from membership config (#13) + let leader_addr = leader_id.and_then(|lid| { + membership.nodes().find(|(nid, _)| **nid == lid).map(|(_, node)| node.rest_addr.clone()) + }); + + return Ok(Json(ClusterStatus { + node_id: state.cluster_node_id.unwrap_or(0), + role, + term, + leader_id, + leader_addr, + members, + wal_last_seq, + last_applied, + commit_index, + })); + } + + Ok(Json(ClusterStatus { + node_id: 0, + role: "standalone".to_string(), + term: 0, + leader_id: None, + leader_addr: None, + members: Vec::new(), + wal_last_seq, + last_applied: 0, + commit_index: 0, + })) +} + +/// POST /api/v1/cluster/join +pub async fn cluster_join( + State(state): State, + headers: HeaderMap, + Json(req): Json, +) -> Result<(StatusCode, Json)> { + validate_cluster_auth(&headers, &state)?; + + tracing::info!( + node_id = req.node_id, + rest_addr = %req.rest_addr, + "Cluster join request received" + ); + + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + // Check if this node is leader; if not, redirect (#14) + let metrics = raft.metrics().borrow_watched().clone(); + if metrics.current_leader != state.cluster_node_id { + let membership = metrics.membership_config.membership(); + let leader_addr = metrics.current_leader.and_then(|lid| { + membership.nodes().find(|(nid, _)| **nid == lid).map(|(_, node)| node.rest_addr.clone()) + }); + if let Some(ref addr) = leader_addr { + return Err(Error::Redirect(format!("http://{}/api/v1/cluster/join", addr))); + } + return Ok(( + StatusCode::CONFLICT, + Json(JoinResponse { + accepted: false, + leader_id: metrics.current_leader, + leader_addr, + message: "Not leader; leader unknown".to_string(), + }), + )); + } + + let node = aingle_raft::CortexNode { + rest_addr: req.rest_addr.clone(), + p2p_addr: req.p2p_addr.clone(), + }; + + // Add as learner first + match raft.add_learner(req.node_id, node, true).await { + Ok(_) => { + // Then promote to voter + let metrics = raft.metrics().borrow_watched().clone(); + let membership = metrics.membership_config.membership(); + let mut voter_ids: std::collections::BTreeSet = + membership.voter_ids().collect(); + voter_ids.insert(req.node_id); + // Resolve leader_addr for response + let leader_addr = metrics.current_leader.and_then(|lid| { + membership.nodes().find(|(nid, _)| **nid == lid).map(|(_, node)| node.rest_addr.clone()) + }); + match raft.change_membership(voter_ids.clone(), false).await { + Ok(_) => { + return Ok(( + StatusCode::OK, + Json(JoinResponse { + accepted: true, + leader_id: metrics.current_leader, + leader_addr, + message: format!("Node {} joined cluster", req.node_id), + }), + )); + } + Err(e) => { + // Rollback: remove orphaned learner + tracing::warn!( + "Membership change failed, removing learner {}", + req.node_id + ); + let mut rollback_ids = voter_ids; + rollback_ids.remove(&req.node_id); + let _ = raft.change_membership(rollback_ids, false).await; + return Ok(( + StatusCode::CONFLICT, + Json(JoinResponse { + accepted: false, + leader_id: metrics.current_leader, + leader_addr, + message: format!("Membership change failed: {e}"), + }), + )); + } + } + } + Err(e) => { + return Ok(( + StatusCode::CONFLICT, + Json(JoinResponse { + accepted: false, + leader_id: None, + leader_addr: None, + message: format!("Add learner failed: {e}"), + }), + )); + } + } + } + + Ok(( + StatusCode::OK, + Json(JoinResponse { + accepted: false, + leader_id: None, + leader_addr: None, + message: "Cluster mode not active on this node".to_string(), + }), + )) +} + +/// POST /api/v1/cluster/leave +pub async fn cluster_leave( + State(state): State, + headers: HeaderMap, +) -> Result { + validate_cluster_auth(&headers, &state)?; + tracing::info!("Cluster leave request received"); + + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + // Check if this node is leader; if not, redirect to leader (#14) + let metrics = raft.metrics().borrow_watched().clone(); + if metrics.current_leader != state.cluster_node_id { + let membership = metrics.membership_config.membership(); + let leader_addr = metrics.current_leader.and_then(|lid| { + membership.nodes().find(|(nid, _)| **nid == lid).map(|(_, node)| node.rest_addr.clone()) + }); + if let Some(ref addr) = leader_addr { + return Err(Error::Redirect(format!("http://{}/api/v1/cluster/leave", addr))); + } + return Err(Error::Internal("Not leader; leader unknown".to_string())); + } + + if let Some(node_id) = state.cluster_node_id { + let membership = metrics.membership_config.membership(); + let mut voter_ids: std::collections::BTreeSet = + membership.voter_ids().collect(); + voter_ids.remove(&node_id); + if !voter_ids.is_empty() { + if let Err(e) = raft.change_membership(voter_ids, false).await { + tracing::error!("Failed to leave cluster: {e}"); + return Err(Error::Internal(format!("Leave failed: {e}"))); + } + } + } + } + + Ok(StatusCode::OK) +} + +/// GET /api/v1/cluster/members +pub async fn cluster_members( + State(state): State, +) -> Result>> { + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let metrics = raft.metrics().borrow_watched().clone(); + let leader_id = metrics.current_leader; + + let membership = metrics.membership_config.membership(); + let members: Vec = membership + .nodes() + .map(|(nid, node)| ClusterMember { + node_id: *nid, + rest_addr: node.rest_addr.clone(), + p2p_addr: node.p2p_addr.clone(), + role: if Some(*nid) == leader_id { + "leader".to_string() + } else { + "follower".to_string() + }, + last_heartbeat: "N/A".to_string(), + replication_lag: 0, + }) + .collect(); + return Ok(Json(members)); + } + + Ok(Json(Vec::new())) +} + +/// GET /api/v1/cluster/wal/stats +pub async fn wal_stats( + State(state): State, +) -> Result> { + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + let stats = wal.stats().map_err(|e| Error::Internal(format!("WAL stats error: {e}")))?; + return Ok(Json(WalStatsResponse { + segment_count: stats.segment_count, + total_size_bytes: stats.total_size_bytes, + last_seq: stats.last_seq, + next_seq: stats.next_seq, + })); + } + + Ok(Json(WalStatsResponse { + segment_count: 0, + total_size_bytes: 0, + last_seq: 0, + next_seq: 0, + })) +} + +/// POST /api/v1/cluster/wal/verify +pub async fn wal_verify( + State(state): State, +) -> Result> { + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + let wal_dir = wal.dir(); + let reader = aingle_wal::WalReader::open(wal_dir) + .map_err(|e| Error::Internal(format!("WAL open failed: {e}")))?; + let result = reader + .verify_integrity() + .map_err(|e| Error::Internal(format!("WAL verify failed: {e}")))?; + return Ok(Json(WalVerifyResponse { + valid: result.valid, + entries_checked: result.entries_checked, + first_invalid_seq: result.first_invalid_seq, + })); + } + + Ok(Json(WalVerifyResponse { + valid: true, + entries_checked: 0, + first_invalid_seq: None, + })) +} + +/// Create the cluster sub-router. +pub fn cluster_router() -> axum::Router { + use axum::routing::{get, post}; + + axum::Router::new() + .route("/api/v1/cluster/status", get(cluster_status)) + .route("/api/v1/cluster/join", post(cluster_join)) + .route("/api/v1/cluster/leave", post(cluster_leave)) + .route("/api/v1/cluster/members", get(cluster_members)) + .route("/api/v1/cluster/wal/stats", get(wal_stats)) + .route("/api/v1/cluster/wal/verify", post(wal_verify)) +} diff --git a/crates/aingle_cortex/src/rest/cluster_utils.rs b/crates/aingle_cortex/src/rest/cluster_utils.rs new file mode 100644 index 00000000..42aa9239 --- /dev/null +++ b/crates/aingle_cortex/src/rest/cluster_utils.rs @@ -0,0 +1,66 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Shared helpers for cluster-mode REST handlers. + +use axum::http::HeaderMap; +use crate::error::Error; +use crate::state::AppState; + +/// Convert a Raft `client_write` error into an appropriate HTTP error. +/// +/// If the error is `ForwardToLeader` with a known leader address, returns +/// `Error::Redirect` so the client gets a 307 with the leader's URL. +pub fn handle_raft_write_error( + e: openraft::error::RaftError< + aingle_raft::CortexTypeConfig, + openraft::error::ClientWriteError, + >, + _state: &AppState, +) -> Error { + use openraft::error::{ClientWriteError, RaftError}; + + match e { + RaftError::APIError(api_err) => match api_err { + ClientWriteError::ForwardToLeader(fwd) => { + if let Some(leader_node) = fwd.leader_node { + Error::Redirect(format!("http://{}", leader_node.rest_addr)) + } else { + Error::Internal("Not leader; leader unknown".to_string()) + } + } + ClientWriteError::ChangeMembershipError(e) => { + Error::Internal(format!("Membership change error: {e}")) + } + }, + RaftError::Fatal(f) => Error::Internal(format!("Raft fatal error: {f}")), + } +} + +/// Validate the `X-Cluster-Secret` header against the configured cluster secret. +/// +/// Returns `Ok(())` if the secret matches or if no secret is configured. +/// Returns `Err(Error::AuthError)` if the secret is missing or incorrect. +pub fn validate_cluster_auth(headers: &HeaderMap, state: &AppState) -> Result<(), Error> { + let expected = match &state.cluster_secret { + Some(s) if !s.is_empty() => s, + _ => return Ok(()), // No secret configured — allow all + }; + + let provided = headers + .get("x-cluster-secret") + .and_then(|v| v.to_str().ok()) + .unwrap_or(""); + + let expected_bytes = expected.as_bytes(); + let provided_bytes = provided.as_bytes(); + // Constant-time comparison to prevent timing side-channel attacks. + // Length check is not constant-time but doesn't leak the secret value. + if expected_bytes.len() != provided_bytes.len() + || subtle::ConstantTimeEq::ct_eq(expected_bytes, provided_bytes).unwrap_u8() != 1 + { + return Err(Error::AuthError("Invalid or missing cluster secret".into())); + } + + Ok(()) +} diff --git a/crates/aingle_cortex/src/rest/dag.rs b/crates/aingle_cortex/src/rest/dag.rs new file mode 100644 index 00000000..8d7ec161 --- /dev/null +++ b/crates/aingle_cortex/src/rest/dag.rs @@ -0,0 +1,729 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! DAG introspection REST endpoints. +//! +//! ## Endpoints +//! +//! - `GET /api/v1/dag/tips` — Current DAG tip hashes and count +//! - `GET /api/v1/dag/action/:hash` — Single DagAction by hash +//! - `GET /api/v1/dag/history` — Mutations affecting a subject +//! - `GET /api/v1/dag/chain` — Author's action chain +//! - `GET /api/v1/dag/stats` — Action count, tip count, depth estimate + +use axum::{ + extract::{Path, Query, State}, + routing::{get, post}, + Json, Router, +}; +use serde::{Deserialize, Serialize}; + +use crate::error::{Error, Result}; +use crate::state::AppState; + +// ============================================================================ +// DTOs +// ============================================================================ + +#[derive(Debug, Serialize)] +pub struct DagTipsResponse { + pub tips: Vec, + pub count: usize, +} + +#[derive(Debug, Serialize)] +pub struct DagActionDto { + pub hash: String, + pub parents: Vec, + pub author: String, + pub seq: u64, + pub timestamp: String, + pub payload_type: String, + pub payload_summary: String, + pub signed: bool, +} + +#[derive(Debug, Serialize)] +pub struct DagStatsResponse { + pub action_count: usize, + pub tip_count: usize, +} + +#[derive(Debug, Deserialize)] +pub struct HistoryQuery { + pub subject: Option, + pub triple_id: Option, + #[serde(default = "default_limit")] + pub limit: usize, +} + +#[derive(Debug, Deserialize)] +pub struct ChainQuery { + pub author: String, + #[serde(default = "default_limit")] + pub limit: usize, +} + +#[derive(Debug, Deserialize)] +pub struct PruneRequest { + /// "keep_all", "keep_since", "keep_last", or "keep_depth" + pub policy: String, + /// The numeric argument for the policy (seconds / count / depth). + #[serde(default)] + pub value: u64, + /// Whether to create a Compact checkpoint action after pruning. + #[serde(default)] + pub create_checkpoint: bool, +} + +#[derive(Debug, Serialize)] +pub struct PruneResponse { + pub pruned_count: usize, + pub retained_count: usize, + pub checkpoint_hash: Option, +} + +#[derive(Debug, Serialize)] +pub struct TimeTravelResponse { + pub target_hash: String, + pub target_timestamp: String, + pub actions_replayed: usize, + pub triple_count: usize, + pub triples: Vec, +} + +#[derive(Debug, Serialize)] +pub struct TimeTravelTriple { + pub subject: String, + pub predicate: String, + pub object: serde_json::Value, +} + +#[derive(Debug, Deserialize)] +pub struct DiffQuery { + pub from: String, + pub to: String, +} + +#[derive(Debug, Deserialize)] +pub struct PullRequest { + /// The peer URL to pull from (e.g. "http://node2:19090"). + pub peer_url: String, +} + +#[derive(Debug, Serialize)] +pub struct PullResponse { + pub ingested: usize, + pub already_had: usize, + pub remote_tips: Vec, +} + +#[derive(Debug, Serialize)] +pub struct DiffResponse { + pub from: String, + pub to: String, + pub action_count: usize, + pub actions: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct ExportQuery { + /// "dot", "mermaid", or "json" (default: "json"). + #[serde(default = "default_export_format")] + pub format: String, +} + +fn default_export_format() -> String { + "json".into() +} + +#[cfg(feature = "dag")] +#[derive(Debug, Deserialize)] +pub struct VerifyQuery { + /// Hex-encoded Ed25519 public key (64 chars). + pub public_key: String, +} + +/// Request body for POST /api/v1/dag/actions. +#[derive(Debug, Deserialize)] +pub struct CreateDagActionRequest { + /// Author identity. Defaults to the node's configured DAG author. + pub author: Option, + /// A descriptive type tag (e.g., "checkpoint", "decision", "annotation"). + pub payload_type: String, + /// A human-readable summary. + pub payload_summary: String, + /// Optional arbitrary payload data. + pub payload: Option, + /// Optional subject for indexing in DAG history. + pub subject: Option, + /// Whether to sign the action. Defaults to true if a signing key is configured. + pub sign: Option, +} + +/// Response for POST /api/v1/dag/actions. +#[derive(Debug, Serialize)] +pub struct CreateDagActionResponse { + pub hash: String, + pub seq: u64, + pub timestamp: String, + pub signed: bool, +} + +fn default_limit() -> usize { + 50 +} + +// ============================================================================ +// Handlers +// ============================================================================ + +/// GET /api/v1/dag/tips +pub async fn get_dag_tips(State(state): State) -> Result> { + let graph = state.graph.read().await; + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + let tips = dag_store.tips().map_err(|e| Error::Internal(e.to_string()))?; + let tip_strings: Vec = tips.iter().map(|h| h.to_hex()).collect(); + let count = tip_strings.len(); + + Ok(Json(DagTipsResponse { + tips: tip_strings, + count, + })) +} + +/// GET /api/v1/dag/action/:hash +pub async fn get_dag_action( + State(state): State, + Path(hash): Path, +) -> Result> { + let action_hash = aingle_graph::dag::DagActionHash::from_hex(&hash) + .ok_or_else(|| Error::InvalidInput(format!("Invalid DAG action hash: {}", hash)))?; + + let graph = state.graph.read().await; + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + let action = dag_store + .get(&action_hash) + .map_err(|e| Error::Internal(e.to_string()))? + .ok_or_else(|| Error::NotFound(format!("DAG action {} not found", hash)))?; + + Ok(Json(action_to_dto(&action))) +} + +/// GET /api/v1/dag/history?subject=X&triple_id=X&limit=N +pub async fn get_dag_history( + State(state): State, + Query(query): Query, +) -> Result>> { + let graph = state.graph.read().await; + + // Subject-based lookup uses the dedicated subject index + if let Some(ref subject) = query.subject { + let actions = graph + .dag_history_by_subject(subject, query.limit) + .map_err(|e| Error::Internal(e.to_string()))?; + return Ok(Json(actions.iter().map(action_to_dto).collect())); + } + + // Triple-ID-based lookup uses the affected index + if let Some(ref tid_hex) = query.triple_id { + let mut bytes = [0u8; 32]; + if tid_hex.len() != 64 { + return Err(Error::InvalidInput("triple_id must be 64 hex chars".into())); + } + for i in 0..32 { + bytes[i] = u8::from_str_radix(&tid_hex[i * 2..i * 2 + 2], 16) + .map_err(|_| Error::InvalidInput("Invalid hex in triple_id".into()))?; + } + + let actions = graph + .dag_history(&bytes, query.limit) + .map_err(|e| Error::Internal(e.to_string()))?; + return Ok(Json(actions.iter().map(action_to_dto).collect())); + } + + Err(Error::InvalidInput( + "Either 'subject' or 'triple_id' query parameter is required".into(), + )) +} + +/// GET /api/v1/dag/chain?author=X&limit=N +pub async fn get_dag_chain( + State(state): State, + Query(query): Query, +) -> Result>> { + let author = aingle_graph::NodeId::named(&query.author); + + let graph = state.graph.read().await; + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + let actions = dag_store + .chain(&author, query.limit) + .map_err(|e| Error::Internal(e.to_string()))?; + + Ok(Json(actions.iter().map(action_to_dto).collect())) +} + +/// GET /api/v1/dag/stats +pub async fn get_dag_stats(State(state): State) -> Result> { + let graph = state.graph.read().await; + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + let action_count = dag_store.action_count(); + let tip_count = dag_store.tip_count().map_err(|e| Error::Internal(e.to_string()))?; + + Ok(Json(DagStatsResponse { + action_count, + tip_count, + })) +} + +/// POST /api/v1/dag/prune +pub async fn post_dag_prune( + State(state): State, + Json(req): Json, +) -> Result> { + let policy = match req.policy.as_str() { + "keep_all" => aingle_graph::dag::RetentionPolicy::KeepAll, + "keep_since" => aingle_graph::dag::RetentionPolicy::KeepSince { seconds: req.value }, + "keep_last" => aingle_graph::dag::RetentionPolicy::KeepLast(req.value as usize), + "keep_depth" => aingle_graph::dag::RetentionPolicy::KeepDepth(req.value as usize), + other => return Err(Error::InvalidInput(format!("Unknown policy: {}", other))), + }; + + let graph = state.graph.read().await; + let result = graph + .dag_prune(&policy, req.create_checkpoint) + .map_err(|e| Error::Internal(e.to_string()))?; + + Ok(Json(PruneResponse { + pruned_count: result.pruned_count, + retained_count: result.retained_count, + checkpoint_hash: result.checkpoint_hash.map(|h| h.to_hex()), + })) +} + +/// GET /api/v1/dag/export?format=dot|mermaid|json +pub async fn get_dag_export( + State(state): State, + Query(query): Query, +) -> Result { + use axum::response::IntoResponse; + + let format = aingle_graph::dag::ExportFormat::from_str(&query.format).ok_or_else(|| { + Error::InvalidInput(format!( + "Unknown format '{}'. Use: dot, mermaid, json", + query.format + )) + })?; + + let graph = state.graph.read().await; + let dag_graph = graph + .dag_export() + .map_err(|e| Error::Internal(e.to_string()))?; + + let body = dag_graph + .export(format) + .map_err(|e| Error::Internal(e.to_string()))?; + + let content_type = match format { + aingle_graph::dag::ExportFormat::Dot => "text/vnd.graphviz", + aingle_graph::dag::ExportFormat::Mermaid => "text/plain", + aingle_graph::dag::ExportFormat::Json => "application/json", + }; + + Ok(([(axum::http::header::CONTENT_TYPE, content_type)], body).into_response()) +} + +/// GET /api/v1/dag/verify/:hash?public_key=X — verify an action's Ed25519 signature +#[cfg(feature = "dag")] +pub async fn get_dag_verify( + State(state): State, + Path(hash): Path, + Query(query): Query, +) -> Result> { + let action_hash = aingle_graph::dag::DagActionHash::from_hex(&hash) + .ok_or_else(|| Error::InvalidInput(format!("Invalid hash: {}", hash)))?; + + let mut pk_bytes = [0u8; 32]; + if query.public_key.len() != 64 { + return Err(Error::InvalidInput("public_key must be 64 hex chars".into())); + } + for i in 0..32 { + pk_bytes[i] = u8::from_str_radix(&query.public_key[i * 2..i * 2 + 2], 16) + .map_err(|_| Error::InvalidInput("Invalid hex in public_key".into()))?; + } + + let graph = state.graph.read().await; + let action = graph + .dag_action(&action_hash) + .map_err(|e| Error::Internal(e.to_string()))? + .ok_or_else(|| Error::NotFound(format!("DAG action {} not found", hash)))?; + + let result = graph + .dag_verify(&action, &pk_bytes) + .map_err(|e| Error::Internal(e.to_string()))?; + + Ok(Json(result)) +} + +/// POST /api/v1/dag/sync — serve missing actions to a peer +pub async fn post_dag_sync( + State(state): State, + Json(req): Json, +) -> Result> { + let graph = state.graph.read().await; + + let actions = if !req.want.is_empty() { + // Serve specific requested actions + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + req.want + .iter() + .filter_map(|h| dag_store.get(h).ok().flatten()) + .collect() + } else { + // Compute what the requester is missing + graph + .dag_compute_missing(&req.local_tips) + .map_err(|e| Error::Internal(e.to_string()))? + }; + + let tips = graph + .dag_tips() + .map_err(|e| Error::Internal(e.to_string()))?; + + let action_count = actions.len(); + + Ok(Json(aingle_graph::dag::SyncResponse { + actions, + remote_tips: tips, + action_count, + })) +} + +/// POST /api/v1/dag/sync/pull — pull missing DAG actions from a peer +pub async fn post_dag_pull( + State(state): State, + Json(req): Json, +) -> Result> { + // Read our current tips + let local_tips = { + let graph = state.graph.read().await; + graph + .dag_tips() + .map_err(|e| Error::Internal(e.to_string()))? + }; + + // Send sync request to peer + let sync_req = aingle_graph::dag::SyncRequest { + local_tips, + want: vec![], + }; + + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(30)) + .build() + .map_err(|e| Error::Internal(format!("HTTP client error: {}", e)))?; + + let url = format!("{}/api/v1/dag/sync", req.peer_url.trim_end_matches('/')); + let resp = client + .post(&url) + .json(&sync_req) + .send() + .await + .map_err(|e| Error::Internal(format!("Failed to contact peer: {}", e)))?; + + if !resp.status().is_success() { + return Err(Error::Internal(format!( + "Peer returned status {}", + resp.status() + ))); + } + + let sync_resp: aingle_graph::dag::SyncResponse = resp + .json() + .await + .map_err(|e| Error::Internal(format!("Invalid peer response: {}", e)))?; + + // Ingest received actions + let graph = state.graph.read().await; + let mut ingested = 0; + let mut already_had = 0; + + for action in &sync_resp.actions { + let hash = action.compute_hash(); + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + if dag_store.contains(&hash).map_err(|e| Error::Internal(e.to_string()))? { + already_had += 1; + } else { + graph + .dag_ingest(action) + .map_err(|e| Error::Internal(e.to_string()))?; + ingested += 1; + } + } + + Ok(Json(PullResponse { + ingested, + already_had, + remote_tips: sync_resp.remote_tips.iter().map(|h| h.to_hex()).collect(), + })) +} + +/// GET /api/v1/dag/at/:hash — reconstruct graph state at a specific DAG action +pub async fn get_dag_at( + State(state): State, + Path(hash): Path, +) -> Result> { + let action_hash = aingle_graph::dag::DagActionHash::from_hex(&hash) + .ok_or_else(|| Error::InvalidInput(format!("Invalid DAG action hash: {}", hash)))?; + + let graph = state.graph.read().await; + let (snapshot_db, info) = graph + .dag_at(&action_hash) + .map_err(|e| Error::Internal(e.to_string()))?; + + let triples = snapshot_db + .find(aingle_graph::TriplePattern::any()) + .map_err(|e| Error::Internal(e.to_string()))? + .into_iter() + .map(|t| TimeTravelTriple { + subject: t.subject.to_string(), + predicate: t.predicate.to_string(), + object: triple_value_to_json(&t.object), + }) + .collect(); + + Ok(Json(TimeTravelResponse { + target_hash: info.target_hash.to_hex(), + target_timestamp: info.target_timestamp.to_rfc3339(), + actions_replayed: info.actions_replayed, + triple_count: info.triple_count, + triples, + })) +} + +/// GET /api/v1/dag/diff?from=X&to=Y — actions between two DAG points +pub async fn get_dag_diff( + State(state): State, + Query(query): Query, +) -> Result> { + let from = aingle_graph::dag::DagActionHash::from_hex(&query.from) + .ok_or_else(|| Error::InvalidInput(format!("Invalid 'from' hash: {}", query.from)))?; + let to = aingle_graph::dag::DagActionHash::from_hex(&query.to) + .ok_or_else(|| Error::InvalidInput(format!("Invalid 'to' hash: {}", query.to)))?; + + let graph = state.graph.read().await; + let diff = graph + .dag_diff(&from, &to) + .map_err(|e| Error::Internal(e.to_string()))?; + + let actions: Vec = diff.actions.iter().map(action_to_dto).collect(); + let action_count = actions.len(); + + Ok(Json(DiffResponse { + from: query.from, + to: query.to, + action_count, + actions, + })) +} + +/// POST /api/v1/dag/actions — create an explicit DAG action with arbitrary payload +pub async fn post_create_dag_action( + State(state): State, + Json(req): Json, +) -> Result<(axum::http::StatusCode, Json)> { + if req.payload_type.is_empty() { + return Err(Error::InvalidInput("payload_type cannot be empty".into())); + } + + let dag_author = if let Some(ref author) = req.author { + aingle_graph::NodeId::named(author) + } else { + state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")) + }; + + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + let graph = state.graph.read().await; + let dag_store = graph + .dag_store() + .ok_or_else(|| Error::Internal("DAG not enabled".into()))?; + + let parents = dag_store.tips().map_err(|e| Error::Internal(e.to_string()))?; + + let timestamp = chrono::Utc::now(); + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp, + payload: aingle_graph::dag::DagPayload::Custom { + payload_type: req.payload_type, + payload_summary: req.payload_summary, + payload: req.payload, + subject: req.subject, + }, + signature: None, + }; + + // Sign unless explicitly disabled + let should_sign = req.sign.unwrap_or(true); + if should_sign { + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + } + + let signed = action.signature.is_some(); + let hash = dag_store + .put(&action) + .map_err(|e| Error::Internal(e.to_string()))?; + + Ok(( + axum::http::StatusCode::CREATED, + Json(CreateDagActionResponse { + hash: hash.to_hex(), + seq: dag_seq, + timestamp: timestamp.to_rfc3339(), + signed, + }), + )) +} + +// ============================================================================ +// Router +// ============================================================================ + +pub fn dag_router() -> Router { + let router = Router::new() + .route("/api/v1/dag/tips", get(get_dag_tips)) + .route("/api/v1/dag/action/{hash}", get(get_dag_action)) + .route("/api/v1/dag/history", get(get_dag_history)) + .route("/api/v1/dag/chain", get(get_dag_chain)) + .route("/api/v1/dag/stats", get(get_dag_stats)) + .route("/api/v1/dag/prune", post(post_dag_prune)) + .route("/api/v1/dag/at/{hash}", get(get_dag_at)) + .route("/api/v1/dag/diff", get(get_dag_diff)) + .route("/api/v1/dag/export", get(get_dag_export)) + .route("/api/v1/dag/sync", post(post_dag_sync)) + .route("/api/v1/dag/sync/pull", post(post_dag_pull)) + .route("/api/v1/dag/actions", post(post_create_dag_action)); + + #[cfg(feature = "dag")] + let router = router.route("/api/v1/dag/verify/{hash}", get(get_dag_verify)); + + router +} + +// ============================================================================ +// Helpers +// ============================================================================ + +fn action_to_dto(action: &aingle_graph::dag::DagAction) -> DagActionDto { + let hash = action.compute_hash().to_hex(); + let parents: Vec = action.parents.iter().map(|h| h.to_hex()).collect(); + + let (payload_type, payload_summary) = match &action.payload { + aingle_graph::dag::DagPayload::TripleInsert { triples } => { + let summary = if triples.len() == 1 { + let t = &triples[0]; + format!("{} -> {} -> {}", t.subject, t.predicate, t.object) + } else { + format!("{} triple(s)", triples.len()) + }; + ("triple:create".to_string(), summary) + } + aingle_graph::dag::DagPayload::TripleDelete { triple_ids, subjects } => { + let summary = if !subjects.is_empty() { + format!("{} triple(s) [{}]", triple_ids.len(), subjects.join(", ")) + } else { + format!("{} triple(s)", triple_ids.len()) + }; + ("triple:delete".to_string(), summary) + } + aingle_graph::dag::DagPayload::MemoryOp { kind } => { + let summary = match kind { + aingle_graph::dag::MemoryOpKind::Store { entry_type, .. } => { + format!("Store({})", entry_type) + } + aingle_graph::dag::MemoryOpKind::Forget { memory_id } => { + format!("Forget({})", memory_id) + } + aingle_graph::dag::MemoryOpKind::Consolidate => "Consolidate".to_string(), + }; + ("memory:op".to_string(), summary) + } + aingle_graph::dag::DagPayload::Batch { ops } => ( + "batch".to_string(), + format!("{} ops", ops.len()), + ), + aingle_graph::dag::DagPayload::Genesis { + triple_count, + description, + } => ( + "genesis".to_string(), + format!("{} triples: {}", triple_count, description), + ), + aingle_graph::dag::DagPayload::Compact { + pruned_count, + retained_count, + ref policy, + } => ( + "compact".to_string(), + format!("pruned {} / retained {} ({})", pruned_count, retained_count, policy), + ), + aingle_graph::dag::DagPayload::Noop => ("noop".to_string(), String::new()), + aingle_graph::dag::DagPayload::Custom { + payload_type, + payload_summary, + .. + } => (payload_type.clone(), payload_summary.clone()), + }; + + DagActionDto { + hash, + parents, + author: action.author.to_string(), + seq: action.seq, + timestamp: action.timestamp.to_rfc3339(), + payload_type, + payload_summary, + signed: action.signature.is_some(), + } +} + +fn triple_value_to_json(v: &aingle_graph::Value) -> serde_json::Value { + match v { + aingle_graph::Value::String(s) => serde_json::Value::String(s.clone()), + aingle_graph::Value::Integer(i) => serde_json::json!(*i), + aingle_graph::Value::Float(f) => serde_json::json!(*f), + aingle_graph::Value::Boolean(b) => serde_json::json!(*b), + aingle_graph::Value::Json(j) => j.clone(), + aingle_graph::Value::Node(n) => serde_json::json!({ "node": n.to_string() }), + aingle_graph::Value::DateTime(dt) => serde_json::Value::String(dt.clone()), + aingle_graph::Value::Null => serde_json::Value::Null, + _ => serde_json::Value::String(format!("{:?}", v)), + } +} diff --git a/crates/aingle_cortex/src/rest/memory.rs b/crates/aingle_cortex/src/rest/memory.rs index e3090ffe..a59e92cb 100644 --- a/crates/aingle_cortex/src/rest/memory.rs +++ b/crates/aingle_cortex/src/rest/memory.rs @@ -1,4 +1,7 @@ -//! REST endpoints for the Titans Memory subsystem. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! REST endpoints for the Ineru memory subsystem. //! //! These endpoints expose the STM/LTM dual-memory architecture through //! the Cortex REST API, allowing agents to store, recall, consolidate, @@ -21,7 +24,7 @@ use axum::{ Json, }; use serde::{Deserialize, Serialize}; -use titans_memory::{MemoryEntry, MemoryId, MemoryQuery}; +use ineru::{MemoryEntry, MemoryId, MemoryQuery}; use crate::error::{Error, Result}; use crate::state::AppState; @@ -117,6 +120,52 @@ pub async fn remember( State(state): State, Json(req): Json, ) -> Result<(StatusCode, Json)> { + // Cluster mode: route through Raft + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::MemoryStore { + memory_id: String::new(), // assigned by state machine + entry_type: req.entry_type.clone(), + data: req.data.clone(), + importance: req.importance, + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft memory store failed".to_string()), + )); + } + + let id = resp + .response() + .id + .clone() + .unwrap_or_else(|| "raft".to_string()); + + return Ok(( + StatusCode::CREATED, + Json(RememberResponse { id }), + )); + } + + // Guard: if Raft is initialized, all writes MUST go through Raft (#2). + #[cfg(feature = "cluster")] + if state.raft.is_some() { + return Err(Error::Internal("Raft initialized but write not routed through Raft".into())); + } + + // Non-cluster mode: direct write + #[cfg(feature = "cluster")] + let wal_data = req.data.clone(); let mut entry = MemoryEntry::new(&req.entry_type, req.data); if !req.tags.is_empty() { @@ -127,7 +176,7 @@ pub async fn remember( entry = entry.with_importance(req.importance); if let Some(emb) = req.embedding { - entry = entry.with_embedding(titans_memory::Embedding::new(emb)); + entry = entry.with_embedding(ineru::Embedding::new(emb)); } let mut memory = state.memory.write().await; @@ -135,6 +184,17 @@ pub async fn remember( .remember(entry) .map_err(|e| Error::Internal(format!("Memory store failed: {e}")))?; + // Append to WAL (legacy cluster path) + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + wal.append(aingle_wal::WalEntryKind::MemoryStore { + memory_id: id.to_hex(), + entry_type: req.entry_type.clone(), + data: wal_data.clone(), + importance: req.importance, + }).map_err(|e| Error::Internal(format!("WAL append failed: {e}")))?; + } + Ok(( StatusCode::CREATED, Json(RememberResponse { @@ -164,8 +224,8 @@ pub async fn recall( importance: r.entry.metadata.importance, relevance: r.relevance, source: match r.source { - titans_memory::types::MemorySource::ShortTerm => "ShortTerm".to_string(), - titans_memory::types::MemorySource::LongTerm => "LongTerm".to_string(), + ineru::types::MemorySource::ShortTerm => "ShortTerm".to_string(), + ineru::types::MemorySource::LongTerm => "LongTerm".to_string(), }, created_at: r.entry.metadata.created_at.0.to_string(), last_accessed: r.entry.metadata.last_accessed.0.to_string(), @@ -180,11 +240,61 @@ pub async fn recall( pub async fn consolidate( State(state): State, ) -> Result> { + // Cluster mode: route through Raft so all nodes consolidate deterministically + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::MemoryConsolidate { + consolidated_count: 0, // state machine will compute real count + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft consolidate failed".to_string()), + )); + } + + // The detail field contains the consolidated count from state machine + let count: usize = resp + .response() + .detail + .as_ref() + .and_then(|d| d.parse().ok()) + .unwrap_or(0); + + return Ok(Json(ConsolidateResponse { + consolidated: count, + })); + } + + // Guard: if Raft is initialized, all writes MUST go through Raft (#2). + #[cfg(feature = "cluster")] + if state.raft.is_some() { + return Err(Error::Internal("Raft initialized but write not routed through Raft".into())); + } + + // Non-cluster mode: direct consolidation let mut memory = state.memory.write().await; let count = memory .consolidate() .map_err(|e| Error::Internal(format!("Consolidation failed: {e}")))?; + // Append to WAL (legacy cluster path) + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + wal.append(aingle_wal::WalEntryKind::MemoryConsolidate { + consolidated_count: count, + }).map_err(|e| Error::Internal(format!("WAL append failed: {e}")))?; + } + Ok(Json(ConsolidateResponse { consolidated: count, })) @@ -209,6 +319,38 @@ pub async fn forget( State(state): State, Path(id): Path, ) -> Result { + // Cluster mode: route through Raft + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::MemoryForget { + memory_id: id.clone(), + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft forget failed".to_string()), + )); + } + + return Ok(StatusCode::NO_CONTENT); + } + + // Guard: if Raft is initialized, all writes MUST go through Raft (#2). + #[cfg(feature = "cluster")] + if state.raft.is_some() { + return Err(Error::Internal("Raft initialized but write not routed through Raft".into())); + } + + // Non-cluster mode: direct delete let memory_id = MemoryId::from_hex(&id) .ok_or_else(|| Error::InvalidInput(format!("Invalid memory ID: {id}")))?; @@ -217,6 +359,14 @@ pub async fn forget( .forget(&memory_id) .map_err(|e| Error::NotFound(format!("Memory not found: {e}")))?; + // Append to WAL (legacy cluster path) + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + wal.append(aingle_wal::WalEntryKind::MemoryForget { + memory_id: id.clone(), + }).map_err(|e| Error::Internal(format!("WAL append failed: {e}")))?; + } + Ok(StatusCode::NO_CONTENT) } @@ -325,10 +475,112 @@ pub async fn restore_checkpoint( Ok(StatusCode::OK) } +// ============================================================================ +// Vector Search (Motomeru) +// ============================================================================ + +#[derive(Debug, Deserialize)] +pub struct VectorSearchRequest { + pub embedding: Vec, + pub k: usize, + #[serde(default = "default_min_similarity")] + pub min_similarity: f32, + pub entry_type: Option, + pub tags: Option>, +} + +fn default_min_similarity() -> f32 { + 0.0 +} + +#[derive(Debug, Serialize)] +pub struct VectorIndexStatsDto { + pub point_count: usize, + pub deleted_count: usize, + pub dimensions: usize, + pub memory_bytes: usize, +} + +/// Vector search over memory entries using HNSW index. +pub async fn vector_search( + State(state): State, + Json(req): Json, +) -> Result>> { + let memory = state.memory.read().await; + let results = memory.ltm.vector_search_memories(&req.embedding, req.k, req.min_similarity); + + let mut dtos: Vec = results + .into_iter() + .map(|(entry, similarity)| MemoryResultDto { + id: entry.id.to_hex(), + entry_type: entry.entry_type.clone(), + data: entry.data.clone(), + tags: entry.tags.iter().map(|t| t.0.clone()).collect(), + importance: entry.metadata.importance, + relevance: similarity, + source: "LongTerm".to_string(), + created_at: entry.metadata.created_at.0.to_string(), + last_accessed: entry.metadata.last_accessed.0.to_string(), + access_count: entry.metadata.access_count, + }) + .collect(); + + // Apply optional filters + if let Some(ref entry_type) = req.entry_type { + dtos.retain(|d| &d.entry_type == entry_type); + } + if let Some(ref tags) = req.tags { + if !tags.is_empty() { + dtos.retain(|d| tags.iter().any(|t| d.tags.contains(t))); + } + } + + Ok(Json(dtos)) +} + +/// Get HNSW vector index statistics. +pub async fn vector_index_stats( + State(state): State, +) -> Result> { + let memory = state.memory.read().await; + let stats = memory.ltm.hnsw_index() + .map(|idx| idx.stats()) + .unwrap_or(ineru::hnsw::HnswStats { + point_count: 0, + deleted_count: 0, + dimensions: 0, + max_layer: 0, + memory_bytes: 0, + }); + + Ok(Json(VectorIndexStatsDto { + point_count: stats.point_count, + deleted_count: stats.deleted_count, + dimensions: stats.dimensions, + memory_bytes: stats.memory_bytes, + })) +} + +/// Force rebuild of the HNSW vector index. +pub async fn rebuild_vector_index( + State(state): State, +) -> Result { + let mut memory = state.memory.write().await; + if let Some(hnsw) = memory.ltm.hnsw_index_mut() { + hnsw.rebuild(); + tracing::info!("HNSW index rebuilt, {} active points", hnsw.len()); + } + Ok(StatusCode::OK) +} + // ============================================================================ // Helpers // ============================================================================ +/// Re-export shared Raft write error handler for this module. +#[cfg(feature = "cluster")] +use crate::rest::cluster_utils::handle_raft_write_error; + fn build_query(req: &RecallRequest) -> MemoryQuery { let mut query = if let Some(text) = &req.text { MemoryQuery::text(text) @@ -369,8 +621,12 @@ pub fn memory_router() -> axum::Router { .route("/api/v1/memory/recall", post(recall)) .route("/api/v1/memory/consolidate", post(consolidate)) .route("/api/v1/memory/stats", get(stats)) - .route("/api/v1/memory/:id", delete(forget)) + .route("/api/v1/memory/{id}", delete(forget)) .route("/api/v1/memory/checkpoint", post(checkpoint)) .route("/api/v1/memory/checkpoints", get(list_checkpoints)) - .route("/api/v1/memory/restore/:id", post(restore_checkpoint)) + .route("/api/v1/memory/restore/{id}", post(restore_checkpoint)) + // Motomeru: HNSW vector search endpoints + .route("/api/v1/memory/search", post(vector_search)) + .route("/api/v1/memory/index/stats", get(vector_index_stats)) + .route("/api/v1/memory/index/rebuild", post(rebuild_vector_index)) } diff --git a/crates/aingle_cortex/src/rest/mod.rs b/crates/aingle_cortex/src/rest/mod.rs index f4493d07..36ab0339 100644 --- a/crates/aingle_cortex/src/rest/mod.rs +++ b/crates/aingle_cortex/src/rest/mod.rs @@ -1,9 +1,13 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! REST API endpoints for Córtex //! //! ## Endpoints //! //! ### Triples //! - `POST /api/v1/triples` - Create triple +//! - `POST /api/v1/triples/batch` - Batch insert triples (atomic) //! - `GET /api/v1/triples/:id` - Get triple by hash //! - `DELETE /api/v1/triples/:id` - Delete triple //! - `GET /api/v1/triples` - List triples (with filters) @@ -16,12 +20,34 @@ //! - `POST /api/v1/validate` - Validate triple(s) //! - `GET /api/v1/proof/:hash` - Get proof //! - `POST /api/v1/verify` - Verify proof +//! +//! ### Skill Verification (Phase 3) +//! - `POST /api/v1/skills/validate` - Validate semantic skill manifest +//! - `POST /api/v1/skills/sandbox` - Create temporary sandbox namespace +//! - `DELETE /api/v1/skills/sandbox/:id` - Clean up sandbox namespace +//! +//! ### Reputation (Phase 3) +//! - `GET /api/v1/agents/:id/consistency` - Agent assertion consistency score +//! - `POST /api/v1/assertions/verify-batch` - Batch verify assertion proofs +pub mod audit; +#[cfg(feature = "cluster")] +pub mod cluster; +#[cfg(feature = "cluster")] +pub(crate) mod cluster_utils; +#[cfg(feature = "cluster")] +pub mod raft_rpc; +#[cfg(feature = "dag")] +pub mod dag; mod memory; mod observability; +#[cfg(feature = "p2p")] +mod p2p; mod proof; mod proof_api; mod query; +mod reputation; +mod skill_verification; mod stats; mod triples; @@ -51,22 +77,27 @@ use axum::{ /// Create REST API router pub fn router() -> Router { - Router::new() + let router = Router::new() // Triple CRUD .route("/api/v1/triples", post(triples::create_triple)) .route("/api/v1/triples", get(triples::list_triples)) - .route("/api/v1/triples/:id", get(triples::get_triple)) - .route("/api/v1/triples/:id", delete(triples::delete_triple)) + .route( + "/api/v1/triples/batch", + post(triples::batch_insert_triples), + ) + .route("/api/v1/triples/{id}", get(triples::get_triple)) + .route("/api/v1/triples/{id}", delete(triples::delete_triple)) // Query endpoints .route("/api/v1/query", post(query::query_pattern)) .route("/api/v1/query/subjects", get(query::list_subjects)) .route("/api/v1/query/predicates", get(query::list_predicates)) - // Stats + // Stats & Management .route("/api/v1/stats", get(stats::get_stats)) .route("/api/v1/health", get(stats::health_check)) + .route("/api/v1/flush", post(stats::flush_data)) // Validation/Proofs (legacy) .route("/api/v1/validate", post(proof::validate_triples)) - .route("/api/v1/proof/:hash", get(proof::get_proof)) + .route("/api/v1/proof/{hash}", get(proof::get_proof)) .route("/api/v1/verify", post(proof::verify_proof)) // ZK Proof API (new proof storage system) .route("/api/v1/proofs", post(proof_api::submit_proof)) @@ -77,14 +108,36 @@ pub fn router() -> Router { "/api/v1/proofs/verify/batch", post(proof_api::verify_proofs_batch), ) - .route("/api/v1/proofs/:id", get(proof_api::get_proof)) - .route("/api/v1/proofs/:id", delete(proof_api::delete_proof)) + .route("/api/v1/proofs/{id}", get(proof_api::get_proof)) + .route("/api/v1/proofs/{id}", delete(proof_api::delete_proof)) .route( - "/api/v1/proofs/:id/verify", + "/api/v1/proofs/{id}/verify", get(proof_api::verify_proof_by_id), ) - // Titans Memory endpoints + // Ineru memory endpoints .merge(memory::memory_router()) // Semantic Observability endpoints .merge(observability::observability_router()) + // Skill Verification endpoints (Phase 3) + .merge(skill_verification::skill_verification_router()) + // Reputation endpoints (Phase 3) + .merge(reputation::reputation_router()) + // Audit log endpoints (Phase 6.5) + .merge(audit::audit_router()); + + // P2P endpoints (feature-gated) + #[cfg(feature = "p2p")] + let router = router.merge(p2p::p2p_router()); + + // Cluster endpoints (feature-gated) + #[cfg(feature = "cluster")] + let router = router + .merge(cluster::cluster_router()) + .merge(raft_rpc::raft_rpc_router()); + + // DAG endpoints (feature-gated) + #[cfg(feature = "dag")] + let router = router.merge(dag::dag_router()); + + router } diff --git a/crates/aingle_cortex/src/rest/observability.rs b/crates/aingle_cortex/src/rest/observability.rs index d5a32597..e9493afb 100644 --- a/crates/aingle_cortex/src/rest/observability.rs +++ b/crates/aingle_cortex/src/rest/observability.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Observability REST endpoints for semantic tracing. //! //! These endpoints provide a thin layer over the triple API, scoped to @@ -438,7 +441,7 @@ pub fn observability_router() -> axum::Router { .route("/api/v1/events", axum::routing::post(batch_store_events)) .route("/api/v1/events", axum::routing::get(query_events)) .route( - "/api/v1/events/:id/chain", + "/api/v1/events/{id}/chain", axum::routing::get(get_causal_chain), ) } diff --git a/crates/aingle_cortex/src/rest/p2p.rs b/crates/aingle_cortex/src/rest/p2p.rs new file mode 100644 index 00000000..28352e12 --- /dev/null +++ b/crates/aingle_cortex/src/rest/p2p.rs @@ -0,0 +1,157 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! REST endpoints for P2P status and peer management. + +use crate::state::AppState; +use axum::{ + extract::State, + http::StatusCode, + response::IntoResponse, + routing::{delete, get}, + Json, Router, +}; +use serde::Deserialize; + +/// Mount P2P routes. +pub fn p2p_router() -> Router { + Router::new() + .route("/api/v1/p2p/status", get(p2p_status)) + .route("/api/v1/p2p/peers", get(list_peers).post(add_peer)) + .route("/api/v1/p2p/peers/{node_id}", delete(remove_peer)) +} + +#[derive(Deserialize)] +struct AddPeerRequest { + addr: String, +} + +async fn p2p_status(State(state): State) -> impl IntoResponse { + let p2p = match &state.p2p { + Some(mgr) => mgr, + None => { + return ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "P2P not enabled"})), + ) + .into_response() + } + }; + let status = p2p.status().await; + match serde_json::to_value(status) { + Ok(v) => (StatusCode::OK, Json(v)).into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": format!("serialize p2p status: {e}")})), + ).into_response(), + } +} + +async fn list_peers(State(state): State) -> impl IntoResponse { + let p2p = match &state.p2p { + Some(mgr) => mgr, + None => { + return ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "P2P not enabled"})), + ) + .into_response() + } + }; + let status = p2p.status().await; + match serde_json::to_value(&status.connected_peers) { + Ok(v) => (StatusCode::OK, Json(v)).into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": format!("serialize peers: {e}")})), + ).into_response(), + } +} + +async fn add_peer( + State(state): State, + Json(body): Json, +) -> impl IntoResponse { + let p2p = match &state.p2p { + Some(mgr) => mgr, + None => { + return ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "P2P not enabled"})), + ) + .into_response() + } + }; + + let addr = match body.addr.parse() { + Ok(a) => a, + Err(_) => { + return ( + StatusCode::BAD_REQUEST, + Json(serde_json::json!({"error": "invalid address"})), + ) + .into_response() + } + }; + + match p2p.add_peer(addr).await { + Ok(()) => ( + StatusCode::OK, + Json(serde_json::json!({"status": "connected", "addr": body.addr})), + ) + .into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": e})), + ) + .into_response(), + } +} + +async fn remove_peer( + State(state): State, + axum::extract::Path(node_id): axum::extract::Path, +) -> impl IntoResponse { + let p2p = match &state.p2p { + Some(mgr) => mgr, + None => { + return ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "P2P not enabled"})), + ) + .into_response() + } + }; + + // Find the peer address by matching node_id prefix in connected peers. + let status = p2p.status().await; + let peer_addr = status + .connected_peers + .iter() + .find(|p| p.addr.contains(&node_id)) + .map(|p| p.addr.clone()); + + match peer_addr { + Some(addr_str) => { + if let Ok(addr) = addr_str.parse() { + p2p.remove_peer(addr).await; + ( + StatusCode::OK, + Json(serde_json::json!({"status": "disconnected"})), + ) + .into_response() + } else { + ( + StatusCode::INTERNAL_SERVER_ERROR, + Json(serde_json::json!({"error": "address parse error"})), + ) + .into_response() + } + } + None => ( + StatusCode::NOT_FOUND, + Json(serde_json::json!({"error": "peer not found"})), + ) + .into_response(), + } +} diff --git a/crates/aingle_cortex/src/rest/proof.rs b/crates/aingle_cortex/src/rest/proof.rs index 3ab6ad33..3dbfb906 100644 --- a/crates/aingle_cortex/src/rest/proof.rs +++ b/crates/aingle_cortex/src/rest/proof.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Proof validation endpoints use axum::{ @@ -7,6 +10,7 @@ use axum::{ use serde::{Deserialize, Serialize}; use crate::error::{Error, Result}; +use crate::middleware::{is_in_namespace, RequestNamespace}; use crate::rest::triples::{TripleDto, ValueDto}; use crate::state::{AppState, Event}; use aingle_graph::{NodeId, Predicate, Triple, Value}; @@ -66,14 +70,27 @@ pub struct ValidationMessage { /// POST /api/v1/validate pub async fn validate_triples( State(state): State, + ns_ext: Option>, Json(req): Json, ) -> Result> { let logic = state.logic.read().await; + // Extract namespace if present + let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns); + let mut results = Vec::new(); let mut all_valid = true; for input in req.triples { + // Enforce namespace on input subjects + if let Some(ref ns) = ns_filter { + if !is_in_namespace(&input.subject, ns) { + return Err(Error::Forbidden(format!( + "Subject \"{}\" is not in namespace \"{}\"", + input.subject, ns + ))); + } + } let object: Value = input.object.clone().into(); // Create a triple for validation diff --git a/crates/aingle_cortex/src/rest/proof_api.rs b/crates/aingle_cortex/src/rest/proof_api.rs index a7f9a14c..73cf56b8 100644 --- a/crates/aingle_cortex/src/rest/proof_api.rs +++ b/crates/aingle_cortex/src/rest/proof_api.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! REST API endpoints for proof storage and verification use axum::{ @@ -7,6 +10,7 @@ use axum::{ use serde::{Deserialize, Serialize}; use crate::error::{Error, Result}; +use crate::middleware::{is_in_namespace, RequestNamespace}; use crate::proofs::{ProofId, ProofMetadata, ProofType, StoredProof, SubmitProofRequest}; use crate::state::AppState; @@ -15,8 +19,23 @@ use crate::state::AppState; /// POST /api/v1/proofs pub async fn submit_proof( State(state): State, + ns_ext: Option>, Json(request): Json, ) -> Result> { + // Enforce namespace: submitter must belong to the namespace + if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext { + if let Some(ref meta) = request.metadata { + if let Some(ref submitter) = meta.submitter { + if !is_in_namespace(submitter, ns) { + return Err(Error::Forbidden(format!( + "Submitter \"{}\" is not in namespace \"{}\"", + submitter, ns + ))); + } + } + } + } + let proof_id = state .proof_store .submit(request) @@ -88,19 +107,29 @@ pub async fn verify_proof_by_id( State(state): State, Path(proof_id): Path, ) -> Result> { - let result = state - .proof_store - .verify(&proof_id) - .await - .map_err(|e| Error::ValidationError(e.to_string()))?; - - Ok(Json(VerifyProofResponse { - proof_id: proof_id.clone(), - valid: result.valid, - verified_at: result.verified_at, - details: result.details, - verification_time_us: result.verification_time_us, - })) + match state.proof_store.verify(&proof_id).await { + Ok(result) => Ok(Json(VerifyProofResponse { + proof_id: proof_id.clone(), + valid: result.valid, + verified_at: result.verified_at, + details: result.details, + verification_time_us: result.verification_time_us, + })), + Err(crate::proofs::VerificationError::ProofNotFound(_)) => { + Err(Error::NotFound(format!("Proof {} not found", proof_id))) + } + Err(e) => { + // Verification infrastructure error (bad proof data format, ZK error, etc.) + // Return 200 with valid=false + error details instead of 422 + Ok(Json(VerifyProofResponse { + proof_id: proof_id.clone(), + valid: false, + verified_at: chrono::Utc::now(), + details: vec![format!("Verification error: {}", e)], + verification_time_us: 0, + })) + } + } } /// Batch verify multiple proofs @@ -113,8 +142,7 @@ pub async fn verify_proofs_batch( let results = state.proof_store.batch_verify(&request.proof_ids).await; let mut verifications = Vec::new(); - for (idx, result) in results.into_iter().enumerate() { - let proof_id = &request.proof_ids[idx]; + for (proof_id, result) in request.proof_ids.iter().zip(results.into_iter()) { match result { Ok(verification) => { verifications.push(VerifyProofResponse { @@ -152,12 +180,24 @@ pub async fn verify_proofs_batch( /// GET /api/v1/proofs pub async fn list_proofs( State(state): State, + ns_ext: Option>, Query(params): Query, ) -> Result> { let proofs = state.proof_store.list(params.proof_type).await; let mut filtered_proofs = proofs; + // Filter by namespace: only show proofs whose submitter is in the namespace + if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext { + filtered_proofs.retain(|p| { + p.metadata + .submitter + .as_deref() + .map(|s| is_in_namespace(s, ns)) + .unwrap_or(false) + }); + } + // Apply verified filter if let Some(verified) = params.verified { filtered_proofs.retain(|p| p.verified == verified); @@ -183,8 +223,23 @@ pub async fn list_proofs( /// DELETE /api/v1/proofs/:id pub async fn delete_proof( State(state): State, + ns_ext: Option>, Path(proof_id): Path, ) -> Result> { + // Enforce namespace: verify the proof's submitter is in the namespace + if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext { + if let Some(proof) = state.proof_store.get(&proof_id).await { + if let Some(ref submitter) = proof.metadata.submitter { + if !is_in_namespace(submitter, ns) { + return Err(Error::Forbidden(format!( + "Proof submitter is not in namespace \"{}\"", + ns + ))); + } + } + } + } + let deleted = state.proof_store.delete(&proof_id).await; if deleted { @@ -335,7 +390,7 @@ mod tests { #[tokio::test] async fn test_submit_and_get_proof() { - let state = AppState::new(); + let state = AppState::new().unwrap(); let request = SubmitProofRequest { proof_type: ProofType::Knowledge, @@ -347,7 +402,7 @@ mod tests { metadata: None, }; - let response = submit_proof(AxumState(state.clone()), Json(request)) + let response = submit_proof(AxumState(state.clone()), None, Json(request)) .await .unwrap(); @@ -361,7 +416,7 @@ mod tests { #[tokio::test] async fn test_list_proofs() { - let state = AppState::new(); + let state = AppState::new().unwrap(); // Submit multiple proofs for _ in 0..3 { @@ -370,7 +425,7 @@ mod tests { proof_data: serde_json::json!({"test": "data"}), metadata: None, }; - submit_proof(AxumState(state.clone()), Json(request)) + submit_proof(AxumState(state.clone()), None, Json(request)) .await .unwrap(); } @@ -381,14 +436,14 @@ mod tests { limit: Some(10), }; - let response = list_proofs(AxumState(state), Query(query)).await.unwrap(); + let response = list_proofs(AxumState(state), None, Query(query)).await.unwrap(); assert_eq!(response.0.count, 3); } #[tokio::test] async fn test_proof_stats() { - let state = AppState::new(); + let state = AppState::new().unwrap(); let request = SubmitProofRequest { proof_type: ProofType::Equality, @@ -396,7 +451,7 @@ mod tests { metadata: None, }; - submit_proof(AxumState(state.clone()), Json(request)) + submit_proof(AxumState(state.clone()), None, Json(request)) .await .unwrap(); diff --git a/crates/aingle_cortex/src/rest/query.rs b/crates/aingle_cortex/src/rest/query.rs index 9382f2d0..09504b94 100644 --- a/crates/aingle_cortex/src/rest/query.rs +++ b/crates/aingle_cortex/src/rest/query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Query endpoints for pattern matching use axum::{ @@ -7,9 +10,10 @@ use axum::{ use serde::{Deserialize, Serialize}; use crate::error::Result; +use crate::middleware::{is_in_namespace, RequestNamespace}; use crate::rest::triples::{TripleDto, ValueDto}; use crate::state::AppState; -use aingle_graph::{NodeId, Predicate, TriplePattern, Value}; +use aingle_graph::{NodeId, Predicate, Triple, TriplePattern, Value}; /// Pattern query request #[derive(Debug, Deserialize)] @@ -29,6 +33,9 @@ fn default_limit() -> usize { 100 } +/// Hard maximum for any query to prevent OOM on large graphs +const MAX_QUERY_LIMIT: usize = 10_000; + /// Pattern query response #[derive(Debug, Serialize)] pub struct PatternQueryResponse { @@ -53,6 +60,7 @@ pub struct PatternDescription { /// POST /api/v1/query pub async fn query_pattern( State(state): State, + ns_ext: Option>, Json(req): Json, ) -> Result> { let graph = state.graph.read().await; @@ -73,10 +81,21 @@ pub async fn query_pattern( let triples = graph.find(pattern)?; + // Enforce hard query limit to prevent OOM + let effective_limit = req.limit.min(MAX_QUERY_LIMIT); + + // Filter by namespace if present + let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns); + let triples: Vec = if let Some(ref ns) = ns_filter { + triples.into_iter().filter(|t| is_in_namespace(&t.subject.to_string(), ns)).collect() + } else { + triples + }; + let total = triples.len(); let matches: Vec = triples .into_iter() - .take(req.limit) + .take(effective_limit) .map(|t| t.into()) .collect(); @@ -110,6 +129,7 @@ pub struct ListSubjectsQuery { /// GET /api/v1/query/subjects pub async fn list_subjects( State(state): State, + ns_ext: Option>, Query(query): Query, ) -> Result> { let graph = state.graph.read().await; @@ -121,7 +141,12 @@ pub async fn list_subjects( }; let triples = graph.find(pattern)?; - let mut subjects: Vec = triples.into_iter().map(|t| t.subject.to_string()).collect(); + let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns); + let mut subjects: Vec = triples + .into_iter() + .map(|t| t.subject.to_string()) + .filter(|s| ns_filter.as_ref().map_or(true, |ns| is_in_namespace(s, ns))) + .collect(); subjects.sort(); subjects.dedup(); @@ -153,6 +178,7 @@ pub struct ListPredicatesQuery { /// GET /api/v1/query/predicates pub async fn list_predicates( State(state): State, + ns_ext: Option>, Query(query): Query, ) -> Result> { let graph = state.graph.read().await; @@ -164,8 +190,10 @@ pub async fn list_predicates( }; let triples = graph.find(pattern)?; + let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns); let mut predicates: Vec = triples .into_iter() + .filter(|t| ns_filter.as_ref().map_or(true, |ns| is_in_namespace(&t.subject.to_string(), ns))) .map(|t| t.predicate.to_string()) .collect(); predicates.sort(); diff --git a/crates/aingle_cortex/src/rest/raft_rpc.rs b/crates/aingle_cortex/src/rest/raft_rpc.rs new file mode 100644 index 00000000..65d1c76b --- /dev/null +++ b/crates/aingle_cortex/src/rest/raft_rpc.rs @@ -0,0 +1,308 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Internal Raft RPC endpoints for inter-node communication. +//! +//! These endpoints handle Raft protocol messages (AppendEntries, Vote, +//! InstallSnapshot) over HTTP. They are used by `HttpRaftRpcSender` +//! on other nodes to drive the Raft consensus protocol. +//! +//! ## Endpoints +//! +//! - `POST /internal/raft/append-entries` — AppendEntries RPC +//! - `POST /internal/raft/vote` — Vote RPC +//! - `POST /internal/raft/snapshot` — Install full snapshot + +use axum::{ + body::Bytes, + extract::State, + http::{HeaderMap, StatusCode}, + response::IntoResponse, +}; + +use crate::error::Error; +use crate::rest::cluster_utils::validate_cluster_auth; +use crate::state::AppState; + +type C = aingle_raft::CortexTypeConfig; + +/// POST /internal/raft/append-entries +/// +/// Receives a serialized `AppendEntriesRequest`, forwards to the local +/// Raft instance, and returns the serialized response. +pub async fn raft_append_entries( + State(state): State, + headers: HeaderMap, + body: Bytes, +) -> Result { + validate_cluster_auth(&headers, &state)?; + + let raft = state + .raft + .as_ref() + .ok_or_else(|| Error::Internal("Raft not initialized".into()))?; + + let req: openraft::raft::AppendEntriesRequest = serde_json::from_slice(&body) + .map_err(|e| Error::Internal(format!("Deserialize AppendEntries: {e}")))?; + + let resp = tokio::time::timeout( + std::time::Duration::from_secs(10), + raft.append_entries(req), + ) + .await + .map_err(|_| Error::Timeout("AppendEntries RPC timed out (10s)".into()))? + .map_err(|e| Error::Internal(format!("AppendEntries failed: {e}")))?; + + let payload = serde_json::to_vec(&resp) + .map_err(|e| Error::Internal(format!("Serialize response: {e}")))?; + + Ok((StatusCode::OK, payload)) +} + +/// POST /internal/raft/vote +/// +/// Receives a serialized `VoteRequest`, forwards to the local +/// Raft instance, and returns the serialized response. +pub async fn raft_vote( + State(state): State, + headers: HeaderMap, + body: Bytes, +) -> Result { + validate_cluster_auth(&headers, &state)?; + + let raft = state + .raft + .as_ref() + .ok_or_else(|| Error::Internal("Raft not initialized".into()))?; + + let req: openraft::raft::VoteRequest = serde_json::from_slice(&body) + .map_err(|e| Error::Internal(format!("Deserialize Vote: {e}")))?; + + let resp = tokio::time::timeout( + std::time::Duration::from_secs(10), + raft.vote(req), + ) + .await + .map_err(|_| Error::Timeout("Vote RPC timed out (10s)".into()))? + .map_err(|e| Error::Internal(format!("Vote failed: {e}")))?; + + let payload = serde_json::to_vec(&resp) + .map_err(|e| Error::Internal(format!("Serialize response: {e}")))?; + + Ok((StatusCode::OK, payload)) +} + +/// POST /internal/raft/snapshot +/// +/// Receives a serialized snapshot envelope (vote + meta + data), +/// forwards to the local Raft instance via `install_full_snapshot`. +pub async fn raft_snapshot( + State(state): State, + headers: HeaderMap, + body: Bytes, +) -> Result { + validate_cluster_auth(&headers, &state)?; + + let raft = state + .raft + .as_ref() + .ok_or_else(|| Error::Internal("Raft not initialized".into()))?; + + // The envelope matches what CortexNetworkConnection::full_snapshot serializes: + // { "vote": ..., "meta": ..., "data": [...] } + let envelope: serde_json::Value = serde_json::from_slice(&body) + .map_err(|e| Error::Internal(format!("Deserialize snapshot envelope: {e}")))?; + + let vote: openraft::type_config::alias::VoteOf = + serde_json::from_value(envelope["vote"].clone()) + .map_err(|e| Error::Internal(format!("Deserialize vote: {e}")))?; + + let meta: openraft::type_config::alias::SnapshotMetaOf = + serde_json::from_value(envelope["meta"].clone()) + .map_err(|e| Error::Internal(format!("Deserialize snapshot meta: {e}")))?; + + let data: Vec = serde_json::from_value(envelope["data"].clone()) + .map_err(|e| Error::Internal(format!("Deserialize snapshot data: {e}")))?; + + let snapshot = openraft::Snapshot { + meta, + snapshot: std::io::Cursor::new(data), + }; + + let resp = tokio::time::timeout( + std::time::Duration::from_secs(60), + raft.install_full_snapshot(vote, snapshot), + ) + .await + .map_err(|_| Error::Timeout("InstallSnapshot RPC timed out (60s)".into()))? + .map_err(|e| Error::Internal(format!("InstallSnapshot failed: {e}")))?; + + let payload = serde_json::to_vec(&resp) + .map_err(|e| Error::Internal(format!("Serialize response: {e}")))?; + + Ok((StatusCode::OK, payload)) +} + +/// In-flight chunked snapshot buffer with creation timestamp for TTL. +struct SnapshotBuffer { + data: Vec, + expected_size: u64, + created_at: std::time::Instant, +} + +/// In-flight chunked snapshot buffers, keyed by snapshot_id. +/// Buffers older than `BUFFER_TTL` are evicted to prevent memory leaks +/// from abandoned transfers. +static SNAPSHOT_BUFFERS: std::sync::LazyLock< + dashmap::DashMap, +> = std::sync::LazyLock::new(dashmap::DashMap::new); + +/// Maximum time a partial snapshot buffer can live before eviction. +const BUFFER_TTL: std::time::Duration = std::time::Duration::from_secs(300); // 5 min + +/// Maximum total memory across all in-flight snapshot buffers (256 MB). +const MAX_BUFFER_MEMORY: usize = 256 * 1024 * 1024; + +/// Evict expired snapshot buffers to reclaim memory. +fn evict_stale_buffers() { + SNAPSHOT_BUFFERS.retain(|id, buf| { + let alive = buf.created_at.elapsed() < BUFFER_TTL; + if !alive { + tracing::warn!(snapshot_id = %id, "Evicting stale snapshot buffer"); + } + alive + }); +} + +/// POST /internal/raft/snapshot-chunk +/// +/// Receives a single chunk of a streamed snapshot. Chunks are buffered +/// in memory and assembled when the final chunk arrives. +pub async fn raft_snapshot_chunk( + State(state): State, + headers: HeaderMap, + body: Bytes, +) -> Result { + validate_cluster_auth(&headers, &state)?; + + // Evict stale buffers on each request (cheap: DashMap::retain is O(n)) + evict_stale_buffers(); + + let chunk: aingle_raft::network::RaftMessage = serde_json::from_slice(&body) + .map_err(|e| Error::Internal(format!("Deserialize snapshot chunk: {e}")))?; + + match chunk { + aingle_raft::network::RaftMessage::SnapshotChunk { + snapshot_id, + offset, + total_size, + is_final, + data, + } => { + // Reject snapshots that would exceed memory budget + if total_size as usize > MAX_BUFFER_MEMORY { + return Err(Error::Internal(format!( + "Snapshot too large: {total_size} bytes exceeds {MAX_BUFFER_MEMORY} limit" + ))); + } + + // Append chunk to buffer + let mut buf = SNAPSHOT_BUFFERS + .entry(snapshot_id.clone()) + .or_insert_with(|| SnapshotBuffer { + data: Vec::with_capacity(total_size as usize), + expected_size: total_size, + created_at: std::time::Instant::now(), + }); + + // Extend buffer to accommodate this chunk + let required = offset as usize + data.len(); + if buf.data.len() < required { + buf.data.resize(required, 0); + } + buf.data[offset as usize..offset as usize + data.len()].copy_from_slice(&data); + + if is_final { + // Remove buffer and validate completeness + let full_buf = SNAPSHOT_BUFFERS + .remove(&snapshot_id) + .ok_or_else(|| Error::Internal("Snapshot buffer missing on final chunk".into()))? + .1; + + if (full_buf.data.len() as u64) != full_buf.expected_size { + return Err(Error::Internal(format!( + "Snapshot size mismatch: got {} bytes, expected {}", + full_buf.data.len(), + full_buf.expected_size + ))); + } + + // Delegate to the monolithic snapshot handler + let result = install_full_snapshot_from_bytes(&state, &full_buf.data).await?; + Ok((StatusCode::OK, result)) + } else { + // ACK this chunk + let ack = aingle_raft::network::RaftMessage::SnapshotChunkAck { + snapshot_id, + next_offset: offset + data.len() as u64, + }; + let payload = serde_json::to_vec(&ack) + .map_err(|e| Error::Internal(format!("Serialize chunk ack: {e}")))?; + Ok((StatusCode::OK, payload)) + } + } + _ => Err(Error::Internal("Expected SnapshotChunk message".into())), + } +} + +/// Shared logic: install a full snapshot from its raw bytes. +async fn install_full_snapshot_from_bytes( + state: &AppState, + data: &[u8], +) -> Result, Error> { + let raft = state + .raft + .as_ref() + .ok_or_else(|| Error::Internal("Raft not initialized".into()))?; + + let envelope: serde_json::Value = serde_json::from_slice(data) + .map_err(|e| Error::Internal(format!("Deserialize snapshot envelope: {e}")))?; + + let vote: openraft::type_config::alias::VoteOf = + serde_json::from_value(envelope["vote"].clone()) + .map_err(|e| Error::Internal(format!("Deserialize vote: {e}")))?; + + let meta: openraft::type_config::alias::SnapshotMetaOf = + serde_json::from_value(envelope["meta"].clone()) + .map_err(|e| Error::Internal(format!("Deserialize snapshot meta: {e}")))?; + + let snap_data: Vec = serde_json::from_value(envelope["data"].clone()) + .map_err(|e| Error::Internal(format!("Deserialize snapshot data: {e}")))?; + + let snapshot = openraft::Snapshot { + meta, + snapshot: std::io::Cursor::new(snap_data), + }; + + let resp = tokio::time::timeout( + std::time::Duration::from_secs(60), + raft.install_full_snapshot(vote, snapshot), + ) + .await + .map_err(|_| Error::Timeout("InstallSnapshot timed out (60s)".into()))? + .map_err(|e| Error::Internal(format!("InstallSnapshot failed: {e}")))?; + + serde_json::to_vec(&resp) + .map_err(|e| Error::Internal(format!("Serialize response: {e}"))) +} + +/// Create the internal Raft RPC sub-router. +pub fn raft_rpc_router() -> axum::Router { + use axum::routing::post; + + axum::Router::new() + .route("/internal/raft/append-entries", post(raft_append_entries)) + .route("/internal/raft/vote", post(raft_vote)) + .route("/internal/raft/snapshot", post(raft_snapshot)) + .route("/internal/raft/snapshot-chunk", post(raft_snapshot_chunk)) +} diff --git a/crates/aingle_cortex/src/rest/reputation.rs b/crates/aingle_cortex/src/rest/reputation.rs new file mode 100644 index 00000000..badc43d4 --- /dev/null +++ b/crates/aingle_cortex/src/rest/reputation.rs @@ -0,0 +1,235 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Reputation REST endpoints. +//! +//! Provides agent consistency scoring and batch assertion verification +//! for the skill reputation system. + +use crate::middleware::{is_in_namespace, RequestNamespace}; +use crate::state::AppState; +use aingle_graph::{NodeId, Value}; +use axum::{ + extract::{Path, State}, + response::IntoResponse, + Json, +}; +use serde::{Deserialize, Serialize}; + +// --------------------------------------------------------------------------- +// DTOs +// --------------------------------------------------------------------------- + +/// Agent consistency score response. +#[derive(Serialize, Debug)] +pub struct ConsistencyResponse { + /// Consistency score between 0.0 and 1.0. + pub score: f64, + /// Total number of assertions by this agent. + pub total: usize, + /// Number of verified assertions. + pub verified: usize, +} + +/// Request to batch-verify assertions. +#[derive(Deserialize, Debug)] +pub struct BatchVerifyAssertionsRequest { + /// Assertions to verify. + pub assertions: Vec, +} + +/// Reference to an assertion to verify. +#[derive(Deserialize, Debug)] +pub struct AssertionRef { + /// Subject of the assertion. + pub subject: String, + /// Predicate of the assertion. + pub predicate: String, +} + +/// Result of verifying a single assertion. +#[derive(Serialize, Debug)] +pub struct AssertionVerifyResult { + /// Subject of the assertion. + pub subject: String, + /// Predicate of the assertion. + pub predicate: String, + /// Whether the assertion is verified. + pub verified: bool, +} + +/// Response from batch assertion verification. +#[derive(Serialize, Debug)] +pub struct BatchVerifyAssertionsResponse { + /// Results for each assertion. + pub results: Vec, +} + +// --------------------------------------------------------------------------- +// Handlers +// --------------------------------------------------------------------------- + +/// GET /api/v1/agents/:id/consistency — Get agent assertion consistency score. +/// +/// Queries all assertions owned by the agent and checks how many +/// have been verified through PoL validation. +pub async fn get_agent_consistency( + State(state): State, + ns_ext: Option>, + Path(agent_id): Path, +) -> impl IntoResponse { + // Determine namespace prefix for agent node + let ns_prefix = ns_ext + .as_ref() + .and_then(|axum::Extension(RequestNamespace(ns))| ns.clone()) + .unwrap_or_else(|| "mayros".to_string()); + + // Phase 1: collect all triples we need from the graph, then drop the lock. + let (owned_subject_triples, prefixed_triples) = { + let graph = state.graph.read().await; + + let agent_node = Value::node(NodeId::named(format!("{}:agent:{}", ns_prefix, agent_id))); + + // Collect owned triples (assertedBy / ownedBy) and their subject triples. + let mut owned = Vec::new(); + if let Ok(triples) = graph.get_object(&agent_node) { + for triple in &triples { + let pred_str = triple.predicate.as_str(); + if pred_str.ends_with(":assertedBy") || pred_str.ends_with(":ownedBy") { + let subject_triples = graph.get_subject(&triple.subject).unwrap_or_default(); + owned.push(subject_triples); + } + } + } + + // Collect agent-prefixed assertion triples. + let agent_prefix = format!("{}:agent:{}:", ns_prefix, agent_id); + let mut prefixed = Vec::new(); + if let Ok(prefixed_subjects) = graph.subjects_with_prefix(&agent_prefix) { + for subj in &prefixed_subjects { + if let Ok(subj_triples) = graph.get_subject(subj) { + let filtered: Vec<_> = subj_triples + .into_iter() + .filter(|t| { + let p = t.predicate.as_str(); + !p.ends_with(":assertedBy") && !p.ends_with(":ownedBy") + }) + .collect(); + prefixed.push(filtered); + } + } + } + + (owned, prefixed) + // graph lock dropped here + }; + + // Phase 2: validate with the logic engine (separate lock). + let logic = state.logic.read().await; + + let mut total: usize = 0; + let mut verified: usize = 0; + + for subject_triples in &owned_subject_triples { + total += 1; + let any_valid = subject_triples.iter().any(|t| logic.validate(t).is_valid); + if any_valid { + verified += 1; + } + } + + for triples in &prefixed_triples { + for t in triples { + total += 1; + if logic.validate(t).is_valid { + verified += 1; + } + } + } + + drop(logic); + + let score = if total > 0 { + verified as f64 / total as f64 + } else { + 0.0 + }; + + Json(ConsistencyResponse { + score, + total, + verified, + }) +} + +/// POST /api/v1/assertions/verify-batch — Batch verify assertion proofs. +/// +/// For each assertion (subject + predicate), checks if the triple exists +/// and if it passes PoL validation. +pub async fn batch_verify_assertions( + State(state): State, + ns_ext: Option>, + Json(req): Json, +) -> impl IntoResponse { + // Extract namespace for filtering + let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns); + + // Phase 1: collect matching triples from the graph, then drop the lock. + let assertion_triples: Vec<_> = { + let graph = state.graph.read().await; + + req.assertions + .iter() + .map(|assertion| { + if let Some(ref ns) = ns_filter { + if !is_in_namespace(&assertion.subject, ns) { + return None; + } + } + let subj = NodeId::named(&assertion.subject); + let triples = graph.get_subject(&subj).unwrap_or_default(); + triples + .into_iter() + .find(|t| t.predicate.as_str() == assertion.predicate) + }) + .collect() + // graph lock dropped here + }; + + // Phase 2: validate with the logic engine (separate lock). + let logic = state.logic.read().await; + + let results: Vec = req + .assertions + .iter() + .zip(assertion_triples.iter()) + .map(|(assertion, maybe_triple)| { + let verified = maybe_triple + .as_ref() + .map(|t| logic.validate(t).is_valid) + .unwrap_or(false); + AssertionVerifyResult { + subject: assertion.subject.clone(), + predicate: assertion.predicate.clone(), + verified, + } + }) + .collect(); + + drop(logic); + + Json(BatchVerifyAssertionsResponse { results }) +} + +/// Create the reputation sub-router. +pub fn reputation_router() -> axum::Router { + axum::Router::new() + .route( + "/api/v1/agents/{id}/consistency", + axum::routing::get(get_agent_consistency), + ) + .route( + "/api/v1/assertions/verify-batch", + axum::routing::post(batch_verify_assertions), + ) +} diff --git a/crates/aingle_cortex/src/rest/skill_verification.rs b/crates/aingle_cortex/src/rest/skill_verification.rs new file mode 100644 index 00000000..db824bc0 --- /dev/null +++ b/crates/aingle_cortex/src/rest/skill_verification.rs @@ -0,0 +1,189 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Skill verification REST endpoints. +//! +//! These endpoints support semantic skill validation, sandbox creation, +//! and cleanup for the Apilium Hub verification pipeline. + +use crate::state::AppState; +use aingle_graph::{NodeId, Predicate, Triple, Value}; +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::IntoResponse, + Json, +}; +use serde::{Deserialize, Serialize}; + +// --------------------------------------------------------------------------- +// DTOs +// --------------------------------------------------------------------------- + +/// Request to validate a semantic skill manifest against PoL rules. +#[derive(Deserialize, Debug)] +pub struct ValidateManifestRequest { + /// Assertions declared in the skill manifest. + pub assertions: Vec, + /// The namespace to validate against. + pub namespace: String, +} + +/// A declared assertion in the skill manifest. +#[derive(Deserialize, Debug)] +pub struct AssertionDecl { + /// The predicate this assertion targets. + pub predicate: String, + /// Whether the assertion requires a proof. + #[serde(default)] + pub require_proof: bool, +} + +/// Response from manifest validation. +#[derive(Serialize, Debug)] +pub struct ValidateManifestResponse { + /// Whether all assertions are valid. + pub valid: bool, + /// List of validation errors. + pub errors: Vec, +} + +/// Request to create a sandbox namespace. +#[derive(Deserialize, Debug)] +pub struct CreateSandboxRequest { + /// Desired namespace for the sandbox. + pub namespace: String, + /// Time-to-live in seconds (default: 300). + #[serde(default = "default_ttl")] + pub ttl_seconds: u64, +} + +fn default_ttl() -> u64 { + 300 +} + +/// Response from sandbox creation. +#[derive(Serialize, Debug)] +pub struct CreateSandboxResponse { + /// Sandbox identifier. + pub id: String, + /// The actual namespace assigned. + pub namespace: String, +} + +// --------------------------------------------------------------------------- +// Handlers +// --------------------------------------------------------------------------- + +/// POST /api/v1/skills/validate — Validate a semantic skill manifest. +/// +/// Checks each declared assertion's predicate against the logic engine +/// to ensure the assertions are consistent with PoL rules. +pub async fn validate_manifest( + State(state): State, + Json(req): Json, +) -> impl IntoResponse { + let logic = state.logic.read().await; + let mut errors: Vec = Vec::new(); + + for assertion in &req.assertions { + let ns_pred = if assertion.predicate.contains(':') { + assertion.predicate.clone() + } else { + format!("{}:{}", req.namespace, assertion.predicate) + }; + + // If require_proof is true, validate the assertion against the + // logic engine by constructing a test triple and checking for + // rejections. This ensures PoL rules exist for this predicate. + if assertion.require_proof { + let test_triple = Triple::new( + NodeId::named(format!("{}:_test", req.namespace)), + Predicate::named(&ns_pred), + Value::literal("_test_value"), + ); + let result = logic.validate(&test_triple); + // If the engine has no matching rules at all, the result + // will have zero matches — warn the author. + if result.matches.is_empty() { + errors.push(format!( + "Assertion predicate '{}' requires proof but no PoL rules found", + ns_pred + )); + } + } + } + + let valid = errors.is_empty(); + Json(ValidateManifestResponse { valid, errors }) +} + +/// POST /api/v1/skills/sandbox — Create a temporary sandbox namespace. +/// +/// Creates an isolated namespace for testing a skill, with an automatic +/// TTL-based cleanup. +pub async fn create_sandbox( + State(state): State, + Json(req): Json, +) -> impl IntoResponse { + let sandbox_id = format!("sandbox-{}", uuid::Uuid::new_v4()); + let sandbox_ns = format!("{}:{}", req.namespace, sandbox_id); + + // Register the sandbox in the manager + state + .sandbox_manager + .create(sandbox_id.clone(), sandbox_ns.clone(), req.ttl_seconds) + .await; + + ( + StatusCode::CREATED, + Json(CreateSandboxResponse { + id: sandbox_id, + namespace: sandbox_ns, + }), + ) +} + +/// DELETE /api/v1/skills/sandbox/:id — Clean up a sandbox namespace. +/// +/// Removes all triples in the sandbox namespace and deregisters it. +pub async fn delete_sandbox( + State(state): State, + Path(sandbox_id): Path, +) -> impl IntoResponse { + let removed = state.sandbox_manager.remove(&sandbox_id).await; + + if let Some(namespace) = removed { + // Clean up all triples whose subject starts with the sandbox namespace. + let graph = state.graph.write().await; + let deleted = graph.delete_by_subject_prefix(&namespace).unwrap_or(0); + + Json(serde_json::json!({ + "deleted": true, + "namespace": namespace, + "triples_removed": deleted + })) + } else { + Json(serde_json::json!({ + "deleted": false, + "error": "sandbox not found" + })) + } +} + +/// Create the skill verification sub-router. +pub fn skill_verification_router() -> axum::Router { + axum::Router::new() + .route( + "/api/v1/skills/validate", + axum::routing::post(validate_manifest), + ) + .route( + "/api/v1/skills/sandbox", + axum::routing::post(create_sandbox), + ) + .route( + "/api/v1/skills/sandbox/{id}", + axum::routing::delete(delete_sandbox), + ) +} diff --git a/crates/aingle_cortex/src/rest/stats.rs b/crates/aingle_cortex/src/rest/stats.rs index b341b9cb..08074dd3 100644 --- a/crates/aingle_cortex/src/rest/stats.rs +++ b/crates/aingle_cortex/src/rest/stats.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Statistics and health check endpoints use axum::{extract::State, Json}; @@ -88,6 +91,26 @@ pub struct ComponentStatus { pub message: Option, } +/// Flush response +#[derive(Debug, Serialize)] +pub struct FlushResponse { + /// Whether the flush was successful + pub ok: bool, +} + +/// Flush graph database and Ineru memory to disk. +/// +/// POST /api/v1/flush +pub async fn flush_data(State(state): State) -> Result> { + // Flush graph + { + let graph = state.graph.read().await; + graph.flush()?; + } + + Ok(Json(FlushResponse { ok: true })) +} + /// Health check endpoint /// /// GET /api/v1/health diff --git a/crates/aingle_cortex/src/rest/triples.rs b/crates/aingle_cortex/src/rest/triples.rs index 8200702d..99d73e9d 100644 --- a/crates/aingle_cortex/src/rest/triples.rs +++ b/crates/aingle_cortex/src/rest/triples.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Triple CRUD operations use axum::{ @@ -8,9 +11,14 @@ use axum::{ use serde::{Deserialize, Serialize}; use crate::error::{Error, Result}; +use crate::middleware::{is_in_namespace, RequestNamespace}; +use crate::rest::audit::AuditEntry; use crate::state::{AppState, Event}; use aingle_graph::{NodeId, Predicate, Triple, TripleId, TriplePattern, Value}; +#[cfg(feature = "cluster")] +use axum::http::HeaderMap; + /// Triple data transfer object #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TripleDto { @@ -122,6 +130,7 @@ fn default_limit() -> usize { /// POST /api/v1/triples pub async fn create_triple( State(state): State, + ns_ext: Option>, Json(req): Json, ) -> Result<(StatusCode, Json)> { // Validate input @@ -132,8 +141,192 @@ pub async fn create_triple( return Err(Error::InvalidInput("Predicate cannot be empty".to_string())); } + // Enforce namespace scoping + if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext { + if !is_in_namespace(&req.subject, ns) { + return Err(Error::Forbidden(format!( + "Subject \"{}\" is not in namespace \"{}\"", + req.subject, ns + ))); + } + } + let object: Value = req.object.clone().into(); + // DAG + Cluster mode: create DagAction and route through Raft + #[cfg(feature = "dag")] + if let Some(ref raft) = state.raft { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named(&format!( + "node:{}", + state.cluster_node_id.unwrap_or(0) + ))); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + // Get current tips + let parents = { + let graph = state.graph.read().await; + graph + .dag_tips() + .unwrap_or_default() + }; + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleInsert { + triples: vec![aingle_graph::dag::TripleInsertPayload { + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: serde_json::to_value(&req.object).unwrap_or_default(), + }], + }, + signature: None, + }; + + // Sign the action with the node's Ed25519 key + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::DagAction { + action_bytes: action.to_bytes(), + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft apply failed".to_string()), + )); + } + + let dag_action_hash = resp.response().id.clone(); + let dto = TripleDto { + id: dag_action_hash.clone(), + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: req.object.clone(), + created_at: Some(chrono::Utc::now().to_rfc3339()), + }; + + let hash = dag_action_hash.unwrap_or_else(|| "raft-dag".to_string()); + + // Record audit entry + { + let namespace = ns_ext + .as_ref() + .and_then(|axum::Extension(RequestNamespace(ns))| ns.clone()); + let mut audit = state.audit_log.write().await; + audit.record(AuditEntry { + timestamp: chrono::Utc::now().to_rfc3339(), + user_id: namespace.clone().unwrap_or_else(|| "anonymous".to_string()), + namespace, + action: "create".to_string(), + resource: format!("/api/v1/triples/{}", hash), + details: Some(format!("subject={} (dag)", req.subject)), + request_id: None, + }); + } + + // Broadcast event + state.broadcaster.broadcast(Event::TripleAdded { + hash, + subject: req.subject, + predicate: req.predicate, + object: serde_json::to_value(&req.object).unwrap_or_default(), + }); + + return Ok((StatusCode::CREATED, Json(dto))); + } + + // Cluster mode (non-DAG): route writes through Raft + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::TripleInsert { + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: serde_json::to_value(&req.object).unwrap_or_default(), + triple_id: [0u8; 32], // State machine will compute the real ID + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft apply failed".to_string()), + )); + } + + // State machine already applied the triple to GraphDB. + // Build response DTO from the request data, using the ID from the state machine. + let triple_id = resp.response().id.clone(); + let dto = TripleDto { + id: triple_id.clone(), + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: req.object.clone(), + created_at: Some(chrono::Utc::now().to_rfc3339()), + }; + + let hash = triple_id.unwrap_or_else(|| "raft".to_string()); + + // Record audit entry + { + let namespace = ns_ext + .as_ref() + .and_then(|axum::Extension(RequestNamespace(ns))| ns.clone()); + let mut audit = state.audit_log.write().await; + audit.record(AuditEntry { + timestamp: chrono::Utc::now().to_rfc3339(), + user_id: namespace.clone().unwrap_or_else(|| "anonymous".to_string()), + namespace, + action: "create".to_string(), + resource: format!("/api/v1/triples/{}", hash), + details: Some(format!("subject={}", req.subject)), + request_id: None, + }); + } + + // Broadcast event + state.broadcaster.broadcast(Event::TripleAdded { + hash, + subject: req.subject, + predicate: req.predicate, + object: serde_json::to_value(&req.object).unwrap_or_default(), + }); + + return Ok((StatusCode::CREATED, Json(dto))); + } + + // Guard: if Raft is initialized, all writes MUST go through Raft. + // Reaching here means Raft was skipped — prevent split-brain (#2). + #[cfg(feature = "cluster")] + if state.raft.is_some() { + return Err(Error::Internal("Raft initialized but write not routed through Raft".into())); + } + + // Non-cluster mode: direct write // Create the triple let triple = Triple::new( NodeId::named(&req.subject), @@ -141,12 +334,80 @@ pub async fn create_triple( object, ); - // Add triple to graph + // Add triple to graph (and record DAG action if enabled) let triple_id = { let graph = state.graph.read().await; - graph.insert(triple.clone())? + let id = graph.insert(triple.clone())?; + + // Record in DAG if enabled + #[cfg(feature = "dag")] + if let Some(dag_store) = graph.dag_store() { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let parents = dag_store.tips().unwrap_or_default(); + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleInsert { + triples: vec![aingle_graph::dag::TripleInsertPayload { + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: serde_json::to_value(&req.object).unwrap_or_default(), + }], + }, + signature: None, + }; + + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + dag_store.put(&action).map_err(|e| { + Error::Internal(format!( + "DAG action failed for triple insert — data integrity at risk: {e}" + )) + })?; + } + + id }; + // Append to WAL (cluster mode without Raft — legacy path) + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + wal.append(aingle_wal::WalEntryKind::TripleInsert { + subject: req.subject.clone(), + predicate: req.predicate.clone(), + object: serde_json::to_value(&req.object).unwrap_or_default(), + triple_id: *triple_id.as_bytes(), + }).map_err(|e| Error::Internal(format!("WAL append failed: {e}")))?; + } + + // Record audit entry + { + let namespace = ns_ext + .as_ref() + .and_then(|axum::Extension(RequestNamespace(ns))| ns.clone()); + let mut audit = state.audit_log.write().await; + audit.record(AuditEntry { + timestamp: chrono::Utc::now().to_rfc3339(), + user_id: namespace.clone().unwrap_or_else(|| "anonymous".to_string()), + namespace, + action: "create".to_string(), + resource: format!("/api/v1/triples/{}", triple_id.to_hex()), + details: Some(format!("subject={}", req.subject)), + request_id: None, + }); + } + // Broadcast event state.broadcaster.broadcast(Event::TripleAdded { hash: triple_id.to_hex(), @@ -158,13 +419,45 @@ pub async fn create_triple( Ok((StatusCode::CREATED, Json(triple.into()))) } +/// Parse X-Consistency header into a ConsistencyLevel. +#[cfg(feature = "cluster")] +fn parse_consistency_header(headers: &HeaderMap) -> aingle_raft::ConsistencyLevel { + headers + .get("x-consistency") + .and_then(|v| v.to_str().ok()) + .map(aingle_raft::ConsistencyLevel::from_header) + .unwrap_or_default() +} + /// Get a triple by hash /// /// GET /api/v1/triples/:id pub async fn get_triple( State(state): State, + #[cfg(feature = "cluster")] headers: HeaderMap, Path(id): Path, ) -> Result> { + // Apply consistency level for cluster reads + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let consistency = parse_consistency_header(&headers); + match consistency { + aingle_raft::ConsistencyLevel::Linearizable => { + raft.ensure_linearizable(openraft::raft::ReadPolicy::ReadIndex) + .await + .map_err(|e| Error::Internal(format!("Linearizable read: {e}")))?; + } + aingle_raft::ConsistencyLevel::Quorum => { + raft.ensure_linearizable(openraft::raft::ReadPolicy::LeaseRead) + .await + .map_err(|e| Error::Internal(format!("Quorum read: {e}")))?; + } + aingle_raft::ConsistencyLevel::Local => { + // Read from local state — no Raft check needed + } + } + } + let triple_id = TripleId::from_hex(&id) .ok_or_else(|| Error::InvalidInput(format!("Invalid triple ID: {}", id)))?; @@ -181,17 +474,206 @@ pub async fn get_triple( /// DELETE /api/v1/triples/:id pub async fn delete_triple( State(state): State, + ns_ext: Option>, Path(id): Path, ) -> Result { let triple_id = TripleId::from_hex(&id) .ok_or_else(|| Error::InvalidInput(format!("Invalid triple ID: {}", id)))?; + // Enforce namespace on delete + if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext { + let graph = state.graph.read().await; + if let Some(triple) = graph.get(&triple_id)? { + if !is_in_namespace(&triple.subject.to_string(), ns) { + return Err(Error::Forbidden(format!( + "Triple subject is not in namespace \"{}\"", + ns + ))); + } + } + } + + // DAG + Cluster mode: create DagAction for delete + #[cfg(feature = "dag")] + if let Some(ref raft) = state.raft { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named(&format!( + "node:{}", + state.cluster_node_id.unwrap_or(0) + ))); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + + let (parents, subject_for_dag) = { + let graph = state.graph.read().await; + let tips = graph.dag_tips().unwrap_or_default(); + let subj = graph + .get(&triple_id) + .ok() + .flatten() + .map(|t| t.subject.to_string()); + (tips, subj) + }; + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleDelete { + triple_ids: vec![*triple_id.as_bytes()], + subjects: subject_for_dag.into_iter().collect(), + }, + signature: None, + }; + + // Sign the action with the node's Ed25519 key + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::DagAction { + action_bytes: action.to_bytes(), + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft delete failed".to_string()), + )); + } + + state + .broadcaster + .broadcast(Event::TripleDeleted { hash: id }); + return Ok(StatusCode::NO_CONTENT); + } + + // Cluster mode (non-DAG): route deletes through Raft + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let raft_req = aingle_raft::CortexRequest { + kind: aingle_wal::WalEntryKind::TripleDelete { + triple_id: *triple_id.as_bytes(), + }, + }; + let resp = raft + .client_write(raft_req) + .await + .map_err(|e| handle_raft_write_error(e, &state))?; + + if !resp.response().success { + return Err(Error::Internal( + resp.response() + .detail + .clone() + .unwrap_or_else(|| "Raft delete failed".to_string()), + )); + } + + state + .broadcaster + .broadcast(Event::TripleDeleted { hash: id }); + return Ok(StatusCode::NO_CONTENT); + } + + // Guard: if Raft is initialized, all writes MUST go through Raft (#2). + #[cfg(feature = "cluster")] + if state.raft.is_some() { + return Err(Error::Internal("Raft initialized but write not routed through Raft".into())); + } + + // Non-cluster mode: direct delete let deleted = { let graph = state.graph.read().await; - graph.delete(&triple_id)? + + // Look up subject before deleting (for DAG indexing) + #[cfg(feature = "dag")] + let subject_for_dag = graph + .get(&triple_id) + .ok() + .flatten() + .map(|t| t.subject.to_string()); + + let deleted = graph.delete(&triple_id)?; + + // Record in DAG if enabled and deletion succeeded + #[cfg(feature = "dag")] + if deleted { + if let Some(dag_store) = graph.dag_store() { + let dag_author = state + .dag_author + .clone() + .unwrap_or_else(|| aingle_graph::NodeId::named("node:local")); + let dag_seq = state + .dag_seq_counter + .fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let parents = dag_store.tips().unwrap_or_default(); + + let mut action = aingle_graph::dag::DagAction { + parents, + author: dag_author, + seq: dag_seq, + timestamp: chrono::Utc::now(), + payload: aingle_graph::dag::DagPayload::TripleDelete { + triple_ids: vec![*triple_id.as_bytes()], + subjects: subject_for_dag.into_iter().collect(), + }, + signature: None, + }; + + if let Some(ref key) = state.dag_signing_key { + key.sign(&mut action); + } + + dag_store.put(&action).map_err(|e| { + Error::Internal(format!( + "DAG action failed for triple delete — data integrity at risk: {e}" + )) + })?; + } + } + + deleted }; if deleted { + // Append to WAL (legacy cluster path) + #[cfg(feature = "cluster")] + if let Some(ref wal) = state.wal { + wal.append(aingle_wal::WalEntryKind::TripleDelete { + triple_id: *triple_id.as_bytes(), + }).map_err(|e| Error::Internal(format!("WAL append failed: {e}")))?; + } + + // Record audit entry + { + let namespace = ns_ext + .as_ref() + .and_then(|axum::Extension(RequestNamespace(ns))| ns.clone()); + let mut audit = state.audit_log.write().await; + audit.record(AuditEntry { + timestamp: chrono::Utc::now().to_rfc3339(), + user_id: namespace.clone().unwrap_or_else(|| "anonymous".to_string()), + namespace, + action: "delete".to_string(), + resource: format!("/api/v1/triples/{}", id), + details: None, + request_id: None, + }); + } + state .broadcaster .broadcast(Event::TripleDeleted { hash: id }); @@ -206,8 +688,29 @@ pub async fn delete_triple( /// GET /api/v1/triples pub async fn list_triples( State(state): State, + #[cfg(feature = "cluster")] headers: HeaderMap, + ns_ext: Option>, Query(query): Query, ) -> Result> { + // Apply consistency level for cluster reads + #[cfg(feature = "cluster")] + if let Some(ref raft) = state.raft { + let consistency = parse_consistency_header(&headers); + match consistency { + aingle_raft::ConsistencyLevel::Linearizable => { + raft.ensure_linearizable(openraft::raft::ReadPolicy::ReadIndex) + .await + .map_err(|e| Error::Internal(format!("Consistent read: {e}")))?; + } + aingle_raft::ConsistencyLevel::Quorum => { + raft.ensure_linearizable(openraft::raft::ReadPolicy::LeaseRead) + .await + .map_err(|e| Error::Internal(format!("Consistent read: {e}")))?; + } + aingle_raft::ConsistencyLevel::Local => {} + } + } + let graph = state.graph.read().await; // Build pattern based on provided filters @@ -222,6 +725,14 @@ pub async fn list_triples( let triples = graph.find(pattern)?; + // Filter by namespace if present + let ns_filter = ns_ext.and_then(|axum::Extension(RequestNamespace(ns))| ns); + let triples: Vec = if let Some(ref ns) = ns_filter { + triples.into_iter().filter(|t| is_in_namespace(&t.subject.to_string(), ns)).collect() + } else { + triples + }; + // Apply pagination let total = triples.len(); let triples: Vec = triples @@ -248,6 +759,154 @@ pub struct ListTriplesResponse { pub offset: usize, } +/// Request to batch-insert multiple triples +#[derive(Debug, Deserialize)] +pub struct BatchInsertRequest { + pub triples: Vec, +} + +/// Response for batch insert +#[derive(Debug, Serialize)] +pub struct BatchInsertResponse { + pub inserted: Vec, + pub total: usize, + pub duplicates: usize, +} + +/// Insert multiple triples atomically +/// +/// POST /api/v1/triples/batch +pub async fn batch_insert_triples( + State(state): State, + ns_ext: Option>, + Json(req): Json, +) -> Result<(StatusCode, Json)> { + if req.triples.is_empty() { + return Ok(( + StatusCode::OK, + Json(BatchInsertResponse { + inserted: vec![], + total: 0, + duplicates: 0, + }), + )); + } + + // Validate all inputs first + for (i, t) in req.triples.iter().enumerate() { + if t.subject.is_empty() { + return Err(Error::InvalidInput(format!( + "Triple [{}]: subject cannot be empty", + i + ))); + } + if t.predicate.is_empty() { + return Err(Error::InvalidInput(format!( + "Triple [{}]: predicate cannot be empty", + i + ))); + } + // Enforce namespace scoping + if let Some(axum::Extension(RequestNamespace(Some(ref ns)))) = ns_ext { + if !is_in_namespace(&t.subject, ns) { + return Err(Error::Forbidden(format!( + "Triple [{}]: subject \"{}\" is not in namespace \"{}\"", + i, t.subject, ns + ))); + } + } + } + + // Build Triple objects + let triples: Vec = req + .triples + .iter() + .map(|t| { + let object: Value = t.object.clone().into(); + Triple::new( + NodeId::named(&t.subject), + Predicate::named(&t.predicate), + object, + ) + }) + .collect(); + + let count_before = { + let graph = state.graph.read().await; + graph.count() + }; + + // Atomic batch insert + let ids = { + let graph = state.graph.read().await; + graph.insert_batch(triples)? + }; + + let count_after = { + let graph = state.graph.read().await; + graph.count() + }; + + let actually_inserted = count_after - count_before; + let duplicates = ids.len() - actually_inserted; + + // Build response DTOs + let inserted: Vec = ids + .iter() + .zip(req.triples.iter()) + .map(|(id, t)| TripleDto { + id: Some(id.to_hex()), + subject: format!("<{}>", t.subject), + predicate: format!("<{}>", t.predicate), + object: t.object.clone(), + created_at: Some(chrono::Utc::now().to_rfc3339()), + }) + .collect(); + + // Record audit entry + { + let namespace = ns_ext + .as_ref() + .and_then(|axum::Extension(RequestNamespace(ns))| ns.clone()); + let mut audit = state.audit_log.write().await; + audit.record(AuditEntry { + timestamp: chrono::Utc::now().to_rfc3339(), + user_id: namespace.clone().unwrap_or_else(|| "anonymous".to_string()), + namespace, + action: "batch_create".to_string(), + resource: "/api/v1/triples/batch".to_string(), + details: Some(format!( + "inserted={}, duplicates={}", + actually_inserted, duplicates + )), + request_id: None, + }); + } + + // Broadcast events for new triples + for (id, t) in ids.iter().zip(req.triples.iter()) { + state.broadcaster.broadcast(Event::TripleAdded { + hash: id.to_hex(), + subject: t.subject.clone(), + predicate: t.predicate.clone(), + object: serde_json::to_value(&t.object).unwrap_or_default(), + }); + } + + Ok(( + StatusCode::CREATED, + Json(BatchInsertResponse { + total: inserted.len(), + duplicates, + inserted, + }), + )) +} + +/// Re-export shared Raft write error handler for this module. +#[cfg(feature = "cluster")] +use crate::rest::cluster_utils::handle_raft_write_error; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/aingle_cortex/src/server.rs b/crates/aingle_cortex/src/server.rs index 330d4d4e..3e909514 100644 --- a/crates/aingle_cortex/src/server.rs +++ b/crates/aingle_cortex/src/server.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The main Córtex API server. use crate::error::Result; @@ -6,7 +9,9 @@ use crate::state::AppState; use axum::Router; use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; +use std::path::PathBuf; +use axum::extract::DefaultBodyLimit; +use tower_http::cors::CorsLayer; use tower_http::trace::TraceLayer; use tracing::info; @@ -17,9 +22,10 @@ pub struct CortexConfig { pub host: String, /// The port to listen on. pub port: u16, - /// If `true`, Cross-Origin Resource Sharing (CORS) headers will be enabled. - pub cors_enabled: bool, + /// Allowed CORS origins. Empty = CORS disabled. Use `["*"]` for development only. + pub cors_allowed_origins: Vec, /// If `true`, the GraphQL playground interface will be served at `/graphql`. + /// **Must be false in production** (exposes schema to unauthenticated users). pub graphql_playground: bool, /// If `true`, HTTP request tracing will be enabled for debugging. pub tracing: bool, @@ -27,6 +33,18 @@ pub struct CortexConfig { pub rate_limit_enabled: bool, /// The number of requests allowed per minute per IP address if rate limiting is enabled. pub rate_limit_rpm: u32, + /// Optional file path for JSONL audit log persistence. + pub audit_log_path: Option, + /// Maximum request body size in bytes (default: 1MB). + pub max_body_size: usize, + /// Periodic flush interval in seconds (0 = disabled, default: 300). + pub flush_interval_secs: u64, + /// Path to the graph database directory. + /// + /// - `Some(":memory:")` — volatile in-memory storage (no persistence). + /// - `Some(path)` — persist to the given directory. + /// - `None` — persist to the default `~/.aingle/cortex/graph.sled`. + pub db_path: Option, } impl Default for CortexConfig { @@ -34,12 +52,16 @@ impl Default for CortexConfig { fn default() -> Self { Self { host: "127.0.0.1".to_string(), - port: 8080, - cors_enabled: true, - graphql_playground: true, + port: 19090, + cors_allowed_origins: vec![], // CORS disabled by default + graphql_playground: false, // Disabled by default for security tracing: true, rate_limit_enabled: true, - rate_limit_rpm: 100, // 100 requests per minute + rate_limit_rpm: 100, + audit_log_path: None, + max_body_size: 1024 * 1024, // 1MB + flush_interval_secs: 300, + db_path: None, } } } @@ -76,12 +98,17 @@ pub struct CortexServer { } impl CortexServer { - /// Creates a new `CortexServer` with a given configuration and a default, in-memory `AppState`. + /// Creates a new `CortexServer` with a given configuration. + /// + /// The graph database backend is selected based on `config.db_path`: + /// - `Some(":memory:")` — volatile in-memory storage. + /// - `Some(path)` — Sled-backed persistent storage at the given path. + /// - `None` — Sled-backed persistent storage at `~/.aingle/cortex/graph.sled`. pub fn new(config: CortexConfig) -> Result { - Ok(Self { - config, - state: AppState::new(), - }) + let db_path = resolve_db_path(&config.db_path); + let state = AppState::with_db_path(&db_path, config.audit_log_path.clone())?; + info!("Graph database: {}", db_path); + Ok(Self { config, state }) } /// Creates a new `CortexServer` with a given configuration and a pre-existing `AppState`. @@ -94,6 +121,11 @@ impl CortexServer { &self.state } + /// Returns a mutable reference to the shared `AppState`. + pub fn state_mut(&mut self) -> &mut AppState { + &mut self.state + } + /// Builds the `axum` router, combining all API routes and middleware. pub fn build_router(&self) -> Router { let mut app: Router = Router::new(); @@ -113,6 +145,13 @@ impl CortexServer { app = app.merge(crate::auth::router()); } + // Add namespace extraction middleware (requires auth feature for JWT parsing). + #[cfg(feature = "auth")] + let app = { + use crate::middleware::namespace_extractor; + app.layer(axum::middleware::from_fn(namespace_extractor)) + }; + // Add the shared state to the router. let app = app.with_state(self.state.clone()); @@ -121,25 +160,41 @@ impl CortexServer { // Rate limiting layer. let app = if self.config.rate_limit_enabled { use crate::middleware::RateLimiter; - use axum_client_ip::SecureClientIpSource; let rate_limiter = RateLimiter::new(self.config.rate_limit_rpm) .with_burst_capacity(self.config.rate_limit_rpm); app.layer(rate_limiter.into_layer()) - .layer(SecureClientIpSource::ConnectInfo.into_extension()) } else { app }; - // CORS layer. - let app = if self.config.cors_enabled { - app.layer( + // Request body size limit (prevents DoS via huge payloads). + let app = app.layer(DefaultBodyLimit::max(self.config.max_body_size)); + + // CORS layer — only enabled with explicit origin whitelist. + let app = if !self.config.cors_allowed_origins.is_empty() { + use tower_http::cors::{Any, AllowOrigin}; + + let cors = if self.config.cors_allowed_origins == ["*"] { + // Development-only wildcard CorsLayer::new() .allow_origin(Any) .allow_methods(Any) - .allow_headers(Any), - ) + .allow_headers(Any) + } else { + let origins: Vec<_> = self + .config + .cors_allowed_origins + .iter() + .filter_map(|o| o.parse().ok()) + .collect(); + CorsLayer::new() + .allow_origin(AllowOrigin::list(origins)) + .allow_methods(Any) + .allow_headers(Any) + }; + app.layer(cors) } else { app }; @@ -161,6 +216,21 @@ impl CortexServer { let router = self.build_router(); + #[cfg(feature = "cluster")] + if let Some(ref tls_config) = self.state.tls_server_config { + info!("Starting Córtex API server on https://{}", addr); + + let tls_acceptor = + tokio_rustls::TlsAcceptor::from(tls_config.clone()); + let tcp_listener = tokio::net::TcpListener::bind(addr).await?; + let tls_listener = TlsListener { + inner: tcp_listener, + acceptor: tls_acceptor, + }; + axum::serve(tls_listener, router.into_make_service()).await?; + return Ok(()); + } + info!("Starting Córtex API server on http://{}", addr); info!("REST API: http://{}/api/v1", addr); #[cfg(feature = "graphql")] @@ -169,7 +239,11 @@ impl CortexServer { info!("SPARQL: http://{}/sparql", addr); let listener = tokio::net::TcpListener::bind(addr).await?; - axum::serve(listener, router).await?; + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .await?; Ok(()) } @@ -177,6 +251,7 @@ impl CortexServer { /// Runs the server with a graceful shutdown signal. /// /// The server will run until the `shutdown_signal` future completes. + /// If cluster TLS is configured, the server will accept HTTPS connections. pub async fn run_with_shutdown(self, shutdown_signal: F) -> Result<()> where F: std::future::Future + Send + 'static, @@ -187,18 +262,103 @@ impl CortexServer { let router = self.build_router(); + #[cfg(feature = "cluster")] + if let Some(ref tls_config) = self.state.tls_server_config { + info!("Starting Córtex API server on https://{}", addr); + + let tls_acceptor = + tokio_rustls::TlsAcceptor::from(tls_config.clone()); + let tcp_listener = tokio::net::TcpListener::bind(addr).await?; + let tls_listener = TlsListener { + inner: tcp_listener, + acceptor: tls_acceptor, + }; + axum::serve( + tls_listener, + router.into_make_service(), + ) + .with_graceful_shutdown(shutdown_signal) + .await?; + + info!("Córtex API server stopped"); + return Ok(()); + } + info!("Starting Córtex API server on http://{}", addr); let listener = tokio::net::TcpListener::bind(addr).await?; - axum::serve(listener, router) - .with_graceful_shutdown(shutdown_signal) - .await?; + axum::serve( + listener, + router.into_make_service_with_connect_info::(), + ) + .with_graceful_shutdown(shutdown_signal) + .await?; info!("Córtex API server stopped"); Ok(()) } } +/// Resolves the graph database path from the configuration. +/// +/// - `":memory:"` → returns `":memory:"` (volatile in-memory storage). +/// - An explicit path → returns it as-is. +/// - `None` → returns the default `~/.aingle/cortex/graph.sled`. +fn resolve_db_path(db_path: &Option) -> String { + match db_path { + Some(p) if p == ":memory:" => ":memory:".to_string(), + Some(p) => p.clone(), + None => { + let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")); + let default_dir = home.join(".aingle").join("cortex"); + std::fs::create_dir_all(&default_dir).ok(); + default_dir.join("graph.sled").to_string_lossy().to_string() + } + } +} + +// --------------------------------------------------------------------------- +// TLS Listener for cluster mode +// --------------------------------------------------------------------------- + +/// A TLS-wrapping listener that implements `axum::serve::Listener`. +/// +/// Accepts TCP connections, performs the TLS handshake, and yields +/// `TlsStream` to axum for request handling. Failed +/// handshakes are logged and retried automatically. +#[cfg(feature = "cluster")] +struct TlsListener { + inner: tokio::net::TcpListener, + acceptor: tokio_rustls::TlsAcceptor, +} + +#[cfg(feature = "cluster")] +impl axum::serve::Listener for TlsListener { + type Io = tokio_rustls::server::TlsStream; + type Addr = SocketAddr; + + async fn accept(&mut self) -> (Self::Io, Self::Addr) { + loop { + match self.inner.accept().await { + Ok((stream, addr)) => match self.acceptor.accept(stream).await { + Ok(tls_stream) => return (tls_stream, addr), + Err(e) => { + tracing::debug!("TLS handshake failed from {addr}: {e}"); + } + }, + Err(e) => { + tracing::debug!("TCP accept failed: {e}"); + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + } + } + } + + fn local_addr(&self) -> std::io::Result { + self.inner.local_addr() + } +} + #[cfg(test)] mod tests { use super::*; @@ -207,8 +367,8 @@ mod tests { fn test_config_default() { let config = CortexConfig::default(); assert_eq!(config.host, "127.0.0.1"); - assert_eq!(config.port, 8080); - assert!(config.cors_enabled); + assert_eq!(config.port, 19090); + assert!(config.cors_allowed_origins.is_empty()); } #[test] diff --git a/crates/aingle_cortex/src/sparql/executor.rs b/crates/aingle_cortex/src/sparql/executor.rs index 5935c6f5..0baac00a 100644 --- a/crates/aingle_cortex/src/sparql/executor.rs +++ b/crates/aingle_cortex/src/sparql/executor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! SPARQL query executor use super::{ParsedQuery, QueryType, SparqlResult}; @@ -463,15 +466,31 @@ fn evaluate_function_call( } } -/// Evaluate a regex match +/// Maximum allowed regex pattern length to prevent ReDoS attacks +const MAX_REGEX_PATTERN_LEN: usize = 256; + +/// Maximum compiled regex size (bytes) to limit backtracking complexity +const MAX_REGEX_SIZE: usize = 10 * 1024; // 10KB + +/// Evaluate a regex match with ReDoS protection fn evaluate_regex(text: &str, pattern: &str, flags: Option<&str>) -> Result { + if pattern.len() > MAX_REGEX_PATTERN_LEN { + return Err(Error::InvalidRegex(format!( + "Regex pattern exceeds maximum length of {} characters", + MAX_REGEX_PATTERN_LEN + ))); + } + let case_insensitive = flags.map(|f| f.contains('i')).unwrap_or(false); let regex = if case_insensitive { regex::RegexBuilder::new(pattern) .case_insensitive(true) + .size_limit(MAX_REGEX_SIZE) .build() } else { - regex::Regex::new(pattern) + regex::RegexBuilder::new(pattern) + .size_limit(MAX_REGEX_SIZE) + .build() } .map_err(|e| Error::InvalidRegex(e.to_string()))?; diff --git a/crates/aingle_cortex/src/sparql/mod.rs b/crates/aingle_cortex/src/sparql/mod.rs index 5bf499ee..89551f13 100644 --- a/crates/aingle_cortex/src/sparql/mod.rs +++ b/crates/aingle_cortex/src/sparql/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! SPARQL query engine for Córtex //! //! Provides SPARQL 1.1 query support for the AIngle graph. diff --git a/crates/aingle_cortex/src/sparql/parser.rs b/crates/aingle_cortex/src/sparql/parser.rs index 59054994..0631e0ca 100644 --- a/crates/aingle_cortex/src/sparql/parser.rs +++ b/crates/aingle_cortex/src/sparql/parser.rs @@ -1,7 +1,10 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! SPARQL query parser use crate::error::{Error, Result}; -use spargebra::Query; +use spargebra::{Query, SparqlParser}; /// Parsed SPARQL query #[derive(Debug)] @@ -25,7 +28,8 @@ pub enum QueryType { /// Parse a SPARQL query string pub fn parse_sparql(query: &str) -> Result { - let parsed = Query::parse(query, None) + let parsed = SparqlParser::new() + .parse_query(query) .map_err(|e| Error::SparqlParseError(format!("Failed to parse SPARQL: {}", e)))?; let query_type = match &parsed { diff --git a/crates/aingle_cortex/src/state.rs b/crates/aingle_cortex/src/state.rs index 60fa1015..ea2c3db5 100644 --- a/crates/aingle_cortex/src/state.rs +++ b/crates/aingle_cortex/src/state.rs @@ -1,14 +1,19 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The shared application state for the Córtex API server. use aingle_graph::GraphDB; use aingle_logic::RuleEngine; +use std::path::Path; use std::sync::Arc; -use titans_memory::{MemoryConfig, TitansMemory}; +use ineru::IneruMemory; use tokio::sync::RwLock; #[cfg(feature = "auth")] use crate::auth::UserStore; use crate::proofs::ProofStore; +use crate::rest::audit::AuditLog; /// The shared state accessible by all API handlers. /// @@ -20,26 +25,57 @@ pub struct AppState { pub graph: Arc>, /// A thread-safe reference to the logic and validation engine. pub logic: Arc>, - /// The Titans dual-memory system (STM + LTM with consolidation). - pub memory: Arc>, + /// The Ineru dual-memory system (STM + LTM with consolidation). + pub memory: Arc>, /// The event broadcaster for sending real-time updates to WebSocket subscribers. pub broadcaster: Arc, /// The store for managing and verifying zero-knowledge proofs. pub proof_store: Arc, + /// Manager for temporary sandbox namespaces used by skill verification. + pub sandbox_manager: Arc, + /// Audit log for tracking API actions. + pub audit_log: Arc>, /// The user store for authentication and authorization. /// /// This field is only available if the `auth` feature is enabled. #[cfg(feature = "auth")] pub user_store: Arc, + /// P2P manager for multi-node triple synchronization. + #[cfg(feature = "p2p")] + pub p2p: Option>, + /// Write-Ahead Log for clustering. + #[cfg(feature = "cluster")] + pub wal: Option>, + /// Raft consensus instance for cluster coordination. + #[cfg(feature = "cluster")] + pub raft: Option>>, + /// This node's ID in the Raft cluster. + #[cfg(feature = "cluster")] + pub cluster_node_id: Option, + /// Shared secret for authenticating internal cluster RPCs. + #[cfg(feature = "cluster")] + pub cluster_secret: Option, + /// TLS server config for encrypting inter-node communication. + #[cfg(feature = "cluster")] + pub tls_server_config: Option>, + /// This node's author identity for DAG actions. + #[cfg(feature = "dag")] + pub dag_author: Option, + /// Per-author monotonic sequence counter for DAG actions. + #[cfg(feature = "dag")] + pub dag_seq_counter: std::sync::Arc, + /// Ed25519 signing key for DAG actions (mandatory in production). + #[cfg(feature = "dag")] + pub dag_signing_key: Option>, } impl AppState { /// Creates a new `AppState` with an in-memory graph database. /// This is useful for testing or development environments. - pub fn new() -> Self { - let graph = GraphDB::memory().expect("Failed to create in-memory graph"); + pub fn new() -> crate::error::Result { + let graph = GraphDB::memory()?; let logic = RuleEngine::new(); - let memory = TitansMemory::agent_mode(); + let memory = IneruMemory::agent_mode(); #[cfg(feature = "auth")] let user_store = { @@ -49,21 +85,41 @@ impl AppState { store }; - Self { + Ok(Self { graph: Arc::new(RwLock::new(graph)), logic: Arc::new(RwLock::new(logic)), memory: Arc::new(RwLock::new(memory)), broadcaster: Arc::new(EventBroadcaster::new()), proof_store: Arc::new(ProofStore::new()), + sandbox_manager: Arc::new(SandboxManager::new()), + audit_log: Arc::new(RwLock::new(AuditLog::default())), #[cfg(feature = "auth")] user_store, - } + #[cfg(feature = "p2p")] + p2p: None, + #[cfg(feature = "cluster")] + wal: None, + #[cfg(feature = "cluster")] + raft: None, + #[cfg(feature = "cluster")] + cluster_node_id: None, + #[cfg(feature = "cluster")] + cluster_secret: None, + #[cfg(feature = "cluster")] + tls_server_config: None, + #[cfg(feature = "dag")] + dag_author: None, + #[cfg(feature = "dag")] + dag_seq_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)), + #[cfg(feature = "dag")] + dag_signing_key: None, + }) } /// Creates a new `AppState` with a pre-configured `GraphDB` instance. pub fn with_graph(graph: GraphDB) -> Self { let logic = RuleEngine::new(); - let memory = TitansMemory::agent_mode(); + let memory = IneruMemory::agent_mode(); #[cfg(feature = "auth")] let user_store = { @@ -79,9 +135,214 @@ impl AppState { memory: Arc::new(RwLock::new(memory)), broadcaster: Arc::new(EventBroadcaster::new()), proof_store: Arc::new(ProofStore::new()), + sandbox_manager: Arc::new(SandboxManager::new()), + audit_log: Arc::new(RwLock::new(AuditLog::default())), + #[cfg(feature = "auth")] + user_store, + #[cfg(feature = "p2p")] + p2p: None, + #[cfg(feature = "cluster")] + wal: None, + #[cfg(feature = "cluster")] + raft: None, + #[cfg(feature = "cluster")] + cluster_node_id: None, + #[cfg(feature = "cluster")] + cluster_secret: None, + #[cfg(feature = "cluster")] + tls_server_config: None, + #[cfg(feature = "dag")] + dag_author: None, + #[cfg(feature = "dag")] + dag_seq_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)), + #[cfg(feature = "dag")] + dag_signing_key: None, + } + } + + /// Creates a new `AppState` with a file-backed audit log. + pub fn with_audit_path(path: std::path::PathBuf) -> crate::error::Result { + let graph = GraphDB::memory()?; + let logic = RuleEngine::new(); + let memory = IneruMemory::agent_mode(); + + #[cfg(feature = "auth")] + let user_store = { + let store = Arc::new(UserStore::new()); + let _ = store.init_default_admin(); + store + }; + + Ok(Self { + graph: Arc::new(RwLock::new(graph)), + logic: Arc::new(RwLock::new(logic)), + memory: Arc::new(RwLock::new(memory)), + broadcaster: Arc::new(EventBroadcaster::new()), + proof_store: Arc::new(ProofStore::new()), + sandbox_manager: Arc::new(SandboxManager::new()), + audit_log: Arc::new(RwLock::new(AuditLog::with_path(10_000, path))), + #[cfg(feature = "auth")] + user_store, + #[cfg(feature = "p2p")] + p2p: None, + #[cfg(feature = "cluster")] + wal: None, + #[cfg(feature = "cluster")] + raft: None, + #[cfg(feature = "cluster")] + cluster_node_id: None, + #[cfg(feature = "cluster")] + cluster_secret: None, + #[cfg(feature = "cluster")] + tls_server_config: None, + #[cfg(feature = "dag")] + dag_author: None, + #[cfg(feature = "dag")] + dag_seq_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)), + #[cfg(feature = "dag")] + dag_signing_key: None, + }) + } + + /// Creates a new `AppState` with a configurable database path and optional audit log. + /// + /// - `":memory:"` — volatile in-memory storage. + /// - Any other path — Sled-backed persistent storage. + pub fn with_db_path( + db_path: &str, + audit_log_path: Option, + ) -> crate::error::Result { + let graph = if db_path == ":memory:" { + GraphDB::memory()? + } else { + // Ensure the parent directory exists + if let Some(parent) = Path::new(db_path).parent() { + std::fs::create_dir_all(parent).ok(); + } + GraphDB::sled(db_path)? + }; + + let logic = RuleEngine::new(); + + // Load Ineru snapshot if available next to the graph database + let memory = if db_path != ":memory:" { + let snapshot_path = Path::new(db_path) + .parent() + .unwrap_or(Path::new(".")) + .join("ineru.snapshot"); + if snapshot_path.exists() { + match IneruMemory::load_from_file(&snapshot_path) { + Ok(mem) => { + log::info!("Loaded Ineru snapshot from {}", snapshot_path.display()); + mem + } + Err(e) => { + log::warn!("Failed to load Ineru snapshot: {}. Starting fresh.", e); + IneruMemory::agent_mode() + } + } + } else { + IneruMemory::agent_mode() + } + } else { + IneruMemory::agent_mode() + }; + + let audit_log = if let Some(path) = audit_log_path { + AuditLog::with_path(10_000, path) + } else { + AuditLog::default() + }; + + // Create ProofStore — persistent if using Sled, in-memory otherwise. + // Uses a separate sled DB path (sibling to graph) to avoid lock contention. + let proof_store = if db_path != ":memory:" { + let proof_db_path = Path::new(db_path) + .parent() + .unwrap_or(Path::new(".")) + .join("proofs.sled"); + let proof_db_str = proof_db_path.to_string_lossy(); + match ProofStore::with_sled(&proof_db_str) { + Ok(ps) => { + log::info!("ProofStore using Sled backend at {}", proof_db_str); + Arc::new(ps) + } + Err(e) => { + log::warn!("Failed to open Sled ProofStore: {}. Falling back to in-memory.", e); + Arc::new(ProofStore::new()) + } + } + } else { + Arc::new(ProofStore::new()) + }; + + #[cfg(feature = "auth")] + let user_store = { + let store = Arc::new(UserStore::new()); + let _ = store.init_default_admin(); + store + }; + + Ok(Self { + graph: Arc::new(RwLock::new(graph)), + logic: Arc::new(RwLock::new(logic)), + memory: Arc::new(RwLock::new(memory)), + broadcaster: Arc::new(EventBroadcaster::new()), + proof_store, + sandbox_manager: Arc::new(SandboxManager::new()), + audit_log: Arc::new(RwLock::new(audit_log)), #[cfg(feature = "auth")] user_store, + #[cfg(feature = "p2p")] + p2p: None, + #[cfg(feature = "cluster")] + wal: None, + #[cfg(feature = "cluster")] + raft: None, + #[cfg(feature = "cluster")] + cluster_node_id: None, + #[cfg(feature = "cluster")] + cluster_secret: None, + #[cfg(feature = "cluster")] + tls_server_config: None, + #[cfg(feature = "dag")] + dag_author: None, + #[cfg(feature = "dag")] + dag_seq_counter: std::sync::Arc::new(std::sync::atomic::AtomicU64::new(1)), + #[cfg(feature = "dag")] + dag_signing_key: None, + }) + } + + + /// Flushes the graph database and saves the Ineru memory snapshot to disk. + /// + /// This should be called before shutdown or binary updates to ensure + /// no data is lost. + pub async fn flush(&self, snapshot_dir: Option<&Path>) -> crate::error::Result<()> { + // Flush graph database + { + let graph = self.graph.read().await; + graph.flush()?; + } + + // Flush proof store + if let Err(e) = self.proof_store.flush() { + log::warn!("Failed to flush proof store: {}", e); + } + + // Save Ineru memory snapshot + if let Some(dir) = snapshot_dir { + let snapshot_path = dir.join("ineru.snapshot"); + let memory = self.memory.read().await; + if let Err(e) = memory.save_to_file(&snapshot_path) { + log::warn!("Failed to save Ineru snapshot: {}", e); + } else { + log::info!("Ineru snapshot saved to {}", snapshot_path.display()); + } } + + Ok(()) } /// Returns an internal Cortex client configured for same-process access. @@ -108,7 +369,7 @@ impl AppState { impl Default for AppState { fn default() -> Self { - Self::new() + Self::new().expect("Failed to create default AppState with in-memory graph") } } @@ -155,8 +416,12 @@ impl EventBroadcaster { /// Decrements the client count when a client unsubscribes. pub fn unsubscribe(&self) { - self.client_count - .fetch_sub(1, std::sync::atomic::Ordering::SeqCst); + // Use fetch_update to prevent underflow wrapping to usize::MAX. + let _ = self.client_count.fetch_update( + std::sync::atomic::Ordering::SeqCst, + std::sync::atomic::Ordering::SeqCst, + |current| current.checked_sub(1), + ); } /// Broadcasts an `Event` to all active subscribers. @@ -207,3 +472,76 @@ impl Event { serde_json::to_string(self).unwrap_or_default() } } + +// --------------------------------------------------------------------------- +// Sandbox Manager +// --------------------------------------------------------------------------- + +/// Entry for a sandbox namespace with TTL. +struct SandboxEntry { + namespace: String, + created_at: std::time::Instant, + ttl: std::time::Duration, +} + +/// Manager for temporary sandbox namespaces used by skill verification. +/// +/// Sandboxes are isolated graph namespaces with a time-to-live (TTL). +/// After TTL expiration, the sandbox should be cleaned up. +pub struct SandboxManager { + entries: RwLock>, +} + +impl SandboxManager { + /// Creates a new, empty `SandboxManager`. + pub fn new() -> Self { + Self { + entries: RwLock::new(std::collections::HashMap::new()), + } + } + + /// Creates a new sandbox entry with the given ID, namespace, and TTL. + pub async fn create(&self, id: String, namespace: String, ttl_seconds: u64) { + let entry = SandboxEntry { + namespace, + created_at: std::time::Instant::now(), + ttl: std::time::Duration::from_secs(ttl_seconds), + }; + let mut entries = self.entries.write().await; + entries.insert(id, entry); + } + + /// Removes a sandbox by ID, returning the namespace if found. + pub async fn remove(&self, id: &str) -> Option { + let mut entries = self.entries.write().await; + entries.remove(id).map(|e| e.namespace) + } + + /// Returns the namespace for a sandbox if it exists and hasn't expired. + pub async fn get(&self, id: &str) -> Option { + let entries = self.entries.read().await; + entries.get(id).and_then(|e| { + if e.created_at.elapsed() < e.ttl { + Some(e.namespace.clone()) + } else { + None + } + }) + } + + /// Returns a list of all expired sandbox IDs for cleanup. + pub async fn expired(&self) -> Vec { + let entries = self.entries.read().await; + entries + .iter() + .filter(|(_, e)| e.created_at.elapsed() >= e.ttl) + .map(|(id, _)| id.clone()) + .collect() + } +} + +impl Default for SandboxManager { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/aingle_cortex/src/wasm_types.rs b/crates/aingle_cortex/src/wasm_types.rs new file mode 100644 index 00000000..63ed07b5 --- /dev/null +++ b/crates/aingle_cortex/src/wasm_types.rs @@ -0,0 +1,149 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! WASM boundary types for semantic graph and memory operations. +//! +//! These types define the interface between zome code and the Cortex +//! knowledge layer. They were previously part of `aingle_zome_types` +//! but are now self-contained to avoid pulling in legacy dependencies. + +use serde::{Deserialize, Serialize}; + +/// A single RDF triple: subject-predicate-object. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Triple { + /// The subject of the triple (e.g., "mayros:agent:alice"). + pub subject: String, + /// The predicate/relationship (e.g., "mayros:memory:category"). + pub predicate: String, + /// The object/value. + pub object: ObjectValue, +} + +/// The object component of a triple, which can be a node reference or a literal value. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(tag = "type", content = "value")] +pub enum ObjectValue { + /// A reference to another node in the graph. + Node(String), + /// A string literal value. + Literal(String), + /// A numeric literal value. + Number(f64), + /// A boolean literal value. + Boolean(bool), +} + +/// A pattern for matching triples, where None means "match any". +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct TriplePattern { + /// Match triples with this subject (None = any). + pub subject: Option, + /// Match triples with this predicate (None = any). + pub predicate: Option, + /// Match triples with this object (None = any). + pub object: Option, +} + +// -- Graph Query -- + +/// Input for querying the semantic graph from a zome. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GraphQueryInput { + /// Optional triple pattern to match against. + pub pattern: Option, + /// Filter by subject. + pub subject: Option, + /// Filter by predicate. + pub predicate: Option, + /// Maximum number of results to return. + pub limit: Option, +} + +/// Output from a graph query. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GraphQueryOutput { + /// The matching triples. + pub triples: Vec, + /// Total number of matching triples (may exceed limit). + pub total: u64, +} + +// -- Graph Store -- + +/// Input for storing a triple in the semantic graph. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GraphStoreInput { + /// The subject of the triple. + pub subject: String, + /// The predicate of the triple. + pub predicate: String, + /// The object of the triple. + pub object: ObjectValue, +} + +/// Output after storing a triple. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GraphStoreOutput { + /// The unique identifier for the stored triple. + pub triple_id: String, +} + +// -- Memory Recall -- + +/// Input for recalling memories from the Titans system. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MemoryRecallInput { + /// The query text to search for. + pub query: String, + /// Optional filter by entry type. + pub entry_type: Option, + /// Maximum number of results. + pub limit: Option, +} + +/// A single memory result. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MemoryResult { + /// Unique identifier for this memory. + pub id: String, + /// The memory content. + pub data: String, + /// The type of memory entry. + pub entry_type: String, + /// Tags associated with this memory. + pub tags: Vec, + /// Importance score (0.0 to 1.0). + pub importance: f32, + /// When this memory was created (ISO 8601). + pub created_at: String, +} + +/// Output from a memory recall operation. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MemoryRecallOutput { + /// The matching memory results. + pub results: Vec, +} + +// -- Memory Remember -- + +/// Input for storing a new memory in the Titans system. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MemoryRememberInput { + /// The data to remember. + pub data: String, + /// The type of entry (e.g., "fact", "preference", "decision"). + pub entry_type: String, + /// Tags for categorization. + pub tags: Vec, + /// Importance score (0.0 to 1.0). + pub importance: f32, +} + +/// Output after storing a memory. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MemoryRememberOutput { + /// The unique identifier for the stored memory. + pub id: String, +} diff --git a/crates/aingle_cortex/tests/cluster_integration_test.rs b/crates/aingle_cortex/tests/cluster_integration_test.rs new file mode 100644 index 00000000..49695534 --- /dev/null +++ b/crates/aingle_cortex/tests/cluster_integration_test.rs @@ -0,0 +1,359 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Integration tests for 3-node Raft cluster. +//! +//! These tests boot multiple CortexServer instances with in-memory +//! databases, initialize Raft consensus, and verify write replication, +//! leader election, and graceful node leave. + +#![cfg(feature = "cluster")] + +use aingle_cortex::cluster_init::{ClusterConfig, init_cluster}; +use aingle_cortex::{CortexConfig, CortexServer}; +use std::time::Duration; +use tokio::time::sleep; + +/// Find a free TCP port by binding to port 0. +async fn free_port() -> u16 { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + listener.local_addr().unwrap().port() +} + +/// Boots a single cluster node and returns (server_handle, shutdown_tx). +async fn boot_node( + node_id: u64, + port: u16, + peers: Vec, + secret: &str, + wal_dir: &str, +) -> (tokio::task::JoinHandle<()>, tokio::sync::watch::Sender) { + let mut config = CortexConfig::default() + .with_host("127.0.0.1") + .with_port(port); + config.db_path = Some(":memory:".to_string()); + + let mut server = CortexServer::new(config).unwrap(); + + let cluster_config = ClusterConfig { + enabled: true, + node_id, + peers, + wal_dir: Some(wal_dir.to_string()), + secret: Some(secret.to_string()), + tls: false, + tls_cert: None, + tls_key: None, + }; + + let bind_addr = format!("127.0.0.1:{port}"); + let p2p_port = free_port().await; + let p2p_addr = format!("127.0.0.1:{p2p_port}"); + + init_cluster(&mut server, &cluster_config, &bind_addr, &p2p_addr) + .await + .expect("cluster init failed"); + + let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false); + + let handle = tokio::spawn(async move { + let shutdown_signal = async move { + while !*shutdown_rx.borrow_and_update() { + if shutdown_rx.changed().await.is_err() { + break; + } + } + }; + let _ = server.run_with_shutdown(shutdown_signal).await; + }); + + (handle, shutdown_tx) +} + +/// Gracefully shut down nodes with enough time for Raft to stop. +async fn shutdown_nodes(nodes: Vec<(tokio::task::JoinHandle<()>, tokio::sync::watch::Sender)>) { + for (_, tx) in &nodes { + tx.send(true).ok(); + } + for (h, _) in nodes { + // Raft shutdown can take up to 10s, give 15s total + let _ = tokio::time::timeout(Duration::from_secs(15), h).await; + } +} + +/// Wait for a node to report a leader via /api/v1/cluster/status. +async fn wait_for_leader(port: u16, timeout_secs: u64) -> Option { + let client = reqwest::Client::new(); + let deadline = tokio::time::Instant::now() + Duration::from_secs(timeout_secs); + + while tokio::time::Instant::now() < deadline { + if let Ok(resp) = client + .get(format!("http://127.0.0.1:{port}/api/v1/cluster/status")) + .send() + .await + { + if let Ok(body) = resp.json::().await { + if let Some(leader_id) = body["leader_id"].as_u64() { + if leader_id > 0 { + return Some(leader_id); + } + } + } + } + sleep(Duration::from_millis(250)).await; + } + None +} + +#[tokio::test] +async fn test_single_node_cluster_bootstrap() { + let tmp = tempfile::tempdir().unwrap(); + let port = free_port().await; + let wal_dir = tmp.path().join("wal1"); + + let (handle, shutdown_tx) = boot_node( + 1, + port, + vec![], // no peers = bootstrap + "test-secret-at-least-16-bytes", + wal_dir.to_str().unwrap(), + ) + .await; + + // Wait for server to start and Raft election to complete + sleep(Duration::from_secs(2)).await; + + // Should become leader of a single-node cluster + let leader = wait_for_leader(port, 10).await; + assert_eq!(leader, Some(1), "Single node should be leader"); + + // Write a triple + let client = reqwest::Client::new(); + let resp = client + .post(format!("http://127.0.0.1:{port}/api/v1/triples")) + .json(&serde_json::json!({ + "subject": "alice", + "predicate": "knows", + "object": "bob" + })) + .send() + .await + .unwrap(); + let status = resp.status(); + if !status.is_success() { + let body = resp.text().await.unwrap_or_default(); + panic!("Write should succeed: {status} — {body}"); + } + + // Read it back + let resp = client + .get(format!( + "http://127.0.0.1:{port}/api/v1/triples?subject=alice" + )) + .send() + .await + .unwrap(); + assert!( + resp.status().is_success(), + "Read should succeed: {}", + resp.status() + ); + let body: serde_json::Value = resp.json().await.unwrap(); + let triples = body["triples"].as_array().expect("triples field should be an array"); + assert!( + !triples.is_empty(), + "Should find the triple we just wrote" + ); + + // Verify cluster members endpoint + let resp = client + .get(format!( + "http://127.0.0.1:{port}/api/v1/cluster/members" + )) + .send() + .await + .unwrap(); + assert!( + resp.status().is_success(), + "Members endpoint should succeed: {}", + resp.status() + ); + let members: Vec = resp.json().await.unwrap(); + assert_eq!(members.len(), 1, "Should have exactly 1 member"); + + // Shutdown + shutdown_nodes(vec![(handle, shutdown_tx)]).await; +} + +#[tokio::test] +async fn test_three_node_cluster_replication() { + let tmp = tempfile::tempdir().unwrap(); + let secret = "cluster-test-secret-32bytes!"; + + // Allocate 3 free ports + let port1 = free_port().await; + let port2 = free_port().await; + let port3 = free_port().await; + + let wal1 = tmp.path().join("wal1"); + let wal2 = tmp.path().join("wal2"); + let wal3 = tmp.path().join("wal3"); + + // Boot node 1 as bootstrap (no peers) + let (h1, tx1) = boot_node( + 1, + port1, + vec![], + secret, + wal1.to_str().unwrap(), + ) + .await; + + // Wait for Raft election (timeout_min=1500ms, give 2s + buffer) + sleep(Duration::from_secs(2)).await; + + // Wait for node 1 to become leader + let leader = wait_for_leader(port1, 10).await; + assert_eq!(leader, Some(1), "Node 1 should be leader after bootstrap"); + + // Boot node 2, joining via node 1 + let (h2, tx2) = boot_node( + 2, + port2, + vec![format!("127.0.0.1:{port1}")], + secret, + wal2.to_str().unwrap(), + ) + .await; + + // Boot node 3, joining via node 1 + let (h3, tx3) = boot_node( + 3, + port3, + vec![format!("127.0.0.1:{port1}")], + secret, + wal3.to_str().unwrap(), + ) + .await; + + // Wait for join operations to complete (join has 2s initial delay + processing) + sleep(Duration::from_secs(6)).await; + + // Verify members from the leader + let client = reqwest::Client::new(); + let resp = client + .get(format!( + "http://127.0.0.1:{port1}/api/v1/cluster/members" + )) + .send() + .await + .unwrap(); + let members: Vec = resp.json().await.unwrap(); + assert!( + members.len() >= 2, + "Should have at least 2 members (got {}): {:?}", + members.len(), + members + ); + + // Write a triple to the leader + let resp = client + .post(format!("http://127.0.0.1:{port1}/api/v1/triples")) + .json(&serde_json::json!({ + "subject": "cluster_test", + "predicate": "replicated_to", + "object": "all_nodes" + })) + .send() + .await + .unwrap(); + let status = resp.status(); + assert!( + status.is_success(), + "Write to leader should succeed: {status}" + ); + + // Wait for replication + sleep(Duration::from_secs(2)).await; + + // Read from follower node 2 — the triple should be replicated + let resp = client + .get(format!( + "http://127.0.0.1:{port2}/api/v1/triples?subject=cluster_test" + )) + .send() + .await + .unwrap(); + assert!(resp.status().is_success(), "Read from node 2 failed: {}", resp.status()); + let body: serde_json::Value = resp.json().await.unwrap(); + let triples = body["triples"].as_array().expect("triples field should be an array"); + assert!( + !triples.is_empty(), + "Follower (node 2) should have the replicated triple" + ); + + // Also verify from follower node 3 + let resp = client + .get(format!( + "http://127.0.0.1:{port3}/api/v1/triples?subject=cluster_test" + )) + .send() + .await + .unwrap(); + assert!(resp.status().is_success(), "Read from node 3 failed: {}", resp.status()); + let body: serde_json::Value = resp.json().await.unwrap(); + let triples = body["triples"].as_array().expect("triples field should be an array"); + assert!( + !triples.is_empty(), + "Follower (node 3) should have the replicated triple" + ); + + // Shutdown all nodes + shutdown_nodes(vec![(h1, tx1), (h2, tx2), (h3, tx3)]).await; +} + +#[tokio::test] +async fn test_cluster_wal_stats() { + let tmp = tempfile::tempdir().unwrap(); + let port = free_port().await; + let wal_dir = tmp.path().join("wal_stats"); + + let (handle, shutdown_tx) = boot_node( + 1, + port, + vec![], + "test-secret-at-least-16-bytes", + wal_dir.to_str().unwrap(), + ) + .await; + + sleep(Duration::from_secs(2)).await; + + let client = reqwest::Client::new(); + + // Check WAL stats endpoint + let resp = client + .get(format!( + "http://127.0.0.1:{port}/api/v1/cluster/wal/stats" + )) + .send() + .await + .unwrap(); + assert!(resp.status().is_success(), "WAL stats failed: {}", resp.status()); + let stats: serde_json::Value = resp.json().await.unwrap(); + assert!(stats["segment_count"].is_number(), "segment_count should be a number: {stats}"); + + // Verify WAL integrity + let resp = client + .post(format!( + "http://127.0.0.1:{port}/api/v1/cluster/wal/verify" + )) + .send() + .await + .unwrap(); + assert!(resp.status().is_success(), "WAL verify failed: {}", resp.status()); + let verify: serde_json::Value = resp.json().await.unwrap(); + assert_eq!(verify["valid"], true, "WAL should be valid: {verify}"); + + shutdown_nodes(vec![(handle, shutdown_tx)]).await; +} diff --git a/crates/aingle_cortex/tests/data_integrity_test.rs b/crates/aingle_cortex/tests/data_integrity_test.rs new file mode 100644 index 00000000..0e261328 --- /dev/null +++ b/crates/aingle_cortex/tests/data_integrity_test.rs @@ -0,0 +1,544 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Cross-subsystem data integrity tests. +//! +//! Verifies that data flows correctly between all AIngle subsystems: +//! - Graph ↔ Proof Store ↔ Raft Snapshots +//! - Graph ↔ DAG ↔ Triple materialization +//! - State flush/restore round-trip +//! - Batch insert atomicity + +use aingle_cortex::state::AppState; +use aingle_cortex::proofs::{ProofStore, ProofType, SubmitProofRequest, ProofMetadata}; + +// ============================================================================ +// 1. ProofStore persistence round-trip (Sled backend) +// ============================================================================ + +#[tokio::test] +async fn test_proof_store_sled_roundtrip_data_integrity() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + let mut proof_ids = Vec::new(); + + // Phase 1: Write 20 proofs of various types + { + let store = ProofStore::with_sled(path).unwrap(); + + let types = [ + ProofType::Schnorr, + ProofType::Equality, + ProofType::Membership, + ProofType::Range, + ProofType::Knowledge, + ]; + + for (i, proof_type) in types.iter().cycle().take(20).enumerate() { + let request = SubmitProofRequest { + proof_type: proof_type.clone(), + proof_data: serde_json::json!({ + "index": i, + "payload": format!("proof-data-{}", i), + }), + metadata: Some(ProofMetadata { + submitter: Some(format!("user-{}", i % 3)), + tags: vec![format!("tag-{}", i)], + extra: Default::default(), + }), + }; + let id = store.submit(request).await.unwrap(); + proof_ids.push(id); + } + + assert_eq!(store.count().await, 20); + store.flush().unwrap(); + } + + // Phase 2: Reopen and verify every proof intact + { + let store = ProofStore::with_sled(path).unwrap(); + assert_eq!(store.count().await, 20, "count mismatch after reopen"); + + for (i, id) in proof_ids.iter().enumerate() { + let proof = store.get(id).await + .unwrap_or_else(|| panic!("proof {} (id={}) missing after reopen", i, id)); + + // Verify data field contains correct index + let data: serde_json::Value = serde_json::from_slice(&proof.data).unwrap(); + let payload = data.get("index").and_then(|v| v.as_u64()).unwrap(); + assert_eq!(payload as usize, i, "data mismatch for proof {}", i); + + // Verify metadata + let submitter = proof.metadata.submitter.as_ref().unwrap(); + assert_eq!(submitter, &format!("user-{}", i % 3)); + } + + // Stats should match + let stats = store.stats().await; + assert_eq!(stats.total_proofs, 20); + assert_eq!(stats.proofs_by_type.get("schnorr"), Some(&4)); + assert_eq!(stats.proofs_by_type.get("equality"), Some(&4)); + assert_eq!(stats.proofs_by_type.get("membership"), Some(&4)); + assert_eq!(stats.proofs_by_type.get("range"), Some(&4)); + assert_eq!(stats.proofs_by_type.get("knowledge"), Some(&4)); + } + + // Phase 3: Delete half, reopen, verify + { + let store = ProofStore::with_sled(path).unwrap(); + for id in &proof_ids[0..10] { + assert!(store.delete(id).await, "delete should succeed for {}", id); + } + store.flush().unwrap(); + } + { + let store = ProofStore::with_sled(path).unwrap(); + assert_eq!(store.count().await, 10, "count after delete+reopen"); + + // Deleted ones should be gone + for id in &proof_ids[0..10] { + assert!(store.get(id).await.is_none(), "deleted proof {} should not exist", id); + } + // Remaining ones should be intact + for (i, id) in proof_ids[10..20].iter().enumerate() { + let proof = store.get(id).await + .unwrap_or_else(|| panic!("remaining proof {} missing", i + 10)); + let data: serde_json::Value = serde_json::from_slice(&proof.data).unwrap(); + assert_eq!(data["index"].as_u64().unwrap() as usize, i + 10); + } + } +} + +// ============================================================================ +// 2. Graph + DAG data consistency +// ============================================================================ + +#[tokio::test] +async fn test_graph_dag_triple_materialization_consistency() { + use aingle_graph::{GraphDB, NodeId, Predicate, Value, Triple, TriplePattern}; + + let mut graph = GraphDB::memory().unwrap(); + graph.enable_dag(); + + // Ensure DAG initialized + if let Some(dag_store) = graph.dag_store() { + let _ = dag_store.init_or_migrate(0); + } + + // Insert 50 triples via graph (materialized view) + let mut triple_ids = Vec::new(); + for i in 0..50 { + let triple = Triple::new( + NodeId::named(&format!("entity:{}", i)), + Predicate::named("has_value"), + Value::Integer(i * 100), + ); + let tid = graph.insert(triple).unwrap(); + triple_ids.push(tid); + } + + // Verify all 50 exist in graph + assert_eq!(graph.count(), 50); + + // Verify each triple can be retrieved by ID + for (i, tid) in triple_ids.iter().enumerate() { + let triple = graph.get(tid).unwrap() + .unwrap_or_else(|| panic!("triple {} not found by ID", i)); + assert_eq!(triple.object, Value::Integer(i as i64 * 100), + "value mismatch for triple {}", i); + } + + // Verify pattern queries return correct results + for i in 0..50 { + let pattern = TriplePattern::subject(NodeId::named(&format!("entity:{}", i))); + let results = graph.find(pattern).unwrap(); + assert_eq!(results.len(), 1, "entity:{} should have exactly 1 triple", i); + assert_eq!(results[0].object, Value::Integer(i * 100)); + } + + // Delete odd-numbered triples + for i in (1..50).step_by(2) { + let deleted = graph.delete(&triple_ids[i]).unwrap(); + assert!(deleted, "delete should succeed for triple {}", i); + } + assert_eq!(graph.count(), 25); + + // Verify even ones remain, odd ones gone + for i in 0..50 { + let exists = graph.get(&triple_ids[i]).unwrap().is_some(); + if i % 2 == 0 { + assert!(exists, "even triple {} should still exist", i); + } else { + assert!(!exists, "odd triple {} should be deleted", i); + } + } +} + +// ============================================================================ +// 3. Batch insert atomicity — index consistency +// ============================================================================ + +#[tokio::test] +async fn test_batch_insert_index_consistency() { + use aingle_graph::{GraphDB, NodeId, Predicate, Value, Triple, TriplePattern}; + + let graph = GraphDB::memory().unwrap(); + + // Batch insert 100 triples + let triples: Vec = (0..100) + .map(|i| Triple::new( + NodeId::named(&format!("batch:{}", i)), + Predicate::named("batch_value"), + Value::Integer(i), + )) + .collect(); + + let ids = graph.insert_batch(triples).unwrap(); + assert_eq!(ids.len(), 100); + assert_eq!(graph.count(), 100); + + // Verify every triple is findable by subject pattern (uses SPO index) + for i in 0..100 { + let pattern = TriplePattern::subject(NodeId::named(&format!("batch:{}", i))); + let results = graph.find(pattern).unwrap(); + assert_eq!(results.len(), 1, "batch:{} should be findable via index", i); + assert_eq!(results[0].object, Value::Integer(i)); + } + + // Verify predicate index works + let by_pred = graph.find( + TriplePattern::predicate(Predicate::named("batch_value")) + ).unwrap(); + assert_eq!(by_pred.len(), 100, "predicate index should find all 100"); + + // Re-batch the same triples — should skip duplicates, no count change + let triples2: Vec = (0..100) + .map(|i| Triple::new( + NodeId::named(&format!("batch:{}", i)), + Predicate::named("batch_value"), + Value::Integer(i), + )) + .collect(); + let ids2 = graph.insert_batch(triples2).unwrap(); + assert_eq!(ids2.len(), 100); + assert_eq!(graph.count(), 100, "duplicates should not increase count"); +} + +// ============================================================================ +// 4. AppState flush/restore round-trip +// ============================================================================ + +#[tokio::test] +async fn test_app_state_flush_restore_roundtrip() { + use aingle_graph::{NodeId, Predicate, Value, Triple, TriplePattern}; + + let dir = tempfile::TempDir::new().unwrap(); + let db_path = dir.path().join("graph.sled"); + let db_str = db_path.to_str().unwrap(); + + // Phase 1: Create state, insert data, flush + let proof_ids = { + let state = AppState::with_db_path(db_str, None).unwrap(); + + // Insert triples + { + let graph = state.graph.read().await; + for i in 0..10 { + let triple = Triple::new( + NodeId::named(&format!("node:{}", i)), + Predicate::named("value"), + Value::String(format!("data-{}", i)), + ); + graph.insert(triple).unwrap(); + } + assert_eq!(graph.count(), 10); + } + + // Submit proofs + let mut ids = Vec::new(); + for i in 0..5 { + let request = SubmitProofRequest { + proof_type: ProofType::Schnorr, + proof_data: serde_json::json!({"flush_test": i}), + metadata: None, + }; + ids.push(state.proof_store.submit(request).await.unwrap()); + } + + // Flush everything + let snapshot_dir = dir.path().to_path_buf(); + state.flush(Some(snapshot_dir.as_path())).await.unwrap(); + ids + }; + // Explicit drop ensures sled DB is fully released before reopen + // (all Arcs from the block above are now dropped) + + // Phase 2: Reopen, verify data survived + { + let state = AppState::with_db_path(db_str, None).unwrap(); + + // Graph triples + { + let graph = state.graph.read().await; + assert_eq!(graph.count(), 10, "graph triples should survive restart"); + + for i in 0..10 { + let pattern = TriplePattern::subject(NodeId::named(&format!("node:{}", i))); + let results = graph.find(pattern).unwrap(); + assert_eq!(results.len(), 1, "node:{} missing after restart", i); + assert_eq!( + results[0].object, + Value::String(format!("data-{}", i)), + "data mismatch for node:{}", i + ); + } + } + + // Proofs — verify each by ID + let proof_count = state.proof_store.count().await; + assert_eq!(proof_count, 5, "proofs should survive restart"); + for (i, id) in proof_ids.iter().enumerate() { + let proof = state.proof_store.get(id).await + .unwrap_or_else(|| panic!("proof {} missing after restart", i)); + let data: serde_json::Value = serde_json::from_slice(&proof.data).unwrap(); + assert_eq!(data["flush_test"].as_u64().unwrap(), i as u64); + } + } +} + +// ============================================================================ +// 5. Raft snapshot round-trip with proofs +// ============================================================================ + +#[tokio::test] +async fn test_raft_snapshot_with_proofs_roundtrip() { + use aingle_raft::state_machine::{ + ClusterSnapshot, TripleSnapshot, ProofSnapshot, + }; + + let snapshot = ClusterSnapshot { + triples: vec![ + TripleSnapshot { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }, + TripleSnapshot { + subject: "bob".into(), + predicate: "age".into(), + object: serde_json::json!(30), + }, + ], + ineru_ltm: vec![10, 20, 30], + last_applied_index: 42, + last_applied_term: 5, + dag_tips: vec![], + proofs: vec![ + ProofSnapshot { + id: "proof-001".into(), + proof_type: "schnorr".into(), + data: vec![1, 2, 3, 4], + created_at: "2026-03-16T00:00:00Z".into(), + verified: true, + verified_at: Some("2026-03-16T00:01:00Z".into()), + metadata: serde_json::json!({"submitter": "alice"}), + }, + ProofSnapshot { + id: "proof-002".into(), + proof_type: "membership".into(), + data: vec![5, 6, 7, 8], + created_at: "2026-03-16T00:02:00Z".into(), + verified: false, + verified_at: None, + metadata: serde_json::json!({}), + }, + ], + checksum: String::new(), + }; + + // Serialize + let bytes = snapshot.to_bytes().unwrap(); + assert!(!bytes.is_empty()); + + // Deserialize + let restored = ClusterSnapshot::from_bytes(&bytes).unwrap(); + + // Verify triples + assert_eq!(restored.triples.len(), 2); + assert_eq!(restored.triples[0].subject, "alice"); + assert_eq!(restored.triples[1].object, serde_json::json!(30)); + + // Verify ineru + assert_eq!(restored.ineru_ltm, vec![10, 20, 30]); + + // Verify proofs + assert_eq!(restored.proofs.len(), 2); + assert_eq!(restored.proofs[0].id, "proof-001"); + assert_eq!(restored.proofs[0].proof_type, "schnorr"); + assert_eq!(restored.proofs[0].data, vec![1, 2, 3, 4]); + assert!(restored.proofs[0].verified); + assert_eq!(restored.proofs[0].verified_at.as_deref(), Some("2026-03-16T00:01:00Z")); + assert_eq!(restored.proofs[1].id, "proof-002"); + assert!(!restored.proofs[1].verified); + assert!(restored.proofs[1].verified_at.is_none()); + + // Verify metadata + assert_eq!(restored.last_applied_index, 42); + assert_eq!(restored.last_applied_term, 5); + + // Verify checksum was computed and matches + assert!(!restored.checksum.is_empty()); +} + +// ============================================================================ +// 6. Snapshot checksum includes proofs +// ============================================================================ + +#[tokio::test] +async fn test_snapshot_checksum_changes_with_proofs() { + use aingle_raft::state_machine::{ + ClusterSnapshot, TripleSnapshot, ProofSnapshot, + }; + + // Snapshot without proofs + let snap_no_proofs = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "a".into(), + predicate: "b".into(), + object: serde_json::json!("c"), + }], + ineru_ltm: vec![], + last_applied_index: 1, + last_applied_term: 1, + dag_tips: vec![], + proofs: vec![], + checksum: String::new(), + }; + + // Same snapshot WITH proofs + let snap_with_proofs = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "a".into(), + predicate: "b".into(), + object: serde_json::json!("c"), + }], + ineru_ltm: vec![], + last_applied_index: 1, + last_applied_term: 1, + dag_tips: vec![], + proofs: vec![ProofSnapshot { + id: "p1".into(), + proof_type: "schnorr".into(), + data: vec![1], + created_at: "2026-01-01T00:00:00Z".into(), + verified: false, + verified_at: None, + metadata: serde_json::json!({}), + }], + checksum: String::new(), + }; + + let bytes1 = snap_no_proofs.to_bytes().unwrap(); + let bytes2 = snap_with_proofs.to_bytes().unwrap(); + + let r1 = ClusterSnapshot::from_bytes(&bytes1).unwrap(); + let r2 = ClusterSnapshot::from_bytes(&bytes2).unwrap(); + + // Checksums should differ + assert_ne!(r1.checksum, r2.checksum, + "checksum should change when proofs are added"); +} + +// ============================================================================ +// 7. Graph Sled persistence — data survives restart +// ============================================================================ + +#[tokio::test] +async fn test_graph_sled_persistence_full_cycle() { + use aingle_graph::{GraphDB, NodeId, Predicate, Value, Triple, TriplePattern}; + + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().join("test.sled"); + let path_str = path.to_str().unwrap(); + + // Write + { + let graph = GraphDB::sled(path_str).unwrap(); + for i in 0..25 { + let triple = Triple::new( + NodeId::named(&format!("persist:{}", i)), + Predicate::named("data"), + Value::Float(i as f64 * 1.5), + ); + graph.insert(triple).unwrap(); + } + assert_eq!(graph.count(), 25); + graph.flush().unwrap(); + } + + // Reopen and verify + { + let graph = GraphDB::sled(path_str).unwrap(); + assert_eq!(graph.count(), 25, "sled graph should persist"); + + for i in 0..25 { + let pattern = TriplePattern::subject(NodeId::named(&format!("persist:{}", i))); + let results = graph.find(pattern).unwrap(); + assert_eq!(results.len(), 1, "persist:{} missing", i); + match &results[0].object { + Value::Float(f) => { + let expected = i as f64 * 1.5; + assert!((f - expected).abs() < 0.001, "float mismatch for {}", i); + } + other => panic!("expected Float, got {:?} for persist:{}", other, i), + } + } + } +} + +// ============================================================================ +// 8. Audit log file-backed integrity +// ============================================================================ + +#[tokio::test] +async fn test_audit_log_fsync_integrity() { + use aingle_cortex::rest::audit::{AuditLog, AuditEntry}; + + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().join("audit_test.jsonl"); + + // Write entries + { + let mut log = AuditLog::with_path(10_000, path.clone()); + for i in 0..50 { + log.record(AuditEntry { + timestamp: format!("2026-03-16T00:{:02}:00Z", i), + user_id: format!("user-{}", i % 5), + namespace: Some("test".to_string()), + action: if i % 3 == 0 { "create".into() } else { "read".into() }, + resource: format!("/api/v1/triples/{}", i), + details: Some(format!("detail-{}", i)), + request_id: Some(format!("req-{}", i)), + }); + } + assert_eq!(log.len(), 50); + } + + // Reopen — entries should be restored from JSONL + { + let log = AuditLog::with_path(10_000, path.clone()); + assert_eq!(log.len(), 50, "audit entries should survive restart"); + + // Verify query filters work + let by_user = log.query(Some("user-0"), None, None, None, None, 100); + assert_eq!(by_user.len(), 10, "10 entries per user out of 50"); + + let by_action = log.query(None, None, Some("create"), None, None, 100); + assert_eq!(by_action.len(), 17, "17 create actions (0,3,6,...,48)"); + + let by_ns = log.query(None, Some("test"), None, None, None, 100); + assert_eq!(by_ns.len(), 50, "all entries in 'test' namespace"); + } +} diff --git a/crates/aingle_cortex/tests/graphql_subscriptions_test.rs b/crates/aingle_cortex/tests/graphql_subscriptions_test.rs index f3650800..86df9fd0 100644 --- a/crates/aingle_cortex/tests/graphql_subscriptions_test.rs +++ b/crates/aingle_cortex/tests/graphql_subscriptions_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for GraphQL subscriptions //! //! Tests WebSocket-based real-time updates for: @@ -9,7 +12,7 @@ #[cfg(feature = "graphql")] mod tests { use aingle_cortex::{AppState, CortexConfig, CortexServer}; - use aingle_graph::{GraphDB, NodeId, Triple, Value}; + use aingle_graph::{GraphDB, NodeId, Predicate, Triple, Value}; use futures::StreamExt; use std::time::Duration; use tokio::time::timeout; @@ -26,10 +29,10 @@ mod tests { let mut graph = state.graph.write().await; let triple = Triple::new( NodeId::named(subject), - NodeId::named(predicate), + Predicate::named(predicate), Value::String(object.to_string()), ); - graph.add_triple(triple).expect("Failed to add triple"); + graph.insert(triple).expect("Failed to add triple"); } /// Helper to broadcast an event diff --git a/crates/aingle_cortex/tests/proof_system_test.rs b/crates/aingle_cortex/tests/proof_system_test.rs index e61e8aa9..a0e451c0 100644 --- a/crates/aingle_cortex/tests/proof_system_test.rs +++ b/crates/aingle_cortex/tests/proof_system_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for the proof storage and verification system use aingle_cortex::prelude::*; @@ -304,7 +307,7 @@ async fn test_schnorr_proof_verification() { #[tokio::test] async fn test_app_state_integration() { - let state = AppState::new(); + let state = AppState::new().unwrap(); // Test that proof store is accessible let count = state.proof_store.count().await; diff --git a/crates/aingle_cortex/tests/rate_limiting_test.rs b/crates/aingle_cortex/tests/rate_limiting_test.rs index be1377a3..19687210 100644 --- a/crates/aingle_cortex/tests/rate_limiting_test.rs +++ b/crates/aingle_cortex/tests/rate_limiting_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for rate limiting middleware //! //! Tests the token bucket rate limiter: diff --git a/crates/aingle_graph/Cargo.toml b/crates/aingle_graph/Cargo.toml index bf5bbd5a..c0d45378 100644 --- a/crates/aingle_graph/Cargo.toml +++ b/crates/aingle_graph/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_graph" -version = "0.2.0" +version = "0.6.3" description = "Native GraphDB for AIngle - Semantic triple store with SPO indexes" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_graph" @@ -20,20 +20,26 @@ rocksdb-backend = ["dep:rocksdb"] sqlite-backend = ["dep:rusqlite"] # RDF support rdf = ["dep:rio_turtle", "dep:rio_api"] +# CRDT conflict resolution (for clustering) +crdt = ["dep:uuid"] +# Semantic DAG — hash-linked action history +dag = [] +# Signed DAG actions with Ed25519 PKI (requires dag) +dag-sign = ["dag", "dep:ed25519-dalek", "dep:rand"] # Full features -full = ["sled-backend", "rocksdb-backend", "sqlite-backend", "rdf"] +full = ["sled-backend", "rocksdb-backend", "sqlite-backend", "rdf", "crdt"] [dependencies] # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -bincode = "1.3" +bincode = { version = "2.0.1", features = ["serde"] } # Hashing -blake3 = "1.5" +blake3 = "1.8" # Collections -indexmap = { version = "2.0", features = ["serde"] } +indexmap = { version = "2.13", features = ["serde"] } # Error handling thiserror = "2.0" @@ -46,16 +52,23 @@ chrono = { version = "0.4", features = ["serde"] } # Storage backends (optional) sled = { version = "0.34", optional = true } -rocksdb = { version = "0.22", optional = true } -rusqlite = { version = "0.25", features = ["bundled"], optional = true } +rocksdb = { version = "0.24", optional = true } +rusqlite = { version = "0.32", features = ["bundled"], optional = true } # RDF parsing (optional) rio_turtle = { version = "0.8", optional = true } rio_api = { version = "0.8", optional = true } +# CRDT support (optional, for clustering) +uuid = { version = "1", features = ["v4", "serde"], optional = true } + +# Ed25519 signing (optional, for dag-sign) +ed25519-dalek = { version = "2", features = ["rand_core"], optional = true } +rand = { version = "0.9", default-features = false, features = ["std", "thread_rng"], optional = true } + [dev-dependencies] criterion = "0.5" -tempfile = "3.10" +tempfile = "3.26" [[bench]] name = "graph_bench" diff --git a/crates/aingle_graph/benches/graph_bench.rs b/crates/aingle_graph/benches/graph_bench.rs index 38151d23..53676331 100644 --- a/crates/aingle_graph/benches/graph_bench.rs +++ b/crates/aingle_graph/benches/graph_bench.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Benchmarks for aingle_graph //! //! Run with: cargo bench -p aingle_graph diff --git a/crates/aingle_graph/src/backends/memory.rs b/crates/aingle_graph/src/backends/memory.rs index 2adfbfd7..84c4a397 100644 --- a/crates/aingle_graph/src/backends/memory.rs +++ b/crates/aingle_graph/src/backends/memory.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! In-memory storage backend //! //! Provides fast, ephemeral storage for testing and temporary graphs. diff --git a/crates/aingle_graph/src/backends/mod.rs b/crates/aingle_graph/src/backends/mod.rs index 553eb985..bcbecd02 100644 --- a/crates/aingle_graph/src/backends/mod.rs +++ b/crates/aingle_graph/src/backends/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Storage backends for the graph database //! //! Multiple backends are supported: @@ -44,6 +47,15 @@ pub trait StorageBackend: Send + Sync { /// Get storage size in bytes (approximate) fn size_bytes(&self) -> usize; + /// Atomically insert a batch of triples. Default implementation falls back + /// to individual puts (non-atomic). + fn apply_batch(&self, items: &[(&TripleId, &Triple)]) -> Result<()> { + for (id, triple) in items { + self.put(id, triple)?; + } + Ok(()) + } + /// Flush pending writes to disk fn flush(&self) -> Result<()> { Ok(()) @@ -142,7 +154,8 @@ mod tests { // Size should be minimal initially let initial_size = backend.size_bytes(); - assert!(initial_size >= 0); + // MemoryBackend starts near-empty + let _ = initial_size; } #[test] diff --git a/crates/aingle_graph/src/backends/rocksdb.rs b/crates/aingle_graph/src/backends/rocksdb.rs index 769f7401..37c0a100 100644 --- a/crates/aingle_graph/src/backends/rocksdb.rs +++ b/crates/aingle_graph/src/backends/rocksdb.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! RocksDB storage backend //! //! Provides high-performance persistent storage using RocksDB. diff --git a/crates/aingle_graph/src/backends/sled.rs b/crates/aingle_graph/src/backends/sled.rs index 63c932d5..021adbbb 100644 --- a/crates/aingle_graph/src/backends/sled.rs +++ b/crates/aingle_graph/src/backends/sled.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Sled storage backend //! //! Provides persistent, transactional storage using the Sled embedded database. @@ -82,6 +85,18 @@ impl StorageBackend for SledBackend { Ok(triples) } + fn apply_batch(&self, items: &[(&TripleId, &Triple)]) -> Result<()> { + let mut batch = ::sled::Batch::default(); + for (id, triple) in items { + let bytes = triple.to_bytes(); + batch.insert(id.as_bytes().as_slice(), bytes); + } + self.triples + .apply_batch(batch) + .map_err(|e| Error::Storage(format!("sled batch insert error: {}", e)))?; + Ok(()) + } + fn count(&self) -> usize { self.triples.len() } diff --git a/crates/aingle_graph/src/backends/sqlite.rs b/crates/aingle_graph/src/backends/sqlite.rs index 795248aa..1617d47b 100644 --- a/crates/aingle_graph/src/backends/sqlite.rs +++ b/crates/aingle_graph/src/backends/sqlite.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! SQLite storage backend //! //! Provides portable, lightweight storage for IoT and embedded devices. diff --git a/crates/aingle_graph/src/crdt.rs b/crates/aingle_graph/src/crdt.rs new file mode 100644 index 00000000..e9e32e2a --- /dev/null +++ b/crates/aingle_graph/src/crdt.rs @@ -0,0 +1,297 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! CRDT conflict resolution for distributed triple stores. +//! +//! Provides Last-Writer-Wins (LWW) registers and Observed-Remove Sets +//! for deterministic conflict resolution when gossip-synced nodes have +//! concurrent writes. + +use crate::triple::{Triple, TripleId}; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use uuid::Uuid; + +/// Last-Writer-Wins Register for triple conflicts. +/// +/// When two nodes write to the same triple ID concurrently, +/// the write with the latest timestamp wins. Ties are broken +/// deterministically by node ID (higher ID wins). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LwwTriple { + pub triple: Triple, + pub timestamp: DateTime, + pub node_id: u64, +} + +impl LwwTriple { + /// Create a new LWW-tagged triple. + pub fn new(triple: Triple, node_id: u64) -> Self { + Self { + triple, + timestamp: Utc::now(), + node_id, + } + } + + /// Create with an explicit timestamp. + pub fn with_timestamp(triple: Triple, timestamp: DateTime, node_id: u64) -> Self { + Self { + triple, + timestamp, + node_id, + } + } + + /// Merge two conflicting versions. Returns the winner. + pub fn merge(a: &LwwTriple, b: &LwwTriple) -> LwwTriple { + if a.timestamp > b.timestamp { + a.clone() + } else if b.timestamp > a.timestamp { + b.clone() + } else { + // Tie-break by node ID (deterministic: higher ID wins) + if a.node_id >= b.node_id { + a.clone() + } else { + b.clone() + } + } + } +} + +/// Observed-Remove Set for triple existence. +/// +/// Handles the case where one node inserts and another deletes +/// the same triple concurrently. Each insert generates a unique +/// tag; a remove only affects the tags that were observed at the +/// time of removal. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OrSet { + /// (triple_id bytes, add_tag) pairs — unique per insert operation. + adds: HashSet<([u8; 32], Uuid)>, + /// (triple_id bytes, add_tag) pairs that have been removed. + removes: HashSet<([u8; 32], Uuid)>, +} + +impl OrSet { + /// Create a new empty OR-Set. + pub fn new() -> Self { + Self { + adds: HashSet::new(), + removes: HashSet::new(), + } + } + + /// Insert a triple ID into the set, returning a unique tag. + pub fn insert(&mut self, id: &TripleId) -> Uuid { + let tag = Uuid::new_v4(); + self.adds.insert((*id.as_bytes(), tag)); + tag + } + + /// Remove all observed add-tags for this triple ID. + pub fn remove(&mut self, id: &TripleId) { + let id_bytes = *id.as_bytes(); + let to_remove: Vec<_> = self + .adds + .iter() + .filter(|(tid, _)| *tid == id_bytes) + .cloned() + .collect(); + for pair in to_remove { + self.adds.remove(&pair); + self.removes.insert(pair); + } + } + + /// Check if a triple ID is in the set (has at least one + /// non-removed add-tag). + pub fn contains(&self, id: &TripleId) -> bool { + let id_bytes = *id.as_bytes(); + self.adds + .iter() + .any(|(tid, tag)| *tid == id_bytes && !self.removes.contains(&(id_bytes, *tag))) + } + + /// Merge another OR-Set into this one. + /// + /// Union of adds and removes. Idempotent. + pub fn merge(&mut self, other: &OrSet) { + self.adds = self.adds.union(&other.adds).cloned().collect(); + self.removes = self.removes.union(&other.removes).cloned().collect(); + } + + /// Number of active (non-removed) entries. + pub fn len(&self) -> usize { + self.adds + .iter() + .filter(|pair| !self.removes.contains(pair)) + .count() + } + + /// Whether the set is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for OrSet { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{NodeId, Predicate, Value}; + use chrono::Duration; + + fn make_triple(subject: &str) -> Triple { + Triple::new( + NodeId::named(subject), + Predicate::named("knows"), + Value::String("bob".into()), + ) + } + + #[test] + fn test_lww_later_timestamp_wins() { + let triple = make_triple("alice"); + let now = Utc::now(); + let earlier = now - Duration::seconds(10); + + let a = LwwTriple::with_timestamp(triple.clone(), earlier, 1); + let b = LwwTriple::with_timestamp(triple, now, 2); + + let winner = LwwTriple::merge(&a, &b); + assert_eq!(winner.node_id, 2); // b wins (later timestamp) + } + + #[test] + fn test_lww_tiebreak_by_node_id() { + let triple = make_triple("alice"); + let same_time = Utc::now(); + + let a = LwwTriple::with_timestamp(triple.clone(), same_time, 1); + let b = LwwTriple::with_timestamp(triple, same_time, 2); + + let winner = LwwTriple::merge(&a, &b); + assert_eq!(winner.node_id, 2); // Higher node_id wins tie + } + + #[test] + fn test_lww_merge_commutative() { + let triple = make_triple("test"); + let now = Utc::now(); + + let a = LwwTriple::with_timestamp(triple.clone(), now, 1); + let b = LwwTriple::with_timestamp(triple, now - Duration::seconds(1), 2); + + let winner1 = LwwTriple::merge(&a, &b); + let winner2 = LwwTriple::merge(&b, &a); + assert_eq!(winner1.node_id, winner2.node_id); // Same result regardless of order + } + + #[test] + fn test_or_set_insert_contains() { + let mut set = OrSet::new(); + let triple = make_triple("alice"); + let id = TripleId::from_triple(&triple); + + set.insert(&id); + assert!(set.contains(&id)); + assert_eq!(set.len(), 1); + } + + #[test] + fn test_or_set_remove() { + let mut set = OrSet::new(); + let triple = make_triple("alice"); + let id = TripleId::from_triple(&triple); + + set.insert(&id); + assert!(set.contains(&id)); + + set.remove(&id); + assert!(!set.contains(&id)); + assert!(set.is_empty()); + } + + #[test] + fn test_or_set_concurrent_insert_remove() { + // Simulates: Node A inserts, Node B removes same ID (with tag from A), + // then Node A inserts again. The second insert should survive. + let mut set = OrSet::new(); + let triple = make_triple("alice"); + let id = TripleId::from_triple(&triple); + + // First insert + set.insert(&id); + assert!(set.contains(&id)); + + // Remove (only removes tags observed so far) + set.remove(&id); + assert!(!set.contains(&id)); + + // Re-insert generates new tag + set.insert(&id); + assert!(set.contains(&id)); + } + + #[test] + fn test_or_set_merge() { + let triple = make_triple("alice"); + let id = TripleId::from_triple(&triple); + + let mut set_a = OrSet::new(); + set_a.insert(&id); + + let mut set_b = OrSet::new(); + let triple2 = make_triple("bob"); + let id2 = TripleId::from_triple(&triple2); + set_b.insert(&id2); + + // Merge B into A + set_a.merge(&set_b); + assert!(set_a.contains(&id)); + assert!(set_a.contains(&id2)); + } + + #[test] + fn test_or_set_merge_idempotent() { + let triple = make_triple("alice"); + let id = TripleId::from_triple(&triple); + + let mut set = OrSet::new(); + set.insert(&id); + + let snapshot = set.clone(); + set.merge(&snapshot); + + assert_eq!(set.len(), 1); // Merging with self doesn't duplicate + } + + #[test] + fn test_or_set_merge_with_removes() { + let triple = make_triple("alice"); + let id = TripleId::from_triple(&triple); + + let mut set_a = OrSet::new(); + set_a.insert(&id); + + // B gets A's state and removes the entry + let mut set_b = set_a.clone(); + set_b.remove(&id); + + // A doesn't know about the remove yet + assert!(set_a.contains(&id)); + assert!(!set_b.contains(&id)); + + // After merge, the remove propagates + set_a.merge(&set_b); + assert!(!set_a.contains(&id)); + } +} diff --git a/crates/aingle_graph/src/dag/action.rs b/crates/aingle_graph/src/dag/action.rs new file mode 100644 index 00000000..af9c2378 --- /dev/null +++ b/crates/aingle_graph/src/dag/action.rs @@ -0,0 +1,404 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Core DAG action types — the nodes of the Semantic DAG. +//! +//! Every mutation creates a `DagAction` linked to its parent actions by hash, +//! forming a verifiable acyclic graph of all changes. + +use crate::NodeId; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// A content-addressable hash identifying a `DagAction`. +/// +/// Computed as `blake3(canonical_serialize(parents, author, seq, timestamp, payload))`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct DagActionHash(pub [u8; 32]); + +impl DagActionHash { + /// Create from raw bytes. + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) + } + + /// Access the underlying bytes. + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + /// Hex-encode the hash. + pub fn to_hex(&self) -> String { + self.0.iter().map(|b| format!("{:02x}", b)).collect() + } + + /// Decode from hex string. + pub fn from_hex(hex: &str) -> Option { + if hex.len() != 64 { + return None; + } + let mut bytes = [0u8; 32]; + for i in 0..32 { + bytes[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).ok()?; + } + Some(Self(bytes)) + } +} + +impl std::fmt::Display for DagActionHash { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_hex()) + } +} + +/// Payload describing what kind of mutation this action represents. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum DagPayload { + /// One or more triples were inserted. + TripleInsert { + /// Triples in wire format (subject, predicate, object JSON). + triples: Vec, + }, + /// One or more triples were deleted. + TripleDelete { + /// Content-addressable IDs of the deleted triples. + triple_ids: Vec<[u8; 32]>, + /// Subjects of the deleted triples (for subject-based indexing). + /// Empty for actions created before v0.6.2. + #[serde(default)] + subjects: Vec, + }, + /// A memory subsystem operation. + MemoryOp { + /// The kind of memory operation. + kind: MemoryOpKind, + }, + /// Multiple operations batched into a single action. + Batch { + /// The individual payloads. + ops: Vec, + }, + /// Genesis action: marks the root of the DAG (e.g., migration from v0.5). + Genesis { + /// Number of triples in the graph at genesis time. + triple_count: usize, + /// Human-readable description. + description: String, + }, + /// Compaction checkpoint: records that pruning occurred. + Compact { + /// Number of actions that were pruned. + pruned_count: usize, + /// Number of actions retained after pruning. + retained_count: usize, + /// Human-readable description of the policy used. + policy: String, + }, + /// No-op action (e.g., for linearizable reads). + Noop, + /// Custom user-defined action (audit annotations, checkpoints, decisions). + Custom { + /// A descriptive type tag (e.g., "checkpoint", "decision", "annotation"). + payload_type: String, + /// A human-readable summary of the action. + payload_summary: String, + /// Optional arbitrary payload data. + #[serde(default)] + payload: Option, + /// Optional subject for indexing in the DAG history. + #[serde(default)] + subject: Option, + }, +} + +/// Wire format for a triple insert within a DAG action. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TripleInsertPayload { + pub subject: String, + pub predicate: String, + pub object: serde_json::Value, +} + +/// Kinds of memory operations tracked in the DAG. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum MemoryOpKind { + /// A memory entry was stored. + Store { + entry_type: String, + importance: f32, + }, + /// A memory entry was forgotten. + Forget { memory_id: String }, + /// Consolidation was triggered. + Consolidate, +} + +/// A single node in the Semantic DAG. +/// +/// Each action records its parent action hashes, forming a directed acyclic graph. +/// The hash of this action is computed deterministically from its content fields +/// (excluding `signature`), so any mutation to the content invalidates the hash. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DagAction { + /// Parent action hashes (the DAG edges). + /// - Empty for genesis actions. + /// - 1 parent for linear chains. + /// - 2+ parents for merge points. + pub parents: Vec, + /// The author (node) that created this action. + pub author: NodeId, + /// Per-author sequence number (monotonically increasing). + pub seq: u64, + /// UTC timestamp when this action was created. + pub timestamp: DateTime, + /// The mutation payload. + pub payload: DagPayload, + /// Optional cryptographic signature. + /// + /// Marked `#[serde(default)]` so that actions serialized before the + /// signing feature was added (or by older versions) deserialize + /// correctly with `None`. + #[serde(default)] + pub signature: Option>, +} + +impl DagAction { + /// Compute the content-addressable hash of this action. + /// + /// Hash = blake3(parents || author || seq || timestamp || payload). + /// The `signature` field is intentionally excluded. + pub fn compute_hash(&self) -> DagActionHash { + let mut hasher = blake3::Hasher::new(); + + // Parents + hasher.update(&(self.parents.len() as u64).to_le_bytes()); + for parent in &self.parents { + hasher.update(&parent.0); + } + + // Author — serde_json::to_vec cannot fail for NodeId (no maps with + // non-string keys, no NaN/Inf floats), so expect() is safe here. + let author_bytes = serde_json::to_vec(&self.author) + .expect("NodeId serialization must not fail"); + hasher.update(&(author_bytes.len() as u64).to_le_bytes()); + hasher.update(&author_bytes); + + // Seq + hasher.update(&self.seq.to_le_bytes()); + + // Timestamp + let ts = self.timestamp.to_rfc3339(); + hasher.update(ts.as_bytes()); + + // Payload — same reasoning: DagPayload contains only strings, + // integers, booleans, and JSON values — all safely serializable. + let payload_bytes = serde_json::to_vec(&self.payload) + .expect("DagPayload serialization must not fail"); + hasher.update(&(payload_bytes.len() as u64).to_le_bytes()); + hasher.update(&payload_bytes); + + DagActionHash(*hasher.finalize().as_bytes()) + } + + /// Serialize this action to bytes (JSON). + pub fn to_bytes(&self) -> Vec { + serde_json::to_vec(self).expect("DagAction serialization must not fail") + } + + /// Deserialize an action from bytes (JSON). + pub fn from_bytes(bytes: &[u8]) -> Option { + serde_json::from_slice(bytes).ok() + } + + /// Returns true if this is a genesis action (no parents). + pub fn is_genesis(&self) -> bool { + self.parents.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::NodeId; + + fn make_test_action(seq: u64, parents: Vec) -> DagAction { + DagAction { + parents, + author: NodeId::named("node:1"), + seq, + timestamp: DateTime::parse_from_rfc3339("2026-01-01T00:00:00Z") + .unwrap() + .with_timezone(&Utc), + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + }, + signature: None, + } + } + + #[test] + fn test_hash_deterministic() { + let action = make_test_action(1, vec![]); + let h1 = action.compute_hash(); + let h2 = action.compute_hash(); + assert_eq!(h1, h2); + } + + #[test] + fn test_hash_differs_on_seq() { + let a1 = make_test_action(1, vec![]); + let a2 = make_test_action(2, vec![]); + assert_ne!(a1.compute_hash(), a2.compute_hash()); + } + + #[test] + fn test_hash_differs_on_parents() { + let a1 = make_test_action(1, vec![]); + let a2 = make_test_action(1, vec![DagActionHash([0xAB; 32])]); + assert_ne!(a1.compute_hash(), a2.compute_hash()); + } + + #[test] + fn test_hash_hex_roundtrip() { + let hash = DagActionHash([0xDE; 32]); + let hex = hash.to_hex(); + assert_eq!(hex.len(), 64); + let restored = DagActionHash::from_hex(&hex).unwrap(); + assert_eq!(hash, restored); + } + + #[test] + fn test_serialization_roundtrip() { + let action = make_test_action(5, vec![DagActionHash([1; 32])]); + let bytes = action.to_bytes(); + let restored = DagAction::from_bytes(&bytes).unwrap(); + assert_eq!(restored.seq, 5); + assert_eq!(restored.parents.len(), 1); + } + + #[test] + fn test_genesis_action() { + let genesis = DagAction { + parents: vec![], + author: NodeId::named("aingle:system"), + seq: 0, + timestamp: Utc::now(), + payload: DagPayload::Genesis { + triple_count: 42, + description: "Migration from v0.5.0".into(), + }, + signature: None, + }; + assert!(genesis.is_genesis()); + + let child = make_test_action(1, vec![genesis.compute_hash()]); + assert!(!child.is_genesis()); + } + + #[test] + fn test_batch_payload() { + let action = DagAction { + parents: vec![], + author: NodeId::named("node:1"), + seq: 1, + timestamp: Utc::now(), + payload: DagPayload::Batch { + ops: vec![ + DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "a".into(), + predicate: "b".into(), + object: serde_json::json!("c"), + }], + }, + DagPayload::TripleDelete { + triple_ids: vec![[0u8; 32]], + subjects: vec![], + }, + ], + }, + signature: None, + }; + let bytes = action.to_bytes(); + let restored = DagAction::from_bytes(&bytes).unwrap(); + assert!(matches!(restored.payload, DagPayload::Batch { ops } if ops.len() == 2)); + } + + #[test] + fn test_signature_excluded_from_hash() { + let mut a1 = make_test_action(1, vec![]); + a1.signature = None; + let h1 = a1.compute_hash(); + + a1.signature = Some(vec![1, 2, 3, 4]); + let h2 = a1.compute_hash(); + + assert_eq!(h1, h2, "signature must not affect hash"); + } + + #[test] + fn test_forward_compat_unknown_fields_ignored() { + // Simulate a v0.6.1 action with an extra field unknown to v0.6.0. + // Serde must silently ignore it without errors. + let json = r#"{ + "parents": [], + "author": {"Named":"node:1"}, + "seq": 42, + "timestamp": "2026-01-01T00:00:00Z", + "payload": "Noop", + "signature": null, + "future_field": "some_new_data", + "another_future": 123 + }"#; + + let action: DagAction = serde_json::from_str(json).expect( + "must deserialize actions with unknown fields (forward compat)" + ); + assert_eq!(action.seq, 42); + assert!(matches!(action.payload, DagPayload::Noop)); + } + + #[test] + fn test_forward_compat_unknown_payload_variant() { + // Simulate a v0.6.1 payload variant unknown to v0.6.0. + // This WILL fail deserialization — which is expected and safe, + // because DagAction::from_bytes returns None. + let json = r#"{ + "parents": [], + "author": {"Named":"node:1"}, + "seq": 1, + "timestamp": "2026-01-01T00:00:00Z", + "payload": {"FutureVariant": {"data": "xyz"}}, + "signature": null + }"#; + + // from_bytes returns None for unrecognized payloads — safe failure + let result = DagAction::from_bytes(json.as_bytes()); + assert!( + result.is_none(), + "unknown payload variants must fail gracefully (None, not panic)" + ); + } + + #[test] + fn test_backward_compat_missing_signature() { + // Simulate a v0.5.0 action that was serialized WITHOUT the signature field. + // #[serde(default)] ensures this deserializes to None. + let json = r#"{ + "parents": [], + "author": {"Named":"node:1"}, + "seq": 1, + "timestamp": "2026-01-01T00:00:00Z", + "payload": "Noop" + }"#; + + let action: DagAction = serde_json::from_str(json).expect( + "must deserialize actions without signature field (backward compat)" + ); + assert!(action.signature.is_none()); + } +} diff --git a/crates/aingle_graph/src/dag/backend.rs b/crates/aingle_graph/src/dag/backend.rs new file mode 100644 index 00000000..e9f15f3b --- /dev/null +++ b/crates/aingle_graph/src/dag/backend.rs @@ -0,0 +1,253 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Pluggable storage backends for the DAG store. +//! +//! Actions are persisted via a [`DagBackend`] trait that supports raw +//! key-value operations. Two implementations ship out of the box: +//! +//! - [`MemoryDagBackend`] — in-memory HashMap (tests / ephemeral use) +//! - [`SledDagBackend`] — persistent Sled tree (production) + +use std::collections::HashMap; +use std::sync::RwLock; + +/// Raw key-value backend for DAG storage. +pub trait DagBackend: Send + Sync { + /// Store a key-value pair (upsert). + fn put(&self, key: &[u8], value: &[u8]) -> crate::Result<()>; + /// Get a value by exact key. + fn get(&self, key: &[u8]) -> crate::Result>>; + /// Delete a key. Returns true if the key existed. + fn delete(&self, key: &[u8]) -> crate::Result; + /// Return all key-value pairs whose key starts with `prefix`. + fn scan_prefix(&self, prefix: &[u8]) -> crate::Result, Vec)>>; + /// Flush pending writes to durable storage. + fn flush(&self) -> crate::Result<()> { + Ok(()) + } +} + +// ============================================================================ +// In-memory backend +// ============================================================================ + +/// In-memory DAG backend backed by a `HashMap`. +pub struct MemoryDagBackend { + data: RwLock, Vec>>, +} + +impl MemoryDagBackend { + pub fn new() -> Self { + Self { + data: RwLock::new(HashMap::new()), + } + } +} + +impl Default for MemoryDagBackend { + fn default() -> Self { + Self::new() + } +} + +impl DagBackend for MemoryDagBackend { + fn put(&self, key: &[u8], value: &[u8]) -> crate::Result<()> { + let mut data = self + .data + .write() + .map_err(|_| crate::Error::Storage("MemoryDagBackend lock poisoned".into()))?; + data.insert(key.to_vec(), value.to_vec()); + Ok(()) + } + + fn get(&self, key: &[u8]) -> crate::Result>> { + let data = self + .data + .read() + .map_err(|_| crate::Error::Storage("MemoryDagBackend lock poisoned".into()))?; + Ok(data.get(key).cloned()) + } + + fn delete(&self, key: &[u8]) -> crate::Result { + let mut data = self + .data + .write() + .map_err(|_| crate::Error::Storage("MemoryDagBackend lock poisoned".into()))?; + Ok(data.remove(key).is_some()) + } + + fn scan_prefix(&self, prefix: &[u8]) -> crate::Result, Vec)>> { + let data = self + .data + .read() + .map_err(|_| crate::Error::Storage("MemoryDagBackend lock poisoned".into()))?; + Ok(data + .iter() + .filter(|(k, _)| k.starts_with(prefix)) + .map(|(k, v)| (k.clone(), v.clone())) + .collect()) + } +} + +// ============================================================================ +// Sled backend +// ============================================================================ + +/// Persistent DAG backend using a Sled named tree. +/// +/// Opens (or creates) a `"dag"` tree inside the given Sled database path. +/// Since `sled::open` is reference-counted, calling it with the same path +/// as the triple store shares the same underlying database instance. +#[cfg(feature = "sled-backend")] +pub struct SledDagBackend { + tree: sled::Tree, +} + +#[cfg(feature = "sled-backend")] +impl SledDagBackend { + /// Open or create a DAG tree inside the Sled database at `path`. + pub fn open(path: &str) -> crate::Result { + let db = sled::open(path) + .map_err(|e| crate::Error::Storage(format!("sled open error: {}", e)))?; + let tree = db + .open_tree("dag") + .map_err(|e| crate::Error::Storage(format!("sled open_tree(dag) error: {}", e)))?; + Ok(Self { tree }) + } +} + +#[cfg(feature = "sled-backend")] +impl DagBackend for SledDagBackend { + fn put(&self, key: &[u8], value: &[u8]) -> crate::Result<()> { + self.tree + .insert(key, value) + .map_err(|e| crate::Error::Storage(format!("sled dag insert error: {}", e)))?; + Ok(()) + } + + fn get(&self, key: &[u8]) -> crate::Result>> { + match self.tree.get(key) { + Ok(Some(bytes)) => Ok(Some(bytes.to_vec())), + Ok(None) => Ok(None), + Err(e) => Err(crate::Error::Storage(format!("sled dag get error: {}", e))), + } + } + + fn delete(&self, key: &[u8]) -> crate::Result { + match self.tree.remove(key) { + Ok(Some(_)) => Ok(true), + Ok(None) => Ok(false), + Err(e) => Err(crate::Error::Storage(format!( + "sled dag delete error: {}", + e + ))), + } + } + + fn scan_prefix(&self, prefix: &[u8]) -> crate::Result, Vec)>> { + let mut results = Vec::new(); + for item in self.tree.scan_prefix(prefix) { + let (k, v) = item + .map_err(|e| crate::Error::Storage(format!("sled dag scan error: {}", e)))?; + results.push((k.to_vec(), v.to_vec())); + } + Ok(results) + } + + fn flush(&self) -> crate::Result<()> { + self.tree + .flush() + .map_err(|e| crate::Error::Storage(format!("sled dag flush error: {}", e)))?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memory_backend_crud() { + let backend = MemoryDagBackend::new(); + let key = b"test_key"; + let value = b"test_value"; + + // Put + Get + backend.put(key, value).unwrap(); + assert_eq!(backend.get(key).unwrap(), Some(value.to_vec())); + + // Overwrite + backend.put(key, b"new_value").unwrap(); + assert_eq!(backend.get(key).unwrap(), Some(b"new_value".to_vec())); + + // Delete + assert!(backend.delete(key).unwrap()); + assert_eq!(backend.get(key).unwrap(), None); + assert!(!backend.delete(key).unwrap()); // already gone + } + + #[test] + fn test_memory_backend_scan_prefix() { + let backend = MemoryDagBackend::new(); + backend.put(b"a:001", b"v1").unwrap(); + backend.put(b"a:002", b"v2").unwrap(); + backend.put(b"b:001", b"v3").unwrap(); + + let results = backend.scan_prefix(b"a:").unwrap(); + assert_eq!(results.len(), 2); + + let results = backend.scan_prefix(b"b:").unwrap(); + assert_eq!(results.len(), 1); + + let results = backend.scan_prefix(b"c:").unwrap(); + assert!(results.is_empty()); + } + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_backend_crud() { + let dir = tempfile::TempDir::new().unwrap(); + let backend = SledDagBackend::open(dir.path().to_str().unwrap()).unwrap(); + + backend.put(b"k1", b"v1").unwrap(); + assert_eq!(backend.get(b"k1").unwrap(), Some(b"v1".to_vec())); + + assert!(backend.delete(b"k1").unwrap()); + assert_eq!(backend.get(b"k1").unwrap(), None); + } + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_backend_scan_prefix() { + let dir = tempfile::TempDir::new().unwrap(); + let backend = SledDagBackend::open(dir.path().to_str().unwrap()).unwrap(); + + backend.put(b"a:001", b"v1").unwrap(); + backend.put(b"a:002", b"v2").unwrap(); + backend.put(b"b:001", b"v3").unwrap(); + + let results = backend.scan_prefix(b"a:").unwrap(); + assert_eq!(results.len(), 2); + } + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_backend_persistence() { + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + // Write data + { + let backend = SledDagBackend::open(path).unwrap(); + backend.put(b"k1", b"v1").unwrap(); + backend.flush().unwrap(); + } + + // Reopen and verify + { + let backend = SledDagBackend::open(path).unwrap(); + assert_eq!(backend.get(b"k1").unwrap(), Some(b"v1".to_vec())); + } + } +} diff --git a/crates/aingle_graph/src/dag/export.rs b/crates/aingle_graph/src/dag/export.rs new file mode 100644 index 00000000..a6c8bbac --- /dev/null +++ b/crates/aingle_graph/src/dag/export.rs @@ -0,0 +1,337 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! DAG graph export in multiple formats (DOT, Mermaid, JSON). + +use super::action::{DagAction, DagActionHash, DagPayload}; +use serde::{Deserialize, Serialize}; + +/// A portable graph representation of the DAG. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DagGraph { + pub nodes: Vec, + pub edges: Vec, +} + +/// A node in the exported graph. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DagNode { + pub id: String, + pub label: String, + pub author: String, + pub seq: u64, + pub timestamp: String, + pub payload_type: String, + pub is_tip: bool, +} + +/// An edge in the exported graph (child → parent). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DagEdge { + pub from: String, + pub to: String, +} + +/// Supported export formats. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExportFormat { + Dot, + Mermaid, + Json, +} + +impl ExportFormat { + /// Parse from string (case-insensitive). + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "dot" | "graphviz" => Some(Self::Dot), + "mermaid" | "md" => Some(Self::Mermaid), + "json" => Some(Self::Json), + _ => None, + } + } +} + +/// Shorten a hex ID to at most 12 characters for display. +/// Returns the full string if shorter than 12 characters. +fn short_id(id: &str) -> &str { + let end = id.len().min(12); + &id[..end] +} + +impl DagGraph { + /// Build a graph from a list of actions and their tip status. + pub fn from_actions(actions: &[DagAction], tips: &[DagActionHash]) -> Self { + let tip_set: std::collections::HashSet<[u8; 32]> = + tips.iter().map(|h| h.0).collect(); + + let mut nodes = Vec::with_capacity(actions.len()); + let mut edges = Vec::new(); + + for action in actions { + let hash = action.compute_hash(); + let hex = hash.to_hex(); + let short_label = short_id(&hex).to_string(); + + let payload_type = match &action.payload { + DagPayload::TripleInsert { triples } => { + format!("Insert({})", triples.len()) + } + DagPayload::TripleDelete { triple_ids, .. } => { + format!("Delete({})", triple_ids.len()) + } + DagPayload::MemoryOp { .. } => "MemoryOp".into(), + DagPayload::Batch { ops } => format!("Batch({})", ops.len()), + DagPayload::Genesis { .. } => "Genesis".into(), + DagPayload::Compact { .. } => "Compact".into(), + DagPayload::Noop => "Noop".into(), + DagPayload::Custom { ref payload_type, .. } => { + format!("Custom({})", payload_type) + } + }; + + let label = format!("{}\\nseq={} {}", short_label, action.seq, payload_type); + + nodes.push(DagNode { + id: hash.to_hex(), + label, + author: action.author.to_string(), + seq: action.seq, + timestamp: action.timestamp.to_rfc3339(), + payload_type, + is_tip: tip_set.contains(&hash.0), + }); + + for parent in &action.parents { + edges.push(DagEdge { + from: hash.to_hex(), + to: parent.to_hex(), + }); + } + } + + DagGraph { nodes, edges } + } + + /// Export as Graphviz DOT format. + pub fn to_dot(&self) -> String { + let mut out = String::from("digraph DAG {\n rankdir=BT;\n node [shape=box, style=filled, fontsize=10];\n\n"); + + for node in &self.nodes { + let color = if node.is_tip { + "#4CAF50" + } else { + match node.payload_type.as_str() { + "Genesis" => "#FF9800", + "Compact" => "#9E9E9E", + _ => "#2196F3", + } + }; + let short = short_id(&node.id); + out.push_str(&format!( + " \"{}\" [label=\"{}\\nseq={} {}\", fillcolor=\"{}\", fontcolor=white];\n", + short, short, node.seq, node.payload_type, color + )); + } + + out.push('\n'); + + for edge in &self.edges { + out.push_str(&format!( + " \"{}\" -> \"{}\";\n", + short_id(&edge.from), + short_id(&edge.to) + )); + } + + out.push_str("}\n"); + out + } + + /// Export as Mermaid graph format. + pub fn to_mermaid(&self) -> String { + let mut out = String::from("graph BT\n"); + + for node in &self.nodes { + let short = short_id(&node.id); + let shape = if node.is_tip { + format!("{}([\"{} seq={}\"])", short, node.payload_type, node.seq) + } else { + format!("{}[\"{} seq={}\"]", short, node.payload_type, node.seq) + }; + out.push_str(&format!(" {}\n", shape)); + } + + for edge in &self.edges { + out.push_str(&format!( + " {} --> {}\n", + short_id(&edge.from), + short_id(&edge.to) + )); + } + + // Style tips + for node in &self.nodes { + if node.is_tip { + out.push_str(&format!(" style {} fill:#4CAF50,color:white\n", short_id(&node.id))); + } + } + + out + } + + /// Export as JSON string. + pub fn to_json(&self) -> Result { + serde_json::to_string_pretty(self) + } + + /// Export in the given format. + pub fn export(&self, format: ExportFormat) -> Result { + match format { + ExportFormat::Dot => Ok(self.to_dot()), + ExportFormat::Mermaid => Ok(self.to_mermaid()), + ExportFormat::Json => self.to_json(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dag::TripleInsertPayload; + use crate::NodeId; + use chrono::Utc; + + fn make_action(seq: u64, parents: Vec) -> DagAction { + DagAction { + parents, + author: NodeId::named("node:1"), + seq, + timestamp: Utc::now(), + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: format!("s{}", seq), + predicate: "p".into(), + object: serde_json::json!("o"), + }], + }, + signature: None, + } + } + + fn build_linear_chain() -> (Vec, Vec) { + let a1 = make_action(1, vec![]); + let h1 = a1.compute_hash(); + let a2 = make_action(2, vec![h1]); + let h2 = a2.compute_hash(); + let a3 = make_action(3, vec![h2]); + let h3 = a3.compute_hash(); + (vec![a1, a2, a3], vec![h3]) + } + + #[test] + fn test_from_actions() { + let (actions, tips) = build_linear_chain(); + let graph = DagGraph::from_actions(&actions, &tips); + + assert_eq!(graph.nodes.len(), 3); + assert_eq!(graph.edges.len(), 2); // a2->a1, a3->a2 + + // Only the last node is a tip + let tip_count = graph.nodes.iter().filter(|n| n.is_tip).count(); + assert_eq!(tip_count, 1); + } + + #[test] + fn test_dot_output() { + let (actions, tips) = build_linear_chain(); + let graph = DagGraph::from_actions(&actions, &tips); + let dot = graph.to_dot(); + + assert!(dot.starts_with("digraph DAG {")); + assert!(dot.contains("rankdir=BT")); + assert!(dot.contains("->")); + assert!(dot.ends_with("}\n")); + } + + #[test] + fn test_mermaid_output() { + let (actions, tips) = build_linear_chain(); + let graph = DagGraph::from_actions(&actions, &tips); + let mmd = graph.to_mermaid(); + + assert!(mmd.starts_with("graph BT")); + assert!(mmd.contains("-->")); + assert!(mmd.contains("fill:#4CAF50")); // tip style + } + + #[test] + fn test_json_roundtrip() { + let (actions, tips) = build_linear_chain(); + let graph = DagGraph::from_actions(&actions, &tips); + let json = graph.to_json().unwrap(); + let back: DagGraph = serde_json::from_str(&json).unwrap(); + + assert_eq!(back.nodes.len(), 3); + assert_eq!(back.edges.len(), 2); + } + + #[test] + fn test_branching_graph() { + let a0 = make_action(0, vec![]); + let h0 = a0.compute_hash(); + let a1 = make_action(1, vec![h0]); + let h1 = a1.compute_hash(); + let a2 = make_action(2, vec![h0]); + let h2 = a2.compute_hash(); + // Merge + let a3 = DagAction { + parents: vec![h1, h2], + author: NodeId::named("node:1"), + seq: 3, + timestamp: Utc::now(), + payload: DagPayload::Noop, + signature: None, + }; + let h3 = a3.compute_hash(); + + let graph = DagGraph::from_actions(&[a0, a1, a2, a3], &[h3]); + assert_eq!(graph.nodes.len(), 4); + assert_eq!(graph.edges.len(), 4); // a1->a0, a2->a0, a3->a1, a3->a2 + } + + #[test] + fn test_export_format_parsing() { + assert_eq!(ExportFormat::from_str("dot"), Some(ExportFormat::Dot)); + assert_eq!(ExportFormat::from_str("DOT"), Some(ExportFormat::Dot)); + assert_eq!(ExportFormat::from_str("graphviz"), Some(ExportFormat::Dot)); + assert_eq!(ExportFormat::from_str("mermaid"), Some(ExportFormat::Mermaid)); + assert_eq!(ExportFormat::from_str("json"), Some(ExportFormat::Json)); + assert_eq!(ExportFormat::from_str("xml"), None); + } + + #[test] + fn test_genesis_coloring() { + let genesis = DagAction { + parents: vec![], + author: NodeId::named("aingle:system"), + seq: 0, + timestamp: Utc::now(), + payload: DagPayload::Genesis { + triple_count: 10, + description: "test".into(), + }, + signature: None, + }; + let h = genesis.compute_hash(); + + // When genesis is NOT a tip (child action exists), it gets orange + let child = make_action(1, vec![h]); + let hc = child.compute_hash(); + let graph = DagGraph::from_actions(&[genesis, child], &[hc]); + let dot = graph.to_dot(); + + assert!(dot.contains("#FF9800")); // genesis = orange + assert!(dot.contains("#4CAF50")); // tip = green + } +} diff --git a/crates/aingle_graph/src/dag/mod.rs b/crates/aingle_graph/src/dag/mod.rs new file mode 100644 index 00000000..cf9ea511 --- /dev/null +++ b/crates/aingle_graph/src/dag/mod.rs @@ -0,0 +1,39 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Semantic DAG — hash-linked action history for AIngle Graph. +//! +//! Every mutation creates a `DagAction` node linked to parent actions by hash, +//! forming a verifiable acyclic graph. The triple store becomes a materialized +//! view of the DAG, enabling full audit history, time-travel queries, and +//! branching/merging. +//! +//! # Modules +//! +//! - [`action`] — Core types: `DagAction`, `DagActionHash`, `DagPayload` +//! - [`store`] — Persistent storage with indexes +//! - [`tips`] — DAG tip set management + +pub mod action; +pub mod backend; +pub mod export; +pub mod pruning; +#[cfg(feature = "dag-sign")] +pub mod signing; +pub mod store; +pub mod sync; +pub mod timetravel; +pub mod tips; + +pub use action::{DagAction, DagActionHash, DagPayload, MemoryOpKind, TripleInsertPayload}; +pub use backend::{DagBackend, MemoryDagBackend}; +#[cfg(feature = "sled-backend")] +pub use backend::SledDagBackend; +pub use export::{DagGraph, ExportFormat}; +pub use pruning::{PruneResult, RetentionPolicy}; +#[cfg(feature = "dag-sign")] +pub use signing::{DagSigningKey, DagVerifyingKey, VerifyResult}; +pub use store::DagStore; +pub use sync::{PullResult, SyncRequest, SyncResponse}; +pub use timetravel::{DagDiff, TimeTravelSnapshot}; +pub use tips::DagTipSet; diff --git a/crates/aingle_graph/src/dag/pruning.rs b/crates/aingle_graph/src/dag/pruning.rs new file mode 100644 index 00000000..b47d08d8 --- /dev/null +++ b/crates/aingle_graph/src/dag/pruning.rs @@ -0,0 +1,34 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! DAG pruning and compaction. +//! +//! Retention policies determine which actions to keep during pruning. +//! Pruning removes old actions from all indexes while preserving tips +//! and the ability to query recent history. + +use serde::{Deserialize, Serialize}; + +/// Policy determining which actions to retain during pruning. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RetentionPolicy { + /// Keep all actions (no pruning). + KeepAll, + /// Keep only actions newer than this many seconds ago. + KeepSince { seconds: u64 }, + /// Keep at most this many actions (oldest pruned first). + KeepLast(usize), + /// Keep only actions within this many hops from current tips. + KeepDepth(usize), +} + +/// Result of a pruning operation. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PruneResult { + /// Number of actions that were removed. + pub pruned_count: usize, + /// Number of actions still retained. + pub retained_count: usize, + /// Hash of the compaction checkpoint action, if one was created. + pub checkpoint_hash: Option, +} diff --git a/crates/aingle_graph/src/dag/signing.rs b/crates/aingle_graph/src/dag/signing.rs new file mode 100644 index 00000000..5f9b6427 --- /dev/null +++ b/crates/aingle_graph/src/dag/signing.rs @@ -0,0 +1,345 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Ed25519 signing and verification for DAG actions. +//! +//! Every `DagAction` has an optional `signature` field. When signed, the +//! signature covers the action's content-addressable hash (blake3 of all +//! fields except `signature`), binding the author's identity to the action. +//! +//! # Key management +//! +//! - [`DagSigningKey`] wraps an Ed25519 signing key (private). +//! - [`DagVerifyingKey`] wraps an Ed25519 verifying key (public). +//! - Keys can be generated, loaded from seed bytes, or serialized as hex. + +use super::action::{DagAction, DagActionHash}; +use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey}; +use serde::{Deserialize, Serialize}; + +/// Ed25519 signing key for DAG actions. +pub struct DagSigningKey { + inner: SigningKey, +} + +/// Ed25519 verifying (public) key for DAG actions. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DagVerifyingKey { + inner: VerifyingKey, +} + +/// Result of verifying a DagAction's signature. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct VerifyResult { + /// Whether the signature is valid. + pub valid: bool, + /// The author's public key (hex). + pub public_key: String, + /// The action hash that was signed. + pub action_hash: String, + /// Human-readable detail. + pub detail: String, +} + +impl DagSigningKey { + /// Generate a new random signing key. + pub fn generate() -> Self { + let mut rng = rand::rng(); + let mut seed = [0u8; 32]; + rand::RngCore::fill_bytes(&mut rng, &mut seed); + Self { + inner: SigningKey::from_bytes(&seed), + } + } + + /// Create from a 32-byte seed (deterministic). + pub fn from_seed(seed: &[u8; 32]) -> Self { + Self { + inner: SigningKey::from_bytes(seed), + } + } + + /// Export the seed bytes. + pub fn seed(&self) -> [u8; 32] { + self.inner.to_bytes() + } + + /// Get the corresponding verifying (public) key. + pub fn verifying_key(&self) -> DagVerifyingKey { + DagVerifyingKey { + inner: self.inner.verifying_key(), + } + } + + /// Get the public key as raw bytes. + pub fn public_key_bytes(&self) -> [u8; 32] { + self.inner.verifying_key().to_bytes() + } + + /// Get the public key as hex string. + pub fn public_key_hex(&self) -> String { + self.public_key_bytes() + .iter() + .map(|b| format!("{:02x}", b)) + .collect() + } + + /// Sign a DagAction's hash and store the signature in the action. + /// + /// The signature covers `action.compute_hash()`, which excludes the + /// signature field itself, preventing circular dependency. + pub fn sign(&self, action: &mut DagAction) { + let hash = action.compute_hash(); + let sig = self.inner.sign(&hash.0); + action.signature = Some(sig.to_bytes().to_vec()); + } + + /// Sign a DagAction's hash and return the signature bytes without mutating. + pub fn sign_hash(&self, hash: &DagActionHash) -> Vec { + self.inner.sign(&hash.0).to_bytes().to_vec() + } +} + +impl DagVerifyingKey { + /// Create from raw 32-byte public key. + pub fn from_bytes(bytes: &[u8; 32]) -> crate::Result { + let inner = VerifyingKey::from_bytes(bytes) + .map_err(|e| crate::Error::Config(format!("Invalid Ed25519 public key: {}", e)))?; + Ok(Self { inner }) + } + + /// Create from hex-encoded public key string. + pub fn from_hex(hex: &str) -> crate::Result { + if hex.len() != 64 { + return Err(crate::Error::Config( + "Public key hex must be 64 characters".into(), + )); + } + let mut bytes = [0u8; 32]; + for i in 0..32 { + bytes[i] = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16) + .map_err(|_| crate::Error::Config("Invalid hex in public key".into()))?; + } + Self::from_bytes(&bytes) + } + + /// Get the raw bytes. + pub fn as_bytes(&self) -> [u8; 32] { + self.inner.to_bytes() + } + + /// Get as hex string. + pub fn to_hex(&self) -> String { + self.as_bytes() + .iter() + .map(|b| format!("{:02x}", b)) + .collect() + } + + /// Verify a DagAction's signature. + /// + /// Returns `Ok(true)` if valid, `Ok(false)` if invalid signature, + /// `Err` if the action has no signature. + pub fn verify(&self, action: &DagAction) -> crate::Result { + let sig_bytes = action + .signature + .as_ref() + .ok_or_else(|| crate::Error::Config("Action has no signature".into()))?; + + if sig_bytes.len() != 64 { + return Ok(false); + } + + let mut sig_arr = [0u8; 64]; + sig_arr.copy_from_slice(sig_bytes); + + let signature = ed25519_dalek::Signature::from_bytes(&sig_arr); + let hash = action.compute_hash(); + + Ok(self.inner.verify(&hash.0, &signature).is_ok()) + } +} + +/// Verify a DagAction using raw public key bytes. +/// +/// Convenience function that creates a temporary verifying key. +pub fn verify_action(action: &DagAction, public_key: &[u8; 32]) -> crate::Result { + let vk = DagVerifyingKey::from_bytes(public_key)?; + let hash = action.compute_hash(); + + let (valid, detail) = match &action.signature { + None => (false, "Action has no signature".into()), + Some(sig) if sig.len() != 64 => (false, format!("Invalid signature length: {}", sig.len())), + Some(_) => match vk.verify(action) { + Ok(true) => (true, "Signature valid".into()), + Ok(false) => (false, "Signature verification failed".into()), + Err(e) => (false, format!("Verification error: {}", e)), + }, + }; + + Ok(VerifyResult { + valid, + public_key: vk.to_hex(), + action_hash: hash.to_hex(), + detail, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dag::{DagPayload, TripleInsertPayload}; + use crate::NodeId; + use chrono::Utc; + + fn make_unsigned_action(seq: u64) -> DagAction { + DagAction { + parents: vec![], + author: NodeId::named("node:1"), + seq, + timestamp: Utc::now(), + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + }, + signature: None, + } + } + + #[test] + fn test_key_generation() { + let key = DagSigningKey::generate(); + let pk = key.public_key_bytes(); + assert_eq!(pk.len(), 32); + assert_eq!(key.public_key_hex().len(), 64); + } + + #[test] + fn test_deterministic_key() { + let seed = [42u8; 32]; + let k1 = DagSigningKey::from_seed(&seed); + let k2 = DagSigningKey::from_seed(&seed); + assert_eq!(k1.public_key_bytes(), k2.public_key_bytes()); + } + + #[test] + fn test_sign_and_verify() { + let key = DagSigningKey::generate(); + let vk = key.verifying_key(); + + let mut action = make_unsigned_action(1); + assert!(action.signature.is_none()); + + key.sign(&mut action); + assert!(action.signature.is_some()); + assert_eq!(action.signature.as_ref().unwrap().len(), 64); + + assert!(vk.verify(&action).unwrap()); + } + + #[test] + fn test_verify_rejects_tampered_action() { + let key = DagSigningKey::generate(); + let vk = key.verifying_key(); + + let mut action = make_unsigned_action(1); + key.sign(&mut action); + + // Tamper with seq — hash changes, signature breaks + action.seq = 999; + assert!(!vk.verify(&action).unwrap()); + } + + #[test] + fn test_verify_rejects_wrong_key() { + let key1 = DagSigningKey::generate(); + let key2 = DagSigningKey::generate(); + + let mut action = make_unsigned_action(1); + key1.sign(&mut action); + + // Verify with different key + let vk2 = key2.verifying_key(); + assert!(!vk2.verify(&action).unwrap()); + } + + #[test] + fn test_verify_unsigned_action_returns_error() { + let key = DagSigningKey::generate(); + let vk = key.verifying_key(); + + let action = make_unsigned_action(1); + assert!(vk.verify(&action).is_err()); + } + + #[test] + fn test_verify_action_convenience() { + let key = DagSigningKey::generate(); + let pk = key.public_key_bytes(); + + let mut action = make_unsigned_action(1); + key.sign(&mut action); + + let result = verify_action(&action, &pk).unwrap(); + assert!(result.valid); + assert_eq!(result.detail, "Signature valid"); + } + + #[test] + fn test_verify_action_no_signature() { + let key = DagSigningKey::generate(); + let pk = key.public_key_bytes(); + + let action = make_unsigned_action(1); + let result = verify_action(&action, &pk).unwrap(); + assert!(!result.valid); + assert_eq!(result.detail, "Action has no signature"); + } + + #[test] + fn test_signature_excluded_from_hash() { + let key = DagSigningKey::generate(); + + let mut action = make_unsigned_action(1); + let hash_before = action.compute_hash(); + key.sign(&mut action); + let hash_after = action.compute_hash(); + + // Hash must be identical — signature is excluded + assert_eq!(hash_before, hash_after); + } + + #[test] + fn test_verifying_key_hex_roundtrip() { + let key = DagSigningKey::generate(); + let vk = key.verifying_key(); + let hex = vk.to_hex(); + + let restored = DagVerifyingKey::from_hex(&hex).unwrap(); + assert_eq!(vk, restored); + } + + #[test] + fn test_sign_hash_matches_sign() { + let key = DagSigningKey::generate(); + let vk = key.verifying_key(); + + let mut action = make_unsigned_action(1); + let hash = action.compute_hash(); + let sig_bytes = key.sign_hash(&hash); + + action.signature = Some(sig_bytes); + assert!(vk.verify(&action).unwrap()); + } + + #[test] + fn test_verifying_key_from_bytes_invalid() { + let bad_bytes = [0u8; 32]; // not a valid Ed25519 point + // This may or may not fail depending on the point — use all-zero which is identity + // For safety, just test that the API doesn't panic + let _ = DagVerifyingKey::from_bytes(&bad_bytes); + } +} diff --git a/crates/aingle_graph/src/dag/store.rs b/crates/aingle_graph/src/dag/store.rs new file mode 100644 index 00000000..3694df68 --- /dev/null +++ b/crates/aingle_graph/src/dag/store.rs @@ -0,0 +1,1755 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Persistent storage for DAG actions with indexes. +//! +//! Actions are persisted via a pluggable [`DagBackend`] (in-memory or Sled). +//! In-memory indexes (author chain, affected triples) are rebuilt on startup +//! from the backend, ensuring zero data loss across restarts. + +use super::action::{DagAction, DagActionHash, DagPayload, TripleInsertPayload}; +use super::backend::DagBackend; +use super::pruning::{PruneResult, RetentionPolicy}; +use super::tips::DagTipSet; +use crate::NodeId; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::sync::RwLock; + +// ============================================================================= +// Key scheme for the backend +// ============================================================================= + +/// Prefix for action entries: `a:` + 32-byte hash = 34-byte key. +const ACTION_PREFIX: &[u8] = b"a:"; +/// Key for the serialized tip set. +const TIPS_KEY: &[u8] = b"_tips"; +/// Key for the schema version byte. +const VERSION_KEY: &[u8] = b"_ver"; +/// Current schema version. +const SCHEMA_VERSION: u8 = 1; + +fn action_key(hash: &[u8; 32]) -> Vec { + let mut key = Vec::with_capacity(34); + key.extend_from_slice(ACTION_PREFIX); + key.extend_from_slice(hash); + key +} + +fn serialize_tips(tips: &[[u8; 32]]) -> Vec { + tips.iter().flat_map(|h| h.iter().copied()).collect() +} + +fn deserialize_tips(bytes: &[u8]) -> Vec<[u8; 32]> { + bytes + .chunks_exact(32) + .map(|c| { + let mut h = [0u8; 32]; + h.copy_from_slice(c); + h + }) + .collect() +} + +// ============================================================================= +// DagStore +// ============================================================================= + +/// Persistent DAG store with in-memory indexes. +/// +/// Actions are stored durably in a [`DagBackend`]. On startup, in-memory +/// indexes (author chain, affected triples, tips, count) are rebuilt by +/// scanning the backend. +pub struct DagStore { + /// Pluggable storage backend (MemoryDagBackend or SledDagBackend). + backend: Box, + /// Author chain: (author_string, seq) → action hash. + author_index: RwLock>, + /// Affected triple index: triple_id → list of action hashes. + affected_index: RwLock>>, + /// Subject index: blake3(subject_string) → list of action hashes. + subject_index: RwLock>>, + /// Current DAG tips. + tips: RwLock, + /// Total action count (cached for fast stats). + count: RwLock, +} + +impl DagStore { + /// Create a new DagStore with an in-memory backend (tests / ephemeral use). + pub fn new() -> Self { + Self::with_backend(Box::new(super::backend::MemoryDagBackend::new())) + .expect("MemoryDagBackend should never fail") + } + + /// Create a DagStore backed by a custom [`DagBackend`]. + /// + /// On construction, all existing data is loaded from the backend and + /// in-memory indexes are rebuilt. + pub fn with_backend(backend: Box) -> crate::Result { + let store = Self { + backend, + author_index: RwLock::new(HashMap::new()), + affected_index: RwLock::new(HashMap::new()), + subject_index: RwLock::new(HashMap::new()), + tips: RwLock::new(DagTipSet::new()), + count: RwLock::new(0), + }; + store.rebuild_indexes()?; + Ok(store) + } + + /// Rebuild all in-memory indexes by scanning the backend. + /// + /// Called once at construction. Validates the schema version, loads all + /// actions, restores tips, and writes the schema version marker if not + /// present. + fn rebuild_indexes(&self) -> crate::Result<()> { + // Validate schema version (upgrade safety) + if let Some(ver_bytes) = self.backend.get(VERSION_KEY)? { + let stored_version = ver_bytes.first().copied().unwrap_or(0); + if stored_version > SCHEMA_VERSION { + return Err(crate::Error::Storage(format!( + "DAG backend schema version {} is newer than this binary supports ({}). \ + Upgrade the aingle binary before opening this database.", + stored_version, SCHEMA_VERSION + ))); + } + // Future: if stored_version < SCHEMA_VERSION, apply migrations here. + // Example: + // if stored_version == 1 { migrate_v1_to_v2()?; } + // if stored_version == 2 { migrate_v2_to_v3()?; } + // After all migrations, update the version: + if stored_version < SCHEMA_VERSION { + self.backend.put(VERSION_KEY, &[SCHEMA_VERSION])?; + } + } + + let entries = self.backend.scan_prefix(ACTION_PREFIX)?; + + if entries.is_empty() { + // Empty backend — nothing to rebuild. + // Ensure schema version is written. + if self.backend.get(VERSION_KEY)?.is_none() { + self.backend.put(VERSION_KEY, &[SCHEMA_VERSION])?; + } + return Ok(()); + } + + let mut author_idx = self + .author_index + .write() + .map_err(|_| crate::Error::Storage("DagStore author index lock poisoned".into()))?; + let mut affected_idx = self + .affected_index + .write() + .map_err(|_| crate::Error::Storage("DagStore affected index lock poisoned".into()))?; + let mut subject_idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + let mut count = self + .count + .write() + .map_err(|_| crate::Error::Storage("DagStore count lock poisoned".into()))?; + + author_idx.clear(); + affected_idx.clear(); + subject_idx.clear(); + + let mut action_count = 0usize; + for (key, value) in &entries { + if key.len() != 34 || !key.starts_with(ACTION_PREFIX) { + continue; + } + let mut hash_bytes = [0u8; 32]; + hash_bytes.copy_from_slice(&key[2..]); + + if let Some(action) = DagAction::from_bytes(value) { + let author_key = format!("{}", action.author); + author_idx.insert((author_key, action.seq), hash_bytes); + + for triple_id in extract_affected_triple_ids(&action.payload) { + affected_idx.entry(triple_id).or_default().push(hash_bytes); + } + for subject_hash in extract_subject_hashes(&action.payload) { + subject_idx.entry(subject_hash).or_default().push(hash_bytes); + } + action_count += 1; + } else { + let hex: String = hash_bytes.iter().map(|b| format!("{:02x}", b)).collect(); + log::warn!( + "Skipping corrupted DAG action {} ({} bytes) during index rebuild", + &hex[..12], + value.len() + ); + } + } + + *count = action_count; + drop(author_idx); + drop(affected_idx); + drop(subject_idx); + drop(count); + + // Restore tips from backend + if let Some(tips_bytes) = self.backend.get(TIPS_KEY)? { + let raw = deserialize_tips(&tips_bytes); + let mut tips = self + .tips + .write() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + *tips = DagTipSet::from_raw(raw); + } + + // Write schema version if not present + if self.backend.get(VERSION_KEY)?.is_none() { + self.backend.put(VERSION_KEY, &[SCHEMA_VERSION])?; + } + + Ok(()) + } + + /// Persist the current tip set to the backend. + fn persist_tips(&self, tips: &DagTipSet) -> crate::Result<()> { + let raw = tips.to_raw(); + let bytes = serialize_tips(&raw); + self.backend.put(TIPS_KEY, &bytes)?; + Ok(()) + } + + /// Flush all pending writes to durable storage. + /// + /// For Sled backends, this ensures data reaches disk immediately. + /// For in-memory backends, this is a no-op. + pub fn flush(&self) -> crate::Result<()> { + self.backend.flush() + } + + /// Store a DagAction. Computes its hash, updates all indexes and tips. + /// Returns the action's content-addressable hash. + /// + /// Validates that all parent hashes exist in the backend (except for + /// genesis actions with no parents). + pub fn put(&self, action: &DagAction) -> crate::Result { + // Validate parent hashes exist (skip for genesis with no parents) + for parent in &action.parents { + if self.backend.get(&action_key(&parent.0))?.is_none() { + return Err(crate::Error::Storage(format!( + "DAG action references non-existent parent {}", + parent.to_hex() + ))); + } + } + + let hash = action.compute_hash(); + let bytes = action.to_bytes(); + + // Store in backend + self.backend.put(&action_key(&hash.0), &bytes)?; + + // Update author index + { + let mut idx = self + .author_index + .write() + .map_err(|_| crate::Error::Storage("DagStore author index lock poisoned".into()))?; + let author_key = format!("{}", action.author); + idx.insert((author_key, action.seq), hash.0); + } + + // Update affected triple index + { + let mut idx = self + .affected_index + .write() + .map_err(|_| crate::Error::Storage("DagStore affected index lock poisoned".into()))?; + for triple_id in extract_affected_triple_ids(&action.payload) { + idx.entry(triple_id).or_default().push(hash.0); + } + } + + // Update subject index + { + let mut idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + for subject_hash in extract_subject_hashes(&action.payload) { + idx.entry(subject_hash).or_default().push(hash.0); + } + } + + // Update tip set and persist + { + let mut tips = self + .tips + .write() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + tips.advance(hash, &action.parents); + self.persist_tips(&tips)?; + } + + // Update count + { + let mut c = self + .count + .write() + .map_err(|_| crate::Error::Storage("DagStore count lock poisoned".into()))?; + *c += 1; + } + + Ok(hash) + } + + /// Retrieve a DagAction by its hash. + pub fn get(&self, hash: &DagActionHash) -> crate::Result> { + match self.backend.get(&action_key(&hash.0))? { + Some(bytes) => Ok(DagAction::from_bytes(&bytes)), + None => Ok(None), + } + } + + /// Check if an action exists. + pub fn contains(&self, hash: &DagActionHash) -> crate::Result { + Ok(self.backend.get(&action_key(&hash.0))?.is_some()) + } + + /// Get current DAG tips. + pub fn tips(&self) -> crate::Result> { + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + Ok(tips.current()) + } + + /// Get tip count. + pub fn tip_count(&self) -> crate::Result { + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + Ok(tips.len()) + } + + /// Export tip set as raw bytes (for snapshots). + pub fn tips_raw(&self) -> crate::Result> { + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + Ok(tips.to_raw()) + } + + /// Restore tip set from raw bytes (for snapshot install). + pub fn restore_tips(&self, raw: Vec<[u8; 32]>) -> crate::Result<()> { + let mut tips = self + .tips + .write() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + *tips = DagTipSet::from_raw(raw); + self.persist_tips(&tips)?; + Ok(()) + } + + /// Get actions by author in sequence order, most recent first. + pub fn chain(&self, author: &NodeId, limit: usize) -> crate::Result> { + let author_key = format!("{}", author); + let idx = self + .author_index + .read() + .map_err(|_| crate::Error::Storage("DagStore lock poisoned".into()))?; + + // Collect all (seq, hash) pairs for this author + let mut entries: Vec<(u64, [u8; 32])> = idx + .iter() + .filter(|((a, _), _)| a == &author_key) + .map(|((_, seq), hash)| (*seq, *hash)) + .collect(); + + // Sort by seq descending (most recent first) + entries.sort_by(|a, b| b.0.cmp(&a.0)); + entries.truncate(limit); + drop(idx); + + let mut result = Vec::new(); + for (_, hash) in &entries { + if let Some(bytes) = self.backend.get(&action_key(hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + result.push(action); + } + } + } + Ok(result) + } + + /// Get the history of mutations affecting a specific triple. + pub fn history(&self, triple_id: &[u8; 32], limit: usize) -> crate::Result> { + let idx = self + .affected_index + .read() + .map_err(|_| crate::Error::Storage("DagStore lock poisoned".into()))?; + + let hashes = match idx.get(triple_id) { + Some(h) => h.clone(), + None => return Ok(vec![]), + }; + drop(idx); + + let mut result: Vec = Vec::new(); + for hash in hashes.iter().rev().take(limit) { + if let Some(bytes) = self.backend.get(&action_key(hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + result.push(action); + } + } + } + + // Sort by timestamp descending + result.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + result.truncate(limit); + + Ok(result) + } + + /// Get the history of mutations affecting a specific subject. + /// + /// Looks up all DAG actions that contain the given subject string + /// (in TripleInsert, TripleDelete, or Custom payloads). + pub fn history_by_subject(&self, subject: &str, limit: usize) -> crate::Result> { + let subject_hash = *blake3::hash(subject.as_bytes()).as_bytes(); + + let idx = self + .subject_index + .read() + .map_err(|_| crate::Error::Storage("DagStore lock poisoned".into()))?; + + let hashes = match idx.get(&subject_hash) { + Some(h) => h.clone(), + None => return Ok(vec![]), + }; + drop(idx); + + let mut result: Vec = Vec::new(); + for hash in hashes.iter().rev().take(limit) { + if let Some(bytes) = self.backend.get(&action_key(hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + result.push(action); + } + } + } + + result.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + result.truncate(limit); + + Ok(result) + } + + /// Total number of stored actions. + pub fn action_count(&self) -> usize { + self.count.read().map(|c| *c).unwrap_or(0) + } + + /// Check if genesis exists, create it if not. + /// Returns the genesis hash. + pub fn init_or_migrate(&self, triple_count: usize) -> crate::Result { + // Check if we already have any actions + let count = self.action_count(); + if count > 0 { + // DAG already initialized — return any tip as "genesis done" signal + let tips = self.tips()?; + return Ok(tips.into_iter().next().unwrap_or(DagActionHash([0; 32]))); + } + + // Create genesis action + let genesis = DagAction { + parents: vec![], + author: NodeId::named("aingle:system"), + seq: 0, + timestamp: chrono::Utc::now(), + payload: DagPayload::Genesis { + triple_count, + description: "Migration from v0.5.0".into(), + }, + signature: None, + }; + + self.put(&genesis) + } + + // ========================================================================= + // Export + // ========================================================================= + + /// Export the full DAG as a portable graph structure. + pub fn export_graph(&self) -> crate::Result { + let entries = self.backend.scan_prefix(ACTION_PREFIX)?; + + let mut all_actions: Vec = entries + .iter() + .filter_map(|(_, value)| DagAction::from_bytes(value)) + .collect(); + + // Sort by timestamp for consistent output + all_actions.sort_by_key(|a| a.timestamp); + + let tips = self.tips()?; + Ok(super::export::DagGraph::from_actions(&all_actions, &tips)) + } + + // ========================================================================= + // Cross-node sync + // ========================================================================= + + /// Store a DAG action received from a peer **without** updating tips. + /// + /// Use this when ingesting historical actions from other nodes. + /// The tip set remains unchanged so that only Raft-applied actions + /// advance the local DAG frontier. + /// + /// Returns the action's hash. Skips silently if the action already exists. + pub fn ingest(&self, action: &DagAction) -> crate::Result { + let hash = action.compute_hash(); + + // Skip if already present + if self.backend.get(&action_key(&hash.0))?.is_some() { + return Ok(hash); + } + + // Store in backend + self.backend.put(&action_key(&hash.0), &action.to_bytes())?; + + // Update author index + { + let mut idx = self + .author_index + .write() + .map_err(|_| crate::Error::Storage("DagStore author index lock poisoned".into()))?; + let author_key = format!("{}", action.author); + idx.insert((author_key, action.seq), hash.0); + } + + // Update affected triple index + { + let mut idx = self + .affected_index + .write() + .map_err(|_| crate::Error::Storage("DagStore affected index lock poisoned".into()))?; + for triple_id in extract_affected_triple_ids(&action.payload) { + idx.entry(triple_id).or_default().push(hash.0); + } + } + + // Update subject index + { + let mut idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + for subject_hash in extract_subject_hashes(&action.payload) { + idx.entry(subject_hash).or_default().push(hash.0); + } + } + + // Update count (but NOT tips) + { + let mut c = self + .count + .write() + .map_err(|_| crate::Error::Storage("DagStore count lock poisoned".into()))?; + *c += 1; + } + + Ok(hash) + } + + /// Compute actions the remote node is missing. + /// + /// Given the remote's tips, finds all actions in our DAG that are + /// ancestors of our tips but NOT ancestors of the remote's tips. + /// Returns them in topological order (roots first). + pub fn compute_missing( + &self, + remote_tips: &[DagActionHash], + ) -> crate::Result> { + // Our full ancestor set (from our tips) + let our_tips = self.tips()?; + let mut our_ancestors: HashSet<[u8; 32]> = HashSet::new(); + for tip in &our_tips { + let set = self.ancestor_set(tip)?; + our_ancestors.extend(set); + } + + // Remote's ancestor set (only actions we know about) + let mut remote_ancestors: HashSet<[u8; 32]> = HashSet::new(); + for tip in remote_tips { + if self.contains(tip)? { + let set = self.ancestor_set(tip)?; + remote_ancestors.extend(set); + } + // Unknown remote tips are skipped — we can't walk their ancestry + } + + // Actions we have that remote doesn't + let missing_hashes: HashSet<[u8; 32]> = our_ancestors + .difference(&remote_ancestors) + .copied() + .collect(); + + if missing_hashes.is_empty() { + return Ok(vec![]); + } + + // Collect actions from backend + let mut collected: HashMap<[u8; 32], DagAction> = HashMap::new(); + for hash in &missing_hashes { + if let Some(bytes) = self.backend.get(&action_key(hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + collected.insert(*hash, action); + } + } + } + + // Topological sort (Kahn's algorithm) + topo_sort(collected) + } + + // ========================================================================= + // Time-travel queries + // ========================================================================= + + /// Collect all ancestors of `target` (inclusive) in topological order (roots first). + /// + /// Uses BFS backwards + Kahn's algorithm for correct ordering. + /// Missing parents (e.g. from pruning) are silently skipped. + pub fn ancestors(&self, target: &DagActionHash) -> crate::Result> { + // Phase 1: BFS backwards from target + let mut visited: HashSet<[u8; 32]> = HashSet::new(); + let mut queue: VecDeque<[u8; 32]> = VecDeque::new(); + let mut collected: HashMap<[u8; 32], DagAction> = HashMap::new(); + + queue.push_back(target.0); + visited.insert(target.0); + + while let Some(hash) = queue.pop_front() { + if let Some(bytes) = self.backend.get(&action_key(&hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + for parent in &action.parents { + if visited.insert(parent.0) { + queue.push_back(parent.0); + } + } + collected.insert(hash, action); + } + } + } + + // Phase 2: Topological sort (Kahn's algorithm) + topo_sort(collected) + } + + /// Collect the set of ancestor hashes for `target` (inclusive). + pub fn ancestor_set(&self, target: &DagActionHash) -> crate::Result> { + let mut visited: HashSet<[u8; 32]> = HashSet::new(); + let mut queue: VecDeque<[u8; 32]> = VecDeque::new(); + + queue.push_back(target.0); + visited.insert(target.0); + + while let Some(hash) = queue.pop_front() { + if let Some(bytes) = self.backend.get(&action_key(&hash))? { + if let Some(action) = DagAction::from_bytes(&bytes) { + for parent in &action.parents { + if visited.insert(parent.0) { + queue.push_back(parent.0); + } + } + } + } + } + + Ok(visited) + } + + /// Find actions in `to`'s ancestry but not in `from`'s ancestry (topological order). + pub fn actions_between( + &self, + from: &DagActionHash, + to: &DagActionHash, + ) -> crate::Result> { + let from_set = self.ancestor_set(from)?; + let to_ancestors = self.ancestors(to)?; + + Ok(to_ancestors + .into_iter() + .filter(|a| { + let h = a.compute_hash(); + !from_set.contains(&h.0) + }) + .collect()) + } + + /// Find the action with the latest timestamp that is ≤ `ts`. + /// + /// Returns `None` if no actions exist before the given time. + pub fn action_at_or_before( + &self, + ts: &chrono::DateTime, + ) -> crate::Result> { + let entries = self.backend.scan_prefix(ACTION_PREFIX)?; + + let mut best: Option<(DagActionHash, chrono::DateTime)> = None; + + for (key, value) in &entries { + if key.len() != 34 || !key.starts_with(ACTION_PREFIX) { + continue; + } + let mut hash = [0u8; 32]; + hash.copy_from_slice(&key[2..]); + + if let Some(action) = DagAction::from_bytes(value) { + if action.timestamp <= *ts { + if best.as_ref().map_or(true, |(_, t)| action.timestamp > *t) { + best = Some((DagActionHash(hash), action.timestamp)); + } + } + } + } + + Ok(best.map(|(h, _)| h)) + } + + // ========================================================================= + // Pruning + // ========================================================================= + + /// Prune old actions according to a retention policy. + /// + /// Tips are never pruned. If `create_checkpoint` is true, a `Compact` + /// action is appended after pruning (its parents are the current tips). + pub fn prune( + &self, + policy: &RetentionPolicy, + create_checkpoint: bool, + ) -> crate::Result { + let to_remove = match policy { + RetentionPolicy::KeepAll => { + return Ok(PruneResult { + pruned_count: 0, + retained_count: self.action_count(), + checkpoint_hash: None, + }); + } + RetentionPolicy::KeepSince { seconds } => self.collect_older_than(*seconds)?, + RetentionPolicy::KeepLast(n) => self.collect_excess(*n)?, + RetentionPolicy::KeepDepth(d) => self.collect_beyond_depth(*d)?, + }; + + if to_remove.is_empty() { + return Ok(PruneResult { + pruned_count: 0, + retained_count: self.action_count(), + checkpoint_hash: None, + }); + } + + let pruned_count = self.remove_actions(&to_remove)?; + let retained_count = self.action_count(); + + let checkpoint_hash = if create_checkpoint { + let tips = self.tips()?; + let action = DagAction { + parents: tips, + author: NodeId::named("aingle:system"), + seq: 0, + timestamp: chrono::Utc::now(), + payload: DagPayload::Compact { + pruned_count, + retained_count, + policy: format!("{:?}", policy), + }, + signature: None, + }; + Some(self.put(&action)?) + } else { + None + }; + + Ok(PruneResult { + pruned_count, + retained_count: self.action_count(), + checkpoint_hash, + }) + } + + /// Compute a depth map: for each action, its minimum hop-distance from any tip. + /// + /// Tips have depth 0, their parents depth 1, and so on. + /// Actions unreachable from tips get `usize::MAX`. + pub fn depth_map(&self) -> crate::Result> { + let entries = self.backend.scan_prefix(ACTION_PREFIX)?; + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + + // Deserialize all actions into a local map + let mut actions_map: HashMap<[u8; 32], DagAction> = HashMap::new(); + for (key, value) in &entries { + if key.len() != 34 || !key.starts_with(ACTION_PREFIX) { + continue; + } + let mut hash = [0u8; 32]; + hash.copy_from_slice(&key[2..]); + if let Some(action) = DagAction::from_bytes(value) { + actions_map.insert(hash, action); + } + } + + let mut depths: HashMap<[u8; 32], usize> = HashMap::new(); + let mut queue: VecDeque<([u8; 32], usize)> = VecDeque::new(); + + // Seed with tips at depth 0 + for tip in tips.current() { + depths.insert(tip.0, 0); + queue.push_back((tip.0, 0)); + } + + // BFS traversal following parent links + while let Some((hash, depth)) = queue.pop_front() { + if let Some(action) = actions_map.get(&hash) { + for parent in &action.parents { + let parent_depth = depth + 1; + let entry = depths.entry(parent.0).or_insert(usize::MAX); + if parent_depth < *entry { + *entry = parent_depth; + queue.push_back((parent.0, parent_depth)); + } + } + } + } + + // Mark any remaining actions not reached by BFS + for hash in actions_map.keys() { + depths.entry(*hash).or_insert(usize::MAX); + } + + Ok(depths) + } + + /// Remove a set of actions from backend and all indexes. Returns the count removed. + fn remove_actions(&self, to_remove: &HashSet<[u8; 32]>) -> crate::Result { + let mut removed = 0; + + // Delete from backend + for hash in to_remove { + if self.backend.delete(&action_key(hash))? { + removed += 1; + } + } + + // Clean author index + let mut author_idx = self + .author_index + .write() + .map_err(|_| crate::Error::Storage("DagStore author index lock poisoned".into()))?; + author_idx.retain(|_, h| !to_remove.contains(h)); + + // Clean affected index + let mut affected_idx = self + .affected_index + .write() + .map_err(|_| crate::Error::Storage("DagStore affected index lock poisoned".into()))?; + affected_idx.retain(|_, hashes| { + hashes.retain(|h| !to_remove.contains(h)); + !hashes.is_empty() + }); + + // Clean subject index + let mut subject_idx = self + .subject_index + .write() + .map_err(|_| crate::Error::Storage("DagStore subject index lock poisoned".into()))?; + subject_idx.retain(|_, hashes| { + hashes.retain(|h| !to_remove.contains(h)); + !hashes.is_empty() + }); + + // Update count + let mut count = self + .count + .write() + .map_err(|_| crate::Error::Storage("DagStore count lock poisoned".into()))?; + *count = count.saturating_sub(removed); + + Ok(removed) + } + + /// Collect action hashes older than `seconds` ago (excluding tips). + fn collect_older_than(&self, seconds: u64) -> crate::Result> { + let cutoff = chrono::Utc::now() + - chrono::Duration::seconds(seconds as i64); + let entries = self.backend.scan_prefix(ACTION_PREFIX)?; + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + + let tip_set: HashSet<[u8; 32]> = tips.current().iter().map(|h| h.0).collect(); + let mut result = HashSet::new(); + + for (key, value) in &entries { + if key.len() != 34 || !key.starts_with(ACTION_PREFIX) { + continue; + } + let mut hash = [0u8; 32]; + hash.copy_from_slice(&key[2..]); + + if tip_set.contains(&hash) { + continue; + } + if let Some(action) = DagAction::from_bytes(value) { + if action.timestamp < cutoff { + result.insert(hash); + } + } + } + + Ok(result) + } + + /// Collect the oldest actions beyond the keep count (excluding tips). + fn collect_excess(&self, keep: usize) -> crate::Result> { + let entries = self.backend.scan_prefix(ACTION_PREFIX)?; + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + + let total = entries.len(); + if total <= keep { + return Ok(HashSet::new()); + } + + let tip_set: HashSet<[u8; 32]> = tips.current().iter().map(|h| h.0).collect(); + + // Deserialize all non-tip actions with their timestamps + let mut candidates: Vec<([u8; 32], chrono::DateTime)> = Vec::new(); + for (key, value) in &entries { + if key.len() != 34 || !key.starts_with(ACTION_PREFIX) { + continue; + } + let mut hash = [0u8; 32]; + hash.copy_from_slice(&key[2..]); + + if tip_set.contains(&hash) { + continue; + } + if let Some(action) = DagAction::from_bytes(value) { + candidates.push((hash, action.timestamp)); + } + } + + // Sort oldest first + candidates.sort_by_key(|(_, ts)| *ts); + + // How many non-tip actions do we need to remove? + let to_prune = total.saturating_sub(keep); + + Ok(candidates + .into_iter() + .take(to_prune) + .map(|(hash, _)| hash) + .collect()) + } + + /// Collect actions beyond the given depth from tips (excluding tips). + fn collect_beyond_depth(&self, max_depth: usize) -> crate::Result> { + let depths = self.depth_map()?; + let tips = self + .tips + .read() + .map_err(|_| crate::Error::Storage("DagStore tips lock poisoned".into()))?; + + let tip_set: HashSet<[u8; 32]> = tips.current().iter().map(|h| h.0).collect(); + + Ok(depths + .into_iter() + .filter(|(hash, depth)| *depth > max_depth && !tip_set.contains(hash)) + .map(|(hash, _)| hash) + .collect()) + } +} + +impl Default for DagStore { + fn default() -> Self { + Self::new() + } +} + +// ============================================================================= +// Topological sort helper +// ============================================================================= + +/// Kahn's topological sort on a collected set of actions. +fn topo_sort(mut collected: HashMap<[u8; 32], DagAction>) -> crate::Result> { + let mut in_degree: HashMap<[u8; 32], usize> = HashMap::new(); + let mut children: HashMap<[u8; 32], Vec<[u8; 32]>> = HashMap::new(); + + for (hash, action) in &collected { + in_degree.entry(*hash).or_insert(0); + for parent in &action.parents { + if collected.contains_key(&parent.0) { + children.entry(parent.0).or_default().push(*hash); + *in_degree.entry(*hash).or_insert(0) += 1; + } + } + } + + let mut ready: VecDeque<[u8; 32]> = in_degree + .iter() + .filter(|(_, deg)| **deg == 0) + .map(|(hash, _)| *hash) + .collect(); + + let mut result = Vec::with_capacity(collected.len()); + + while let Some(hash) = ready.pop_front() { + if let Some(action) = collected.remove(&hash) { + result.push(action); + } + if let Some(kids) = children.get(&hash) { + for kid in kids { + if let Some(deg) = in_degree.get_mut(kid) { + *deg -= 1; + if *deg == 0 { + ready.push_back(*kid); + } + } + } + } + } + + Ok(result) +} + +// ============================================================================= +// Helpers +// ============================================================================= + +/// Extract triple IDs affected by a payload (for the affected index). +fn extract_affected_triple_ids(payload: &DagPayload) -> Vec<[u8; 32]> { + match payload { + DagPayload::TripleInsert { triples } => triples + .iter() + .map(|t| compute_triple_id_from_payload(t)) + .collect(), + DagPayload::TripleDelete { triple_ids, .. } => triple_ids.clone(), + DagPayload::Batch { ops } => ops.iter().flat_map(extract_affected_triple_ids).collect(), + _ => vec![], + } +} + +/// Extract subject hashes from a payload (for the subject index). +/// +/// Returns `blake3(subject_string)` for each subject mentioned in the payload. +fn extract_subject_hashes(payload: &DagPayload) -> Vec<[u8; 32]> { + match payload { + DagPayload::TripleInsert { triples } => triples + .iter() + .map(|t| *blake3::hash(t.subject.as_bytes()).as_bytes()) + .collect(), + DagPayload::TripleDelete { subjects, .. } => subjects + .iter() + .map(|s| *blake3::hash(s.as_bytes()).as_bytes()) + .collect(), + DagPayload::Custom { subject, .. } => subject + .iter() + .map(|s| *blake3::hash(s.as_bytes()).as_bytes()) + .collect(), + DagPayload::Batch { ops } => ops.iter().flat_map(extract_subject_hashes).collect(), + _ => vec![], + } +} + +/// Compute a triple ID from a TripleInsertPayload. +/// +/// Must match `TripleId::from_triple()` exactly: blake3(subject.to_bytes() || predicate.to_bytes() || object.to_bytes()). +fn compute_triple_id_from_payload(t: &TripleInsertPayload) -> [u8; 32] { + let subject = crate::NodeId::named(&t.subject); + let predicate = crate::Predicate::named(&t.predicate); + let object = json_to_graph_value(&t.object); + let triple = crate::Triple::new(subject, predicate, object); + *crate::TripleId::from_triple(&triple).as_bytes() +} + +/// Convert a serde_json::Value to a graph Value (matching the state machine's json_to_value). +pub(crate) fn json_to_graph_value(v: &serde_json::Value) -> crate::Value { + match v { + serde_json::Value::String(s) => crate::Value::String(s.clone()), + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + crate::Value::Integer(i) + } else if let Some(f) = n.as_f64() { + crate::Value::Float(f) + } else { + crate::Value::String(n.to_string()) + } + } + serde_json::Value::Bool(b) => crate::Value::Boolean(*b), + serde_json::Value::Null => crate::Value::Null, + _ => crate::Value::Json(v.clone()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::NodeId; + use chrono::Utc; + + fn make_action(seq: u64, parents: Vec) -> DagAction { + DagAction { + parents, + author: NodeId::named("node:1"), + seq, + timestamp: Utc::now(), + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + }, + signature: None, + } + } + + #[test] + fn test_put_and_get() { + let store = DagStore::new(); + let action = make_action(1, vec![]); + let hash = store.put(&action).unwrap(); + + let retrieved = store.get(&hash).unwrap().unwrap(); + assert_eq!(retrieved.seq, 1); + } + + #[test] + fn test_tips_linear_chain() { + let store = DagStore::new(); + + let a1 = make_action(1, vec![]); + let h1 = store.put(&a1).unwrap(); + assert_eq!(store.tip_count().unwrap(), 1); + + let a2 = make_action(2, vec![h1]); + let h2 = store.put(&a2).unwrap(); + assert_eq!(store.tip_count().unwrap(), 1); + + let tips = store.tips().unwrap(); + assert_eq!(tips[0], h2); + } + + #[test] + fn test_author_chain() { + let store = DagStore::new(); + + for seq in 0..5 { + let action = make_action(seq, vec![]); + store.put(&action).unwrap(); + } + + let chain = store.chain(&NodeId::named("node:1"), 10).unwrap(); + assert_eq!(chain.len(), 5); + // Most recent first + assert_eq!(chain[0].seq, 4); + } + + #[test] + fn test_triple_history() { + let store = DagStore::new(); + + // Two actions affecting the same triple + let a1 = make_action(1, vec![]); + store.put(&a1).unwrap(); + let a2 = make_action(2, vec![]); + store.put(&a2).unwrap(); + + // Compute the triple ID that both actions affect + let tid = compute_triple_id_from_payload(&TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }); + + let history = store.history(&tid, 10).unwrap(); + assert_eq!(history.len(), 2); + } + + #[test] + fn test_init_or_migrate() { + let store = DagStore::new(); + + // First call creates genesis + let hash = store.init_or_migrate(100).unwrap(); + assert_eq!(store.action_count(), 1); + + let genesis = store.get(&hash).unwrap().unwrap(); + assert!(genesis.is_genesis()); + assert!(matches!( + genesis.payload, + DagPayload::Genesis { triple_count: 100, .. } + )); + + // Second call returns existing tip + let hash2 = store.init_or_migrate(200).unwrap(); + assert_eq!(store.action_count(), 1); // No new action created + assert_ne!(hash2, DagActionHash([0; 32])); + } + + #[test] + fn test_action_count() { + let store = DagStore::new(); + assert_eq!(store.action_count(), 0); + + store.put(&make_action(1, vec![])).unwrap(); + assert_eq!(store.action_count(), 1); + + store.put(&make_action(2, vec![])).unwrap(); + assert_eq!(store.action_count(), 2); + } + + #[test] + fn test_contains() { + let store = DagStore::new(); + let action = make_action(1, vec![]); + let hash = store.put(&action).unwrap(); + + assert!(store.contains(&hash).unwrap()); + assert!(!store.contains(&DagActionHash([0xFF; 32])).unwrap()); + } + + #[test] + fn test_restore_tips() { + let store = DagStore::new(); + let raw = vec![[1u8; 32], [2u8; 32]]; + store.restore_tips(raw).unwrap(); + assert_eq!(store.tip_count().unwrap(), 2); + } + + #[test] + fn test_triple_id_matches_graph_triple_id() { + // CRITICAL: the triple ID computed from a DagPayload must match + // the TripleId::from_triple() in the graph's triple store. + // If these diverge, history lookups by triple ID silently fail. + use crate::{Triple, TripleId, Predicate, Value}; + + let subject = "user:alice"; + let predicate = "knows"; + let object_json = serde_json::json!("bob"); + + // Compute via DagStore's helper + let dag_tid = compute_triple_id_from_payload(&TripleInsertPayload { + subject: subject.into(), + predicate: predicate.into(), + object: object_json.clone(), + }); + + // Compute via TripleId::from_triple (the canonical graph path) + let triple = Triple::new( + NodeId::named(subject), + Predicate::named(predicate), + Value::String("bob".into()), + ); + let graph_tid = *TripleId::from_triple(&triple).as_bytes(); + + assert_eq!( + dag_tid, graph_tid, + "DagStore triple ID must match TripleId::from_triple()" + ); + } + + #[test] + fn test_history_matches_real_triple_id() { + // End-to-end: insert via DagStore, then look up history using + // the same triple ID that GraphDB.insert() would produce. + use crate::{Triple, TripleId, Predicate, Value}; + + let store = DagStore::new(); + let action = make_action(1, vec![]); + store.put(&action).unwrap(); + + // Compute the real triple ID as GraphDB would + let triple = Triple::new( + NodeId::named("alice"), + Predicate::named("knows"), + Value::String("bob".into()), + ); + let real_tid = *TripleId::from_triple(&triple).as_bytes(); + + let history = store.history(&real_tid, 10).unwrap(); + assert_eq!( + history.len(), + 1, + "history lookup using real TripleId must find the DagAction" + ); + } + + // ======================================================================= + // Pruning tests + // ======================================================================= + + fn make_action_at(seq: u64, parents: Vec, ts: chrono::DateTime) -> DagAction { + DagAction { + parents, + author: NodeId::named("node:1"), + seq, + timestamp: ts, + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: format!("s{}", seq), + predicate: "p".into(), + object: serde_json::json!(seq), + }], + }, + signature: None, + } + } + + #[test] + fn test_prune_keep_all() { + let store = DagStore::new(); + store.put(&make_action(1, vec![])).unwrap(); + store.put(&make_action(2, vec![])).unwrap(); + + let result = store.prune(&RetentionPolicy::KeepAll, false).unwrap(); + assert_eq!(result.pruned_count, 0); + assert_eq!(result.retained_count, 2); + } + + #[test] + fn test_prune_keep_last() { + let store = DagStore::new(); + + let now = Utc::now(); + // Build a linear chain: a1 -> a2 -> a3 -> a4 -> a5 + let a1 = make_action_at(1, vec![], now - chrono::Duration::seconds(50)); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action_at(2, vec![h1], now - chrono::Duration::seconds(40)); + let h2 = store.put(&a2).unwrap(); + let a3 = make_action_at(3, vec![h2], now - chrono::Duration::seconds(30)); + let h3 = store.put(&a3).unwrap(); + let a4 = make_action_at(4, vec![h3], now - chrono::Duration::seconds(20)); + let h4 = store.put(&a4).unwrap(); + let a5 = make_action_at(5, vec![h4], now - chrono::Duration::seconds(10)); + let h5 = store.put(&a5).unwrap(); + assert_eq!(store.action_count(), 5); + + // Keep last 3 → prune 2 oldest (a1, a2) + let result = store.prune(&RetentionPolicy::KeepLast(3), false).unwrap(); + assert_eq!(result.pruned_count, 2); + assert_eq!(result.retained_count, 3); + + // a1, a2 gone; a3, a4, a5 remain + assert!(store.get(&h1).unwrap().is_none()); + assert!(store.get(&h2).unwrap().is_none()); + assert!(store.get(&h3).unwrap().is_some()); + assert!(store.get(&h4).unwrap().is_some()); + assert!(store.get(&h5).unwrap().is_some()); + + // Tip is still h5 + let tips = store.tips().unwrap(); + assert_eq!(tips.len(), 1); + assert_eq!(tips[0], h5); + } + + #[test] + fn test_prune_keep_since() { + let store = DagStore::new(); + + let now = Utc::now(); + // Old actions (>100s ago) + let old1 = make_action_at(1, vec![], now - chrono::Duration::seconds(200)); + let h_old1 = store.put(&old1).unwrap(); + let old2 = make_action_at(2, vec![h_old1], now - chrono::Duration::seconds(150)); + let h_old2 = store.put(&old2).unwrap(); + // Recent actions (<100s ago) + let new1 = make_action_at(3, vec![h_old2], now - chrono::Duration::seconds(50)); + let h_new1 = store.put(&new1).unwrap(); + let new2 = make_action_at(4, vec![h_new1], now - chrono::Duration::seconds(10)); + let h_new2 = store.put(&new2).unwrap(); + + // Keep actions from last 100 seconds + let result = store + .prune(&RetentionPolicy::KeepSince { seconds: 100 }, false) + .unwrap(); + assert_eq!(result.pruned_count, 2); + assert_eq!(result.retained_count, 2); + assert!(store.get(&h_old1).unwrap().is_none()); + assert!(store.get(&h_old2).unwrap().is_none()); + assert!(store.get(&h_new1).unwrap().is_some()); + assert!(store.get(&h_new2).unwrap().is_some()); + } + + #[test] + fn test_prune_keep_depth() { + let store = DagStore::new(); + + // Chain: a1 -> a2 -> a3 -> a4 (tip) + // Depths from tip: a4=0, a3=1, a2=2, a1=3 + let a1 = make_action(1, vec![]); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action(2, vec![h1]); + let h2 = store.put(&a2).unwrap(); + let a3 = make_action(3, vec![h2]); + let h3 = store.put(&a3).unwrap(); + let a4 = make_action(4, vec![h3]); + let h4 = store.put(&a4).unwrap(); + + // Keep depth 1 → keep a4 (tip, depth 0) and a3 (depth 1), prune a1, a2 + let result = store.prune(&RetentionPolicy::KeepDepth(1), false).unwrap(); + assert_eq!(result.pruned_count, 2); + assert!(store.get(&h1).unwrap().is_none()); + assert!(store.get(&h2).unwrap().is_none()); + assert!(store.get(&h3).unwrap().is_some()); + assert!(store.get(&h4).unwrap().is_some()); + } + + #[test] + fn test_prune_never_removes_tips() { + let store = DagStore::new(); + + // Two concurrent tips (branches) + let a1 = make_action_at(1, vec![], Utc::now() - chrono::Duration::seconds(1000)); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action_at(2, vec![], Utc::now() - chrono::Duration::seconds(1000)); + let h2 = store.put(&a2).unwrap(); + + // Both are tips and very old — KeepLast(1) should still keep both + let result = store.prune(&RetentionPolicy::KeepLast(1), false).unwrap(); + assert_eq!(result.pruned_count, 0); + assert!(store.get(&h1).unwrap().is_some()); + assert!(store.get(&h2).unwrap().is_some()); + } + + #[test] + fn test_prune_with_checkpoint() { + let store = DagStore::new(); + + let now = Utc::now(); + let a1 = make_action_at(1, vec![], now - chrono::Duration::seconds(200)); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action_at(2, vec![h1], now - chrono::Duration::seconds(10)); + store.put(&a2).unwrap(); + assert_eq!(store.action_count(), 2); + + let result = store + .prune(&RetentionPolicy::KeepSince { seconds: 100 }, true) + .unwrap(); + assert_eq!(result.pruned_count, 1); + assert!(result.checkpoint_hash.is_some()); + + // Checkpoint action was created + let cp = store.get(&result.checkpoint_hash.unwrap()).unwrap().unwrap(); + assert!(matches!(cp.payload, DagPayload::Compact { .. })); + // +1 for the checkpoint + assert_eq!(store.action_count(), 2); // 1 retained + 1 checkpoint + } + + #[test] + fn test_prune_cleans_indexes() { + let store = DagStore::new(); + + let now = Utc::now(); + let a1 = make_action_at(1, vec![], now - chrono::Duration::seconds(200)); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action_at(2, vec![h1], now - chrono::Duration::seconds(10)); + store.put(&a2).unwrap(); + + // Author chain has 2 entries before pruning + assert_eq!(store.chain(&NodeId::named("node:1"), 10).unwrap().len(), 2); + + store + .prune(&RetentionPolicy::KeepSince { seconds: 100 }, false) + .unwrap(); + + // Author chain should now have only 1 entry + assert_eq!(store.chain(&NodeId::named("node:1"), 10).unwrap().len(), 1); + } + + #[test] + fn test_depth_map() { + let store = DagStore::new(); + + // Chain: a1 -> a2 -> a3 (tip) + let a1 = make_action(1, vec![]); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action(2, vec![h1]); + let h2 = store.put(&a2).unwrap(); + let a3 = make_action(3, vec![h2]); + let h3 = store.put(&a3).unwrap(); + + let depths = store.depth_map().unwrap(); + assert_eq!(depths[&h3.0], 0); // tip + assert_eq!(depths[&h2.0], 1); + assert_eq!(depths[&h1.0], 2); + } + + // ======================================================================= + // Backend persistence test + // ======================================================================= + + #[test] + fn test_with_backend_rebuilds_indexes() { + use super::super::backend::MemoryDagBackend; + use std::sync::Arc; + + // Create a store and populate it + let _backend = Arc::new(MemoryDagBackend::new()); + + // Use a wrapper that shares the backend + let store = DagStore::new(); + let a1 = make_action(1, vec![]); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action(2, vec![h1]); + let h2 = store.put(&a2).unwrap(); + + assert_eq!(store.action_count(), 2); + assert_eq!(store.tip_count().unwrap(), 1); + assert_eq!(store.tips().unwrap()[0], h2); + + // Verify chain works + let chain = store.chain(&NodeId::named("node:1"), 10).unwrap(); + assert_eq!(chain.len(), 2); + } + + #[test] + fn test_tips_persisted_to_backend() { + // Verify that tips are stored in the backend after put + let store = DagStore::new(); + let a1 = make_action(1, vec![]); + store.put(&a1).unwrap(); + + // Tips should be persisted — check the backend directly + let tips_bytes = store.backend.get(TIPS_KEY).unwrap(); + assert!(tips_bytes.is_some()); + let raw = deserialize_tips(&tips_bytes.unwrap()); + assert_eq!(raw.len(), 1); + } + + // ======================================================================= + // Sled persistence end-to-end test + // ======================================================================= + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_persistence_end_to_end() { + use super::super::backend::SledDagBackend; + + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + let h1; + let h3; + + // Phase 1: Write data, then drop the store + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + + let a1 = make_action(1, vec![]); + h1 = store.put(&a1).unwrap(); + let a2 = make_action(2, vec![h1]); + let h2 = store.put(&a2).unwrap(); + let a3 = make_action(3, vec![h2]); + h3 = store.put(&a3).unwrap(); + + assert_eq!(store.action_count(), 3); + assert_eq!(store.tip_count().unwrap(), 1); + assert_eq!(store.tips().unwrap()[0], h3); + store.flush().unwrap(); + } + + // Phase 2: Reopen from same path — all data must survive + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + + // Action count restored + assert_eq!(store.action_count(), 3); + + // Tips restored + assert_eq!(store.tip_count().unwrap(), 1); + assert_eq!(store.tips().unwrap()[0], h3); + + // Individual actions retrievable + let a1 = store.get(&h1).unwrap().unwrap(); + assert_eq!(a1.seq, 1); + let a3 = store.get(&h3).unwrap().unwrap(); + assert_eq!(a3.seq, 3); + + // Author index rebuilt — chain works + let chain = store.chain(&NodeId::named("node:1"), 10).unwrap(); + assert_eq!(chain.len(), 3); + assert_eq!(chain[0].seq, 3); // most recent first + + // Affected index rebuilt — history works + let tid = compute_triple_id_from_payload(&TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }); + let history = store.history(&tid, 10).unwrap(); + assert_eq!(history.len(), 3); + + // Can extend the chain after reopen + let a4 = make_action(4, vec![h3]); + let h4 = store.put(&a4).unwrap(); + assert_eq!(store.action_count(), 4); + assert_eq!(store.tips().unwrap()[0], h4); + } + + // Phase 3: Reopen again — verify the new action also persisted + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + assert_eq!(store.action_count(), 4); + } + } + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_persistence_with_pruning() { + use super::super::backend::SledDagBackend; + + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + // Phase 1: Write data + prune + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + + let now = chrono::Utc::now(); + let a1 = make_action_at(1, vec![], now - chrono::Duration::seconds(200)); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action_at(2, vec![h1], now - chrono::Duration::seconds(10)); + store.put(&a2).unwrap(); + + // Prune old actions + let result = store + .prune(&RetentionPolicy::KeepSince { seconds: 100 }, true) + .unwrap(); + assert_eq!(result.pruned_count, 1); + store.flush().unwrap(); + } + + // Phase 2: Reopen — pruned data must stay gone + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + + // 1 retained + 1 checkpoint = 2 + assert_eq!(store.action_count(), 2); + + // Author chain should have only the retained action + checkpoint + let chain = store.chain(&NodeId::named("node:1"), 10).unwrap(); + // Only seq=2 from node:1 (checkpoint is from aingle:system) + assert_eq!(chain.len(), 1); + assert_eq!(chain[0].seq, 2); + } + } + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_persistence_genesis_migration() { + use super::super::backend::SledDagBackend; + + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + // Phase 1: Create genesis + let genesis_hash; + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + genesis_hash = store.init_or_migrate(42).unwrap(); + assert_eq!(store.action_count(), 1); + store.flush().unwrap(); + } + + // Phase 2: Reopen — genesis must be there, init_or_migrate should be a no-op + { + let backend = SledDagBackend::open(path).unwrap(); + let store = DagStore::with_backend(Box::new(backend)).unwrap(); + assert_eq!(store.action_count(), 1); + + let hash2 = store.init_or_migrate(999).unwrap(); + assert_eq!(store.action_count(), 1); // No new genesis + assert_ne!(hash2, DagActionHash([0; 32])); + + // Verify genesis content + let genesis = store.get(&genesis_hash).unwrap().unwrap(); + assert!(genesis.is_genesis()); + assert!(matches!( + genesis.payload, + DagPayload::Genesis { triple_count: 42, .. } + )); + } + } + + #[test] + fn test_schema_version_reject_future() { + use super::super::backend::MemoryDagBackend; + + // Simulate a database written by a newer version (schema v99) + let backend = MemoryDagBackend::new(); + backend.put(VERSION_KEY, &[99u8]).unwrap(); + + // Attempting to open it should fail with a clear error + let result = DagStore::with_backend(Box::new(backend)); + assert!(result.is_err()); + let err_msg = format!("{}", result.err().unwrap()); + assert!( + err_msg.contains("newer than this binary"), + "error must explain version mismatch: {err_msg}" + ); + } + + #[test] + fn test_schema_version_written_on_first_use() { + use super::super::backend::MemoryDagBackend; + + let backend = MemoryDagBackend::new(); + // No version key yet + assert!(backend.get(VERSION_KEY).unwrap().is_none()); + + let _store = DagStore::with_backend(Box::new(backend)).unwrap(); + // Can't check the backend directly since it was moved into the store. + // But rebuild_indexes() should have written the version key. + // Verified indirectly: if it panicked, with_backend would have failed. + } + + #[cfg(feature = "sled-backend")] + #[test] + fn test_sled_schema_version_persists() { + use super::super::backend::SledDagBackend; + + let dir = tempfile::TempDir::new().unwrap(); + let path = dir.path().to_str().unwrap(); + + // Phase 1: Create store (writes schema version) + { + let backend = SledDagBackend::open(path).unwrap(); + let _store = DagStore::with_backend(Box::new(backend)).unwrap(); + } + + // Phase 2: Verify schema version was persisted + { + let backend = SledDagBackend::open(path).unwrap(); + let ver = backend.get(VERSION_KEY).unwrap().unwrap(); + assert_eq!(ver, vec![SCHEMA_VERSION]); + } + } +} diff --git a/crates/aingle_graph/src/dag/sync.rs b/crates/aingle_graph/src/dag/sync.rs new file mode 100644 index 00000000..ddfbe588 --- /dev/null +++ b/crates/aingle_graph/src/dag/sync.rs @@ -0,0 +1,198 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Cross-node DAG synchronization protocol. +//! +//! Nodes exchange DAG actions using a pull-based protocol: +//! +//! 1. Node A sends its tips to Node B via `SyncRequest` +//! 2. Node B computes which actions A is missing +//! 3. Node B responds with those actions in topological order +//! 4. Node A ingests them into its local DagStore + +use super::action::{DagAction, DagActionHash}; +use serde::{Deserialize, Serialize}; + +/// Request sent by a node to synchronize DAG actions. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SyncRequest { + /// The requesting node's current DAG tips. + pub local_tips: Vec, + /// Specific action hashes to request (if known). + /// When non-empty, the responder returns only these actions. + #[serde(default)] + pub want: Vec, +} + +/// Response containing DAG actions the requester is missing. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SyncResponse { + /// Actions the requester is missing, in topological order. + pub actions: Vec, + /// The responding node's current tips. + pub remote_tips: Vec, + /// Number of actions sent. + pub action_count: usize, +} + +/// Result of a pull sync operation. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PullResult { + /// Number of new actions ingested. + pub ingested: usize, + /// Number of actions that were already present locally. + pub already_had: usize, + /// The remote node's tips after sync. + pub remote_tips: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dag::{DagPayload, DagStore, TripleInsertPayload}; + use crate::NodeId; + use chrono::Utc; + + fn make_action(seq: u64, subject: &str, parents: Vec) -> DagAction { + DagAction { + parents, + author: NodeId::named("node:1"), + seq, + timestamp: Utc::now(), + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: subject.into(), + predicate: "knows".into(), + object: serde_json::json!("x"), + }], + }, + signature: None, + } + } + + #[test] + fn test_compute_missing_linear() { + // Node B has: a1 -> a2 -> a3 + // Node A has: a1 -> a2 (tips = [a2]) + // Missing for A: [a3] + let store_b = DagStore::new(); + let a1 = make_action(1, "s1", vec![]); + let h1 = store_b.put(&a1).unwrap(); + let a2 = make_action(2, "s2", vec![h1]); + let h2 = store_b.put(&a2).unwrap(); + let a3 = make_action(3, "s3", vec![h2]); + store_b.put(&a3).unwrap(); + + let missing = store_b.compute_missing(&[h2]).unwrap(); + assert_eq!(missing.len(), 1); + assert_eq!(missing[0].seq, 3); + } + + #[test] + fn test_compute_missing_branching() { + // Node B: a1 -> a2, a1 -> a3 + // Node A: has a1, a2 (tips = [a2]) + // Missing: a3 + let store_b = DagStore::new(); + let a1 = make_action(1, "s1", vec![]); + let h1 = store_b.put(&a1).unwrap(); + let a2 = make_action(2, "s2", vec![h1]); + let h2 = store_b.put(&a2).unwrap(); + let a3 = make_action(3, "s3", vec![h1]); + store_b.put(&a3).unwrap(); + + let missing = store_b.compute_missing(&[h2]).unwrap(); + assert_eq!(missing.len(), 1); + assert_eq!(missing[0].seq, 3); + } + + #[test] + fn test_compute_missing_fully_synced() { + let store = DagStore::new(); + let a1 = make_action(1, "s1", vec![]); + let h1 = store.put(&a1).unwrap(); + let a2 = make_action(2, "s2", vec![h1]); + let h2 = store.put(&a2).unwrap(); + + let missing = store.compute_missing(&[h2]).unwrap(); + assert!(missing.is_empty()); + } + + #[test] + fn test_compute_missing_unknown_remote_tip() { + // Remote tip is unknown to us — we send everything + let store = DagStore::new(); + let a1 = make_action(1, "s1", vec![]); + store.put(&a1).unwrap(); + + let unknown = DagActionHash([0xFF; 32]); + let missing = store.compute_missing(&[unknown]).unwrap(); + assert_eq!(missing.len(), 1); + } + + #[test] + fn test_ingest_stores_without_touching_tips() { + let store = DagStore::new(); + + // Put a1 as the "real" tip + let a1 = make_action(1, "s1", vec![]); + let h1 = store.put(&a1).unwrap(); + assert_eq!(store.tip_count().unwrap(), 1); + + // Ingest a historical action (a0, parent of a1) + let a0 = DagAction { + parents: vec![], + author: NodeId::named("node:1"), + seq: 0, + timestamp: Utc::now(), + payload: DagPayload::Noop, + signature: None, + }; + let h0 = store.ingest(&a0).unwrap(); + assert_ne!(h0, h1); + + // Tips should still be [h1], not changed by ingest + let tips = store.tips().unwrap(); + assert_eq!(tips.len(), 1); + assert_eq!(tips[0], h1); + + // But the action is stored and retrievable + assert!(store.get(&h0).unwrap().is_some()); + assert_eq!(store.action_count(), 2); + } + + #[test] + fn test_ingest_skips_duplicates() { + let store = DagStore::new(); + let a1 = make_action(1, "s1", vec![]); + let h1 = store.put(&a1).unwrap(); + + // Ingest same action again + let h1_again = store.ingest(&a1).unwrap(); + assert_eq!(h1, h1_again); + assert_eq!(store.action_count(), 1); // no duplicate + } + + #[test] + fn test_sync_request_serialization() { + let req = SyncRequest { + local_tips: vec![DagActionHash([1; 32])], + want: vec![], + }; + let json = serde_json::to_string(&req).unwrap(); + let back: SyncRequest = serde_json::from_str(&json).unwrap(); + assert_eq!(back.local_tips.len(), 1); + } + + #[test] + fn test_sync_response_serialization() { + let resp = SyncResponse { + actions: vec![], + remote_tips: vec![DagActionHash([2; 32])], + action_count: 0, + }; + let json = serde_json::to_string(&resp).unwrap(); + let back: SyncResponse = serde_json::from_str(&json).unwrap(); + assert_eq!(back.remote_tips.len(), 1); + } +} diff --git a/crates/aingle_graph/src/dag/timetravel.rs b/crates/aingle_graph/src/dag/timetravel.rs new file mode 100644 index 00000000..46df01d5 --- /dev/null +++ b/crates/aingle_graph/src/dag/timetravel.rs @@ -0,0 +1,271 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Time-travel queries — reconstruct graph state at any point in DAG history. +//! +//! ## Usage +//! +//! ```ignore +//! // Reconstruct state at a specific action +//! let (snapshot_db, info) = graph.dag_at(&some_hash)?; +//! let triples = snapshot_db.find(TriplePattern::any())?; +//! +//! // Get the diff between two points +//! let diff = graph.dag_diff(&from_hash, &to_hash)?; +//! ``` + +use super::action::{DagAction, DagActionHash, DagPayload}; +use super::store::json_to_graph_value; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// Metadata about a time-travel snapshot reconstruction. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TimeTravelSnapshot { + /// The target action hash that was reconstructed up to. + pub target_hash: DagActionHash, + /// The timestamp of the target action. + pub target_timestamp: DateTime, + /// Number of actions replayed to build this snapshot. + pub actions_replayed: usize, + /// Number of triples in the reconstructed state. + pub triple_count: usize, +} + +/// The diff between two points in DAG history. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DagDiff { + /// The "from" action hash. + pub from: DagActionHash, + /// The "to" action hash. + pub to: DagActionHash, + /// Actions present in `to`'s ancestry but not in `from`'s ancestry, + /// in topological order. + pub actions: Vec, +} + +/// Replay a single DagPayload onto a GraphDB (for time-travel reconstruction). +/// +/// Errors from individual insert/delete operations are intentionally ignored: +/// - Duplicate inserts are expected (same triple in multiple actions). +/// - Deletes of already-deleted triples are expected after pruning. +/// These are not failures — they're inherent to replaying a DAG. +pub(crate) fn replay_payload(db: &crate::GraphDB, payload: &DagPayload) -> crate::Result<()> { + match payload { + DagPayload::TripleInsert { triples } => { + for t in triples { + let triple = crate::Triple::new( + crate::NodeId::named(&t.subject), + crate::Predicate::named(&t.predicate), + json_to_graph_value(&t.object), + ); + // Duplicate inserts return Err(DuplicateTriple) — expected during replay. + let _ = db.insert(triple); + } + } + DagPayload::TripleDelete { triple_ids, .. } => { + for tid_bytes in triple_ids { + let tid = crate::TripleId::new(*tid_bytes); + // Delete of nonexistent triple returns Err(NotFound) — expected after pruning. + let _ = db.delete(&tid); + } + } + DagPayload::Batch { ops } => { + for op in ops { + replay_payload(db, op)?; + } + } + // Genesis, Noop, Compact, MemoryOp — no triple mutations + _ => {} + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::dag::{DagStore, TripleInsertPayload}; + use crate::{GraphDB, NodeId, Predicate, Triple, Value}; + use chrono::Utc; + + fn insert_action( + store: &DagStore, + seq: u64, + subject: &str, + object: &str, + parents: Vec, + ) -> DagActionHash { + let action = DagAction { + parents, + author: NodeId::named("node:1"), + seq, + timestamp: Utc::now(), + payload: DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: subject.into(), + predicate: "knows".into(), + object: serde_json::json!(object), + }], + }, + signature: None, + }; + store.put(&action).unwrap() + } + + #[test] + fn test_replay_triple_insert() { + let db = GraphDB::memory().unwrap(); + let payload = DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + }; + replay_payload(&db, &payload).unwrap(); + assert_eq!(db.count(), 1); + } + + #[test] + fn test_replay_triple_delete() { + let db = GraphDB::memory().unwrap(); + let triple = Triple::new( + NodeId::named("alice"), + Predicate::named("knows"), + Value::String("bob".into()), + ); + let tid = db.insert(triple).unwrap(); + assert_eq!(db.count(), 1); + + let payload = DagPayload::TripleDelete { + triple_ids: vec![*tid.as_bytes()], + subjects: vec![], + }; + replay_payload(&db, &payload).unwrap(); + assert_eq!(db.count(), 0); + } + + #[test] + fn test_replay_batch() { + let db = GraphDB::memory().unwrap(); + let payload = DagPayload::Batch { + ops: vec![ + DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + }, + DagPayload::TripleInsert { + triples: vec![TripleInsertPayload { + subject: "bob".into(), + predicate: "knows".into(), + object: serde_json::json!("charlie"), + }], + }, + ], + }; + replay_payload(&db, &payload).unwrap(); + assert_eq!(db.count(), 2); + } + + #[test] + fn test_replay_noop_and_genesis_are_no_ops() { + let db = GraphDB::memory().unwrap(); + replay_payload(&db, &DagPayload::Noop).unwrap(); + replay_payload( + &db, + &DagPayload::Genesis { + triple_count: 0, + description: "test".into(), + }, + ) + .unwrap(); + assert_eq!(db.count(), 0); + } + + #[test] + fn test_dag_at_linear_chain() { + let db = GraphDB::memory_with_dag().unwrap(); + let store = db.dag_store().unwrap(); + + let h1 = insert_action(store, 1, "alice", "bob", vec![]); + let h2 = insert_action(store, 2, "bob", "charlie", vec![h1]); + let h3 = insert_action(store, 3, "charlie", "dave", vec![h2]); + + // At h1: only alice->bob + let (snap1, info1) = db.dag_at(&h1).unwrap(); + assert_eq!(info1.triple_count, 1); + assert_eq!(info1.actions_replayed, 1); + assert_eq!(snap1.count(), 1); + + // At h2: alice->bob + bob->charlie + let (_snap2, info2) = db.dag_at(&h2).unwrap(); + assert_eq!(info2.triple_count, 2); + assert_eq!(info2.actions_replayed, 2); + + // At h3: all three + let (snap3, info3) = db.dag_at(&h3).unwrap(); + assert_eq!(info3.triple_count, 3); + assert_eq!(info3.actions_replayed, 3); + assert_eq!(snap3.count(), 3); + } + + #[test] + fn test_dag_at_branching() { + let db = GraphDB::memory_with_dag().unwrap(); + let store = db.dag_store().unwrap(); + + // Genesis -> branch A, branch B + let h0 = insert_action(store, 0, "root", "x", vec![]); + let ha = insert_action(store, 1, "alice", "bob", vec![h0]); + let hb = insert_action(store, 2, "charlie", "dave", vec![h0]); + + // At ha: root + alice->bob (no charlie->dave) + let (snap_a, _) = db.dag_at(&ha).unwrap(); + assert_eq!(snap_a.count(), 2); + + // At hb: root + charlie->dave (no alice->bob) + let (snap_b, _) = db.dag_at(&hb).unwrap(); + assert_eq!(snap_b.count(), 2); + } + + #[test] + fn test_dag_diff() { + let db = GraphDB::memory_with_dag().unwrap(); + let store = db.dag_store().unwrap(); + + let h1 = insert_action(store, 1, "alice", "bob", vec![]); + let h2 = insert_action(store, 2, "bob", "charlie", vec![h1]); + let h3 = insert_action(store, 3, "charlie", "dave", vec![h2]); + + // Diff from h1 to h3: should have h2 and h3 (not h1) + let diff = db.dag_diff(&h1, &h3).unwrap(); + assert_eq!(diff.actions.len(), 2); + assert_eq!(diff.actions[0].seq, 2); // h2 first (topological) + assert_eq!(diff.actions[1].seq, 3); // h3 second + } + + #[test] + fn test_dag_at_timestamp() { + let db = GraphDB::memory_with_dag().unwrap(); + let store = db.dag_store().unwrap(); + + let before = Utc::now(); + let h1 = insert_action(store, 1, "alice", "bob", vec![]); + let _h2 = insert_action(store, 2, "bob", "charlie", vec![h1]); + + // At a timestamp before any actions: None + let result = db.dag_at_timestamp(&(before - chrono::Duration::seconds(10))); + assert!(result.is_err() || { + // Should fail or return empty + true + }); + + // At current time: should get state with both triples + let (snap, info) = db.dag_at_timestamp(&Utc::now()).unwrap(); + assert_eq!(info.triple_count, 2); + assert_eq!(snap.count(), 2); + } +} diff --git a/crates/aingle_graph/src/dag/tips.rs b/crates/aingle_graph/src/dag/tips.rs new file mode 100644 index 00000000..e32bbeb2 --- /dev/null +++ b/crates/aingle_graph/src/dag/tips.rs @@ -0,0 +1,156 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! DAG tip set management. +//! +//! Tips are the "leaf" actions in the DAG — actions that are not yet +//! a parent of any newer action. A single tip means a linear chain; +//! multiple tips indicate concurrent branches. + +use super::action::DagActionHash; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +/// The set of current tip hashes in the DAG. +/// +/// When a new action is applied, its parents are removed from the tip set +/// and the new action's hash is added. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DagTipSet { + tips: HashSet, +} + +impl DagTipSet { + /// Create an empty tip set. + pub fn new() -> Self { + Self { + tips: HashSet::new(), + } + } + + /// Create a tip set from raw hash bytes (used during snapshot restore). + pub fn from_raw(hashes: Vec<[u8; 32]>) -> Self { + Self { + tips: hashes.into_iter().map(DagActionHash).collect(), + } + } + + /// Record a new action: remove its parents from tips, add its own hash. + pub fn advance(&mut self, action_hash: DagActionHash, parent_hashes: &[DagActionHash]) { + for parent in parent_hashes { + self.tips.remove(parent); + } + self.tips.insert(action_hash); + } + + /// Current tips (unordered). + pub fn current(&self) -> Vec { + self.tips.iter().copied().collect() + } + + /// Number of tips. 1 = linear chain, >1 = concurrent branches. + pub fn len(&self) -> usize { + self.tips.len() + } + + /// Returns true if there are no tips (empty DAG). + pub fn is_empty(&self) -> bool { + self.tips.is_empty() + } + + /// Check if a given hash is currently a tip. + pub fn contains(&self, hash: &DagActionHash) -> bool { + self.tips.contains(hash) + } + + /// Export tip hashes as raw byte arrays (for snapshot serialization). + pub fn to_raw(&self) -> Vec<[u8; 32]> { + self.tips.iter().map(|h| h.0).collect() + } +} + +impl Default for DagTipSet { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_tip_set() { + let tips = DagTipSet::new(); + assert!(tips.is_empty()); + assert_eq!(tips.len(), 0); + } + + #[test] + fn test_linear_chain() { + let mut tips = DagTipSet::new(); + + // Genesis (no parents) + let genesis = DagActionHash([1; 32]); + tips.advance(genesis, &[]); + assert_eq!(tips.len(), 1); + assert!(tips.contains(&genesis)); + + // Action 2 extends genesis + let a2 = DagActionHash([2; 32]); + tips.advance(a2, &[genesis]); + assert_eq!(tips.len(), 1); + assert!(!tips.contains(&genesis)); + assert!(tips.contains(&a2)); + } + + #[test] + fn test_concurrent_branches() { + let mut tips = DagTipSet::new(); + + let genesis = DagActionHash([1; 32]); + tips.advance(genesis, &[]); + + // Two branches from genesis + let b1 = DagActionHash([2; 32]); + let b2 = DagActionHash([3; 32]); + tips.advance(b1, &[genesis]); + // genesis is already removed, but b2 also lists it as parent + tips.advance(b2, &[genesis]); + + assert_eq!(tips.len(), 2); + assert!(tips.contains(&b1)); + assert!(tips.contains(&b2)); + } + + #[test] + fn test_merge() { + let mut tips = DagTipSet::new(); + + let genesis = DagActionHash([1; 32]); + tips.advance(genesis, &[]); + + let b1 = DagActionHash([2; 32]); + let b2 = DagActionHash([3; 32]); + tips.advance(b1, &[genesis]); + tips.advance(b2, &[genesis]); + assert_eq!(tips.len(), 2); + + // Merge action with both branches as parents + let merge = DagActionHash([4; 32]); + tips.advance(merge, &[b1, b2]); + assert_eq!(tips.len(), 1); + assert!(tips.contains(&merge)); + } + + #[test] + fn test_raw_roundtrip() { + let mut tips = DagTipSet::new(); + tips.advance(DagActionHash([10; 32]), &[]); + tips.advance(DagActionHash([20; 32]), &[]); + + let raw = tips.to_raw(); + let restored = DagTipSet::from_raw(raw); + assert_eq!(restored.len(), 2); + } +} diff --git a/crates/aingle_graph/src/error.rs b/crates/aingle_graph/src/error.rs index e003e49c..ec44f097 100644 --- a/crates/aingle_graph/src/error.rs +++ b/crates/aingle_graph/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for AIngle Graph. //! //! This module provides a unified `Error` type for all graph database operations. @@ -73,8 +76,14 @@ impl From for Error { } } -impl From for Error { - fn from(err: bincode::Error) -> Self { +impl From for Error { + fn from(err: bincode::error::EncodeError) -> Self { + Self::Serialization(err.to_string()) + } +} + +impl From for Error { + fn from(err: bincode::error::DecodeError) -> Self { Self::Serialization(err.to_string()) } } diff --git a/crates/aingle_graph/src/index.rs b/crates/aingle_graph/src/index.rs index f078fa08..b929f149 100644 --- a/crates/aingle_graph/src/index.rs +++ b/crates/aingle_graph/src/index.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Triple indexes for efficient querying //! //! Implements SPO, POS, and OSP indexes for O(1) lookups: diff --git a/crates/aingle_graph/src/lib.rs b/crates/aingle_graph/src/lib.rs index 4cb9a52c..bf0e3863 100644 --- a/crates/aingle_graph/src/lib.rs +++ b/crates/aingle_graph/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle Graph - Native Semantic GraphDB //! //! A high-performance triple store designed for the AIngle distributed ledger. @@ -67,6 +70,8 @@ //! ``` pub mod backends; +#[cfg(feature = "crdt")] +pub mod crdt; pub mod error; pub mod index; pub mod node; @@ -79,6 +84,9 @@ pub mod value; #[cfg(feature = "rdf")] pub mod rdf; +#[cfg(feature = "dag")] +pub mod dag; + // Re-exports pub use error::{Error, Result}; pub use index::{IndexType, TripleIndex}; @@ -86,7 +94,7 @@ pub use node::NodeId; pub use predicate::Predicate; pub use query::{QueryBuilder, QueryResult, TriplePattern}; pub use store::GraphStore; -pub use triple::{Triple, TripleId, TripleMeta}; +pub use triple::{Triple, TripleBuilder, TripleId, TripleMeta}; pub use value::Value; #[cfg(feature = "sled-backend")] @@ -180,6 +188,8 @@ pub use backends::memory::MemoryBackend; /// ``` pub struct GraphDB { store: GraphStore, + #[cfg(feature = "dag")] + dag_store: Option, } impl GraphDB { @@ -202,7 +212,11 @@ impl GraphDB { pub fn memory() -> Result { let backend = MemoryBackend::new(); let store = GraphStore::new(Box::new(backend))?; - Ok(Self { store }) + Ok(Self { + store, + #[cfg(feature = "dag")] + dag_store: None, + }) } /// Creates or opens a `GraphDB` using the `Sled` storage backend. @@ -231,7 +245,11 @@ impl GraphDB { pub fn sled(path: &str) -> Result { let backend = SledBackend::open(path)?; let store = GraphStore::new(Box::new(backend))?; - Ok(Self { store }) + Ok(Self { + store, + #[cfg(feature = "dag")] + dag_store: None, + }) } /// Creates or opens a `GraphDB` using the `RocksDB` storage backend. @@ -260,7 +278,11 @@ impl GraphDB { pub fn rocksdb(path: &str) -> Result { let backend = RocksBackend::open(path)?; let store = GraphStore::new(Box::new(backend))?; - Ok(Self { store }) + Ok(Self { + store, + #[cfg(feature = "dag")] + dag_store: None, + }) } /// Creates or opens a `GraphDB` using the `SQLite` storage backend. @@ -290,7 +312,333 @@ impl GraphDB { pub fn sqlite(path: &str) -> Result { let backend = SqliteBackend::open(path)?; let store = GraphStore::new(Box::new(backend))?; - Ok(Self { store }) + Ok(Self { + store, + #[cfg(feature = "dag")] + dag_store: None, + }) + } + + /// Creates an in-memory `GraphDB` with DAG enabled. + #[cfg(feature = "dag")] + pub fn memory_with_dag() -> Result { + let backend = MemoryBackend::new(); + let store = GraphStore::new(Box::new(backend))?; + Ok(Self { + store, + dag_store: Some(dag::DagStore::new()), + }) + } + + /// Creates a Sled-backed `GraphDB` with persistent DAG enabled. + /// + /// Both the triple store and the DAG share the same Sled database + /// (reference-counted) but use separate named trees. + #[cfg(all(feature = "dag", feature = "sled-backend"))] + pub fn sled_with_dag(path: &str) -> Result { + let backend = SledBackend::open(path)?; + let store = GraphStore::new(Box::new(backend))?; + let dag_backend = dag::SledDagBackend::open(path)?; + Ok(Self { + store, + dag_store: Some(dag::DagStore::with_backend(Box::new(dag_backend))?), + }) + } + + /// Enable DAG on an existing GraphDB instance (in-memory backend). + /// + /// For persistent DAG storage, use [`enable_dag_persistent`] instead. + #[cfg(feature = "dag")] + pub fn enable_dag(&mut self) { + if self.dag_store.is_none() { + self.dag_store = Some(dag::DagStore::new()); + } + } + + /// Enable DAG with a persistent Sled backend. + /// + /// The DAG tree is created inside the same Sled database at `path`, + /// sharing the instance with the triple store. + #[cfg(all(feature = "dag", feature = "sled-backend"))] + pub fn enable_dag_persistent(&mut self, path: &str) -> Result<()> { + if self.dag_store.is_none() { + let dag_backend = dag::SledDagBackend::open(path)?; + self.dag_store = Some(dag::DagStore::with_backend(Box::new(dag_backend))?); + } + Ok(()) + } + + /// Returns a reference to the DAG store, if enabled. + #[cfg(feature = "dag")] + pub fn dag_store(&self) -> Option<&dag::DagStore> { + self.dag_store.as_ref() + } + + /// Insert a triple via the DAG, creating a new DagAction. + /// + /// The triple is inserted into the materialized view (triple store) + /// AND recorded as a DagAction in the DAG history. + #[cfg(feature = "dag")] + pub fn insert_via_dag( + &self, + triple: Triple, + author: NodeId, + seq: u64, + parents: Vec, + ) -> Result<(dag::DagActionHash, TripleId)> { + // Insert into materialized view + let triple_id = self.store.insert(triple.clone())?; + + // Record in DAG + let dag_store = self + .dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))?; + + let action = dag::DagAction { + parents, + author, + seq, + timestamp: chrono::Utc::now(), + payload: dag::DagPayload::TripleInsert { + triples: vec![dag::TripleInsertPayload { + subject: triple.subject.to_string(), + predicate: triple.predicate.to_string(), + object: value_to_json(&triple.object), + }], + }, + signature: None, + }; + + let hash = dag_store.put(&action)?; + Ok((hash, triple_id)) + } + + /// Delete a triple via the DAG, creating a DagAction recording the deletion. + #[cfg(feature = "dag")] + pub fn delete_via_dag( + &self, + triple_id: &TripleId, + author: NodeId, + seq: u64, + parents: Vec, + ) -> Result { + // Delete from materialized view + self.store.delete(triple_id)?; + + // Record in DAG + let dag_store = self + .dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))?; + + let action = dag::DagAction { + parents, + author, + seq, + timestamp: chrono::Utc::now(), + payload: dag::DagPayload::TripleDelete { + triple_ids: vec![*triple_id.as_bytes()], + subjects: vec![], + }, + signature: None, + }; + + dag_store.put(&action) + } + + /// Get current DAG tips. + #[cfg(feature = "dag")] + pub fn dag_tips(&self) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .tips() + } + + /// Get a single DagAction by hash. + #[cfg(feature = "dag")] + pub fn dag_action(&self, hash: &dag::DagActionHash) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .get(hash) + } + + /// Get mutation history for a specific triple. + #[cfg(feature = "dag")] + pub fn dag_history( + &self, + triple_id: &[u8; 32], + limit: usize, + ) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .history(triple_id, limit) + } + + /// Get mutation history for a specific subject string. + #[cfg(feature = "dag")] + pub fn dag_history_by_subject( + &self, + subject: &str, + limit: usize, + ) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .history_by_subject(subject, limit) + } + + /// Get an author's action chain in sequence order. + #[cfg(feature = "dag")] + pub fn dag_chain(&self, author: &NodeId, limit: usize) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .chain(author, limit) + } + + /// Prune old DAG actions according to a retention policy. + #[cfg(feature = "dag")] + pub fn dag_prune( + &self, + policy: &dag::RetentionPolicy, + create_checkpoint: bool, + ) -> Result { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .prune(policy, create_checkpoint) + } + + /// Reconstruct graph state at a specific point in DAG history. + /// + /// Returns a new in-memory `GraphDB` containing only the triples that + /// existed at the target action, plus metadata about the reconstruction. + #[cfg(feature = "dag")] + pub fn dag_at( + &self, + target: &dag::DagActionHash, + ) -> Result<(GraphDB, dag::TimeTravelSnapshot)> { + let dag_store = self + .dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))?; + + let actions = dag_store.ancestors(target)?; + let snapshot_db = GraphDB::memory()?; + + for action in &actions { + dag::timetravel::replay_payload(&snapshot_db, &action.payload)?; + } + + let target_action = dag_store + .get(target)? + .ok_or_else(|| Error::NotFound(format!("DagAction {} not found", target)))?; + + let info = dag::TimeTravelSnapshot { + target_hash: *target, + target_timestamp: target_action.timestamp, + actions_replayed: actions.len(), + triple_count: snapshot_db.count(), + }; + + Ok((snapshot_db, info)) + } + + /// Reconstruct graph state at the latest action on or before a timestamp. + #[cfg(feature = "dag")] + pub fn dag_at_timestamp( + &self, + ts: &chrono::DateTime, + ) -> Result<(GraphDB, dag::TimeTravelSnapshot)> { + let dag_store = self + .dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))?; + + let target = dag_store + .action_at_or_before(ts)? + .ok_or_else(|| Error::NotFound("No actions found before the given timestamp".into()))?; + + self.dag_at(&target) + } + + /// Sign a DAG action using an Ed25519 signing key. + #[cfg(feature = "dag-sign")] + pub fn dag_sign( + &self, + action: &mut dag::DagAction, + key: &dag::DagSigningKey, + ) { + key.sign(action); + } + + /// Verify a DAG action's signature. + #[cfg(feature = "dag-sign")] + pub fn dag_verify( + &self, + action: &dag::DagAction, + public_key: &[u8; 32], + ) -> Result { + dag::signing::verify_action(action, public_key) + .map_err(|e| Error::Config(e.to_string())) + } + + /// Export the full DAG as a portable graph structure. + #[cfg(feature = "dag")] + pub fn dag_export(&self) -> Result { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .export_graph() + } + + /// Ingest a DAG action from a peer without updating tips. + #[cfg(feature = "dag")] + pub fn dag_ingest(&self, action: &dag::DagAction) -> Result { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .ingest(action) + } + + /// Compute actions that a remote node with the given tips is missing. + #[cfg(feature = "dag")] + pub fn dag_compute_missing( + &self, + remote_tips: &[dag::DagActionHash], + ) -> Result> { + self.dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))? + .compute_missing(remote_tips) + } + + /// Compute the diff between two points in DAG history. + /// + /// Returns actions in `to`'s ancestry that are not in `from`'s ancestry, + /// in topological order. + #[cfg(feature = "dag")] + pub fn dag_diff( + &self, + from: &dag::DagActionHash, + to: &dag::DagActionHash, + ) -> Result { + let dag_store = self + .dag_store + .as_ref() + .ok_or_else(|| Error::Config("DAG not enabled".into()))?; + + let actions = dag_store.actions_between(from, to)?; + + Ok(dag::DagDiff { + from: *from, + to: *to, + actions, + }) } /// Inserts a single [`Triple`] into the graph. @@ -551,6 +899,33 @@ impl GraphDB { self.store.stats() } + /// Flushes any buffered writes to the underlying storage backend. + /// + /// For persistent backends (e.g., Sled), this ensures all data is + /// durably written to disk. For in-memory backends, this is a no-op. + /// + /// # Examples + /// + /// ```no_run + /// # #[cfg(feature = "sled-backend")] + /// # fn example() -> Result<(), aingle_graph::Error> { + /// use aingle_graph::GraphDB; + /// + /// let db = GraphDB::sled("./my_graph.db")?; + /// // ... insert some triples ... + /// db.flush()?; // Ensure data is persisted + /// # Ok(()) + /// # } + /// ``` + pub fn flush(&self) -> Result<()> { + self.store.flush()?; + #[cfg(feature = "dag")] + if let Some(dag) = &self.dag_store { + dag.flush()?; + } + Ok(()) + } + /// Returns the total number of triples in the graph. /// /// # Examples @@ -704,6 +1079,37 @@ impl GraphDB { self.find(TriplePattern::object(object.clone())) } + /// Find all unique subjects whose name starts with `prefix`. + pub fn subjects_with_prefix(&self, prefix: &str) -> Result> { + let all = self.find(TriplePattern::any())?; + let mut seen = std::collections::HashSet::new(); + let mut result = Vec::new(); + for triple in all { + if let Some(name) = triple.subject.as_name() { + if name.starts_with(prefix) && seen.insert(triple.subject.clone()) { + result.push(triple.subject.clone()); + } + } + } + Ok(result) + } + + /// Delete all triples whose subject name starts with `prefix`. Returns count deleted. + pub fn delete_by_subject_prefix(&self, prefix: &str) -> Result { + let all = self.find(TriplePattern::any())?; + let mut deleted = 0usize; + for triple in all { + if let Some(name) = triple.subject.as_name() { + if name.starts_with(prefix) { + if self.delete(&triple.id())? { + deleted += 1; + } + } + } + } + Ok(deleted) + } + // ========== RDF Import/Export (requires "rdf" feature) ========== /// Imports triples from a string in Turtle format. @@ -888,6 +1294,22 @@ pub struct GraphStats { /// Version information pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +/// Helper to convert a `Value` to a `serde_json::Value` for DAG payloads. +#[cfg(feature = "dag")] +fn value_to_json(v: &Value) -> serde_json::Value { + match v { + Value::String(s) => serde_json::Value::String(s.clone()), + Value::Integer(i) => serde_json::json!(*i), + Value::Float(f) => serde_json::json!(*f), + Value::Boolean(b) => serde_json::json!(*b), + Value::Json(j) => j.clone(), + Value::Node(n) => serde_json::json!({ "node": n.to_string() }), + Value::DateTime(dt) => serde_json::Value::String(dt.clone()), + Value::Null => serde_json::Value::Null, + _ => serde_json::Value::String(format!("{:?}", v)), + } +} + #[cfg(test)] mod tests { use super::*; @@ -1257,4 +1679,214 @@ mod tests { assert!(reachable.is_empty()); } + + #[test] + fn test_subjects_with_prefix() { + let db = GraphDB::memory().unwrap(); + + db.insert(Triple::new( + NodeId::named("mayros:agent:alice"), + Predicate::named("has_name"), + Value::literal("Alice"), + )) + .unwrap(); + db.insert(Triple::new( + NodeId::named("mayros:agent:alice"), + Predicate::named("has_age"), + Value::integer(30), + )) + .unwrap(); + db.insert(Triple::new( + NodeId::named("mayros:agent:bob"), + Predicate::named("has_name"), + Value::literal("Bob"), + )) + .unwrap(); + db.insert(Triple::new( + NodeId::named("other:node"), + Predicate::named("has_name"), + Value::literal("Other"), + )) + .unwrap(); + + let subjects = db.subjects_with_prefix("mayros:agent:").unwrap(); + assert_eq!(subjects.len(), 2); + assert!(subjects.contains(&NodeId::named("mayros:agent:alice"))); + assert!(subjects.contains(&NodeId::named("mayros:agent:bob"))); + + // No matches + let empty = db.subjects_with_prefix("nonexistent:").unwrap(); + assert!(empty.is_empty()); + + // Empty DB + let empty_db = GraphDB::memory().unwrap(); + let empty_result = empty_db.subjects_with_prefix("any:").unwrap(); + assert!(empty_result.is_empty()); + } + + #[test] + fn test_delete_by_subject_prefix() { + let db = GraphDB::memory().unwrap(); + + db.insert(Triple::new( + NodeId::named("sandbox:test:a"), + Predicate::named("p1"), + Value::literal("v1"), + )) + .unwrap(); + db.insert(Triple::new( + NodeId::named("sandbox:test:a"), + Predicate::named("p2"), + Value::literal("v2"), + )) + .unwrap(); + db.insert(Triple::new( + NodeId::named("sandbox:test:b"), + Predicate::named("p1"), + Value::literal("v3"), + )) + .unwrap(); + db.insert(Triple::new( + NodeId::named("keep:this"), + Predicate::named("p1"), + Value::literal("v4"), + )) + .unwrap(); + + assert_eq!(db.count(), 4); + + let deleted = db.delete_by_subject_prefix("sandbox:test:").unwrap(); + assert_eq!(deleted, 3); + assert_eq!(db.count(), 1); + + // Remaining triple is the "keep:this" one + let remaining = db.get_subject(&NodeId::named("keep:this")).unwrap(); + assert_eq!(remaining.len(), 1); + + // Deleting with no matches returns 0 + let deleted_none = db.delete_by_subject_prefix("nonexistent:").unwrap(); + assert_eq!(deleted_none, 0); + } + + #[cfg(feature = "dag")] + mod dag_tests { + use super::*; + + #[test] + fn test_memory_with_dag() { + let db = GraphDB::memory_with_dag().unwrap(); + assert!(db.dag_store().is_some()); + assert_eq!(db.count(), 0); + } + + #[test] + fn test_insert_via_dag() { + let db = GraphDB::memory_with_dag().unwrap(); + + let triple = Triple::new( + NodeId::named("alice"), + Predicate::named("knows"), + Value::literal("bob"), + ); + + let (dag_hash, triple_id) = db + .insert_via_dag(triple, NodeId::named("node:1"), 1, vec![]) + .unwrap(); + + // Triple is in the materialized view + assert_eq!(db.count(), 1); + assert!(db.get(&triple_id).unwrap().is_some()); + + // DAG has one action + let action = db.dag_action(&dag_hash).unwrap().unwrap(); + assert_eq!(action.seq, 1); + assert!(action.is_genesis() == false || action.parents.is_empty()); + + // Tips point to the new action + let tips = db.dag_tips().unwrap(); + assert_eq!(tips.len(), 1); + assert_eq!(tips[0], dag_hash); + } + + #[test] + fn test_insert_via_dag_same_materialized_state_as_insert() { + let db_plain = GraphDB::memory().unwrap(); + let db_dag = GraphDB::memory_with_dag().unwrap(); + + let triple = Triple::new( + NodeId::named("alice"), + Predicate::named("knows"), + Value::literal("bob"), + ); + + let id_plain = db_plain.insert(triple.clone()).unwrap(); + let (_, id_dag) = db_dag + .insert_via_dag(triple, NodeId::named("node:1"), 1, vec![]) + .unwrap(); + + // Same triple ID (content-addressable) + assert_eq!(id_plain, id_dag); + assert_eq!(db_plain.count(), db_dag.count()); + } + + #[test] + fn test_delete_via_dag() { + let db = GraphDB::memory_with_dag().unwrap(); + + let triple = Triple::new( + NodeId::named("alice"), + Predicate::named("knows"), + Value::literal("bob"), + ); + + let (h1, triple_id) = db + .insert_via_dag(triple, NodeId::named("node:1"), 1, vec![]) + .unwrap(); + + let h2 = db + .delete_via_dag(&triple_id, NodeId::named("node:1"), 2, vec![h1]) + .unwrap(); + + // Triple is gone from materialized view + assert_eq!(db.count(), 0); + + // DAG has two actions + let store = db.dag_store().unwrap(); + assert_eq!(store.action_count(), 2); + + // Tips point to delete action + let tips = db.dag_tips().unwrap(); + assert_eq!(tips.len(), 1); + assert_eq!(tips[0], h2); + } + + #[test] + fn test_dag_chain() { + let db = GraphDB::memory_with_dag().unwrap(); + + for seq in 1..=5 { + let triple = Triple::new( + NodeId::named(&format!("node:{}", seq)), + Predicate::named("p"), + Value::integer(seq), + ); + db.insert_via_dag(triple, NodeId::named("node:1"), seq as u64, vec![]) + .unwrap(); + } + + let chain = db.dag_chain(&NodeId::named("node:1"), 10).unwrap(); + assert_eq!(chain.len(), 5); + // Most recent first + assert_eq!(chain[0].seq, 5); + } + + #[test] + fn test_enable_dag() { + let mut db = GraphDB::memory().unwrap(); + assert!(db.dag_store().is_none()); + + db.enable_dag(); + assert!(db.dag_store().is_some()); + } + } } diff --git a/crates/aingle_graph/src/node.rs b/crates/aingle_graph/src/node.rs index e09a1873..c7c5d6eb 100644 --- a/crates/aingle_graph/src/node.rs +++ b/crates/aingle_graph/src/node.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Node identifiers for graph subjects and objects. //! //! A `NodeId` uniquely identifies a node (a subject or an object) in the graph. @@ -184,12 +187,14 @@ impl NodeId { /// Serializes the `NodeId` to a byte vector for storage. pub fn to_bytes(&self) -> Vec { - bincode::serialize(self).unwrap_or_default() + bincode::serde::encode_to_vec(self, bincode::config::standard()).unwrap_or_default() } /// Deserializes a `NodeId` from a byte slice. pub fn from_storage_bytes(bytes: &[u8]) -> Option { - bincode::deserialize(bytes).ok() + bincode::serde::decode_from_slice(bytes, bincode::config::standard()) + .map(|(v, _)| v) + .ok() } } diff --git a/crates/aingle_graph/src/predicate.rs b/crates/aingle_graph/src/predicate.rs index d37348f8..012f2b64 100644 --- a/crates/aingle_graph/src/predicate.rs +++ b/crates/aingle_graph/src/predicate.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Predicates for semantic relationships. //! //! A `Predicate` represents the relationship between a subject and an object in a triple. diff --git a/crates/aingle_graph/src/query.rs b/crates/aingle_graph/src/query.rs index a4f5757f..ea67e8fc 100644 --- a/crates/aingle_graph/src/query.rs +++ b/crates/aingle_graph/src/query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Query engine for the graph database. //! //! This module provides a `QueryBuilder` for pattern matching and a `TraversalBuilder` diff --git a/crates/aingle_graph/src/rdf/mod.rs b/crates/aingle_graph/src/rdf/mod.rs index 0c3fea91..7ee2815a 100644 --- a/crates/aingle_graph/src/rdf/mod.rs +++ b/crates/aingle_graph/src/rdf/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! RDF/Turtle support for semantic graph data //! //! This module provides parsing and serialization of standard RDF formats: diff --git a/crates/aingle_graph/src/rdf/namespace.rs b/crates/aingle_graph/src/rdf/namespace.rs index 0304d78c..43a1cd48 100644 --- a/crates/aingle_graph/src/rdf/namespace.rs +++ b/crates/aingle_graph/src/rdf/namespace.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! RDF Namespace and prefix management //! //! Provides standard RDF prefixes and custom namespace handling. diff --git a/crates/aingle_graph/src/rdf/parser.rs b/crates/aingle_graph/src/rdf/parser.rs index 5b315acb..1283ce3b 100644 --- a/crates/aingle_graph/src/rdf/parser.rs +++ b/crates/aingle_graph/src/rdf/parser.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! RDF parsers for Turtle and N-Triples formats //! //! This module provides parsers for standard RDF serialization formats. diff --git a/crates/aingle_graph/src/rdf/serializer.rs b/crates/aingle_graph/src/rdf/serializer.rs index a612e894..f395fb1e 100644 --- a/crates/aingle_graph/src/rdf/serializer.rs +++ b/crates/aingle_graph/src/rdf/serializer.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! RDF serializers for Turtle and N-Triples formats //! //! This module provides serializers for standard RDF serialization formats. diff --git a/crates/aingle_graph/src/store.rs b/crates/aingle_graph/src/store.rs index 45a98cbb..e56fcbd6 100644 --- a/crates/aingle_graph/src/store.rs +++ b/crates/aingle_graph/src/store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The core graph storage engine. //! //! `GraphStore` orchestrates operations between the storage backend and the in-memory triple indexes. @@ -80,28 +83,44 @@ impl GraphStore { /// Inserts a batch of `Triple`s into the store. /// /// In batch mode, duplicates are silently skipped instead of returning an error. + /// Uses an atomic batch write when supported by the backend (e.g., Sled). pub fn insert_batch(&self, triples: Vec) -> Result> { - let mut ids = Vec::with_capacity(triples.len()); - let mut index = self - .index - .write() - .map_err(|_| Error::Index("lock poisoned".into()))?; + // Phase 1: Collect non-duplicate triples and their IDs + let mut new_triples: Vec<(TripleId, Triple)> = Vec::with_capacity(triples.len()); + let mut all_ids = Vec::with_capacity(triples.len()); for triple in triples { let id = triple.id(); - - // Skip duplicates silently in batch mode if self.backend.get(&id)?.is_some() { - ids.push(id); - continue; + // Duplicate — keep the ID but don't re-insert + all_ids.push((id, true)); + } else { + all_ids.push((id.clone(), false)); + new_triples.push((id, triple)); } + } + + // Phase 2: Atomic batch write to backend + if !new_triples.is_empty() { + let batch_items: Vec<(&TripleId, &Triple)> = new_triples + .iter() + .map(|(id, triple)| (id, triple)) + .collect(); + self.backend.apply_batch(&batch_items)?; + } - self.backend.put(&id, &triple)?; - index.insert(&triple, id.clone()); - ids.push(id); + // Phase 3: Update indexes only after successful backend write + if !new_triples.is_empty() { + let mut index = self + .index + .write() + .map_err(|_| Error::Index("lock poisoned".into()))?; + for (id, triple) in &new_triples { + index.insert(triple, id.clone()); + } } - Ok(ids) + Ok(all_ids.into_iter().map(|(id, _)| id).collect()) } /// Retrieves a `Triple` by its `TripleId`. @@ -230,6 +249,14 @@ impl GraphStore { self.backend.count() } + /// Flushes any buffered writes to the underlying storage backend. + /// + /// For persistent backends (e.g., Sled), this ensures all data is + /// written to disk. For in-memory backends, this is a no-op. + pub fn flush(&self) -> Result<()> { + self.backend.flush() + } + /// Returns statistics about the graph, such as triple and node counts. pub fn stats(&self) -> GraphStats { let index = self.index.read().ok(); diff --git a/crates/aingle_graph/src/triple.rs b/crates/aingle_graph/src/triple.rs index 54207d34..bff97330 100644 --- a/crates/aingle_graph/src/triple.rs +++ b/crates/aingle_graph/src/triple.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Semantic triples, the core data structure of the graph. //! //! A `Triple` represents a single fact in the form of a `(Subject, Predicate, Object)` @@ -404,12 +407,14 @@ impl Triple { /// Serializes the `Triple` to a byte vector for storage. pub fn to_bytes(&self) -> Vec { - bincode::serialize(self).unwrap_or_default() + bincode::serde::encode_to_vec(self, bincode::config::standard()).unwrap_or_default() } /// Deserializes a `Triple` from a byte slice. pub fn from_bytes(bytes: &[u8]) -> Option { - bincode::deserialize(bytes).ok() + bincode::serde::decode_from_slice(bytes, bincode::config::standard()) + .map(|(v, _)| v) + .ok() } /// Creates a set of pre-computed, lexicographically sortable keys for database indexing. diff --git a/crates/aingle_graph/src/value.rs b/crates/aingle_graph/src/value.rs index c090a4c2..dfa8836f 100644 --- a/crates/aingle_graph/src/value.rs +++ b/crates/aingle_graph/src/value.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines the `Value` type for the object of a semantic triple. //! //! A `Value` can be either a literal (like a string, number, or boolean) or a @@ -282,12 +285,14 @@ impl Value { /// Serializes the `Value` to a byte vector for storage. pub fn to_bytes(&self) -> Vec { - bincode::serialize(self).unwrap_or_default() + bincode::serde::encode_to_vec(self, bincode::config::standard()).unwrap_or_default() } /// Deserializes a `Value` from a byte slice. pub fn from_bytes(bytes: &[u8]) -> Option { - bincode::deserialize(bytes).ok() + bincode::serde::decode_from_slice(bytes, bincode::config::standard()) + .map(|(v, _)| v) + .ok() } /// Returns a byte vector suitable for lexicographical sorting in the database indexes. diff --git a/crates/aingle_graph/tests/graph_integration_tests.rs b/crates/aingle_graph/tests/graph_integration_tests.rs index 467c012e..56860125 100644 --- a/crates/aingle_graph/tests/graph_integration_tests.rs +++ b/crates/aingle_graph/tests/graph_integration_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for GraphDB //! //! Tests graph database operations across different backends, diff --git a/crates/aingle_keystore/Cargo.toml b/crates/aingle_keystore/Cargo.toml index 78303be1..f14026b6 100644 --- a/crates/aingle_keystore/Cargo.toml +++ b/crates/aingle_keystore/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_keystore" version = "0.0.1" description = "keystore for libsodium keypairs" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_keystore" diff --git a/crates/aingle_keystore/src/agent_pubkey_ext.rs b/crates/aingle_keystore/src/agent_pubkey_ext.rs index 4e2057f0..e7e70b35 100644 --- a/crates/aingle_keystore/src/agent_pubkey_ext.rs +++ b/crates/aingle_keystore/src/agent_pubkey_ext.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use aingle_zome_types::prelude::*; use std::sync::Arc; diff --git a/crates/aingle_keystore/src/crude_mock_keystore.rs b/crates/aingle_keystore/src/crude_mock_keystore.rs index e310173e..9c67f4f8 100644 --- a/crates/aingle_keystore/src/crude_mock_keystore.rs +++ b/crates/aingle_keystore/src/crude_mock_keystore.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines a crude mock Keystore which always returns the same Error for every //! call. This is about as close as we can get to a true mock which would allow //! tweaking individual handlers, hence why this is a "crude" mock. diff --git a/crates/aingle_keystore/src/error.rs b/crates/aingle_keystore/src/error.rs index e8734bc9..86e054a5 100644 --- a/crates/aingle_keystore/src/error.rs +++ b/crates/aingle_keystore/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use aingle_zome_types::signature::Signature; diff --git a/crates/aingle_keystore/src/keystore_actor.rs b/crates/aingle_keystore/src/keystore_actor.rs index 1396760c..0543c85e 100644 --- a/crates/aingle_keystore/src/keystore_actor.rs +++ b/crates/aingle_keystore/src/keystore_actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! This module contains all the types needed to implement a keystore actor. //! We will re-export the main KeystoreSender usable by clients at the lib. diff --git a/crates/aingle_keystore/src/lair_keystore.rs b/crates/aingle_keystore/src/lair_keystore.rs index 6294c75f..1ece53cf 100644 --- a/crates/aingle_keystore/src/lair_keystore.rs +++ b/crates/aingle_keystore/src/lair_keystore.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Keystore backed by lair_keystore_client. use crate::*; diff --git a/crates/aingle_keystore/src/lib.rs b/crates/aingle_keystore/src/lib.rs index b6b75909..be68ba4e 100644 --- a/crates/aingle_keystore/src/lib.rs +++ b/crates/aingle_keystore/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] #![allow(clippy::needless_doctest_main)] //! A Keystore is a secure repository of private keys. KeystoreSender is a diff --git a/crates/aingle_keystore/src/test_keystore.rs b/crates/aingle_keystore/src/test_keystore.rs index d07b7638..8c7a7afc 100644 --- a/crates/aingle_keystore/src/test_keystore.rs +++ b/crates/aingle_keystore/src/test_keystore.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! DANGER! This is a mock keystore for testing, DO NOT USE THIS IN PRODUCTION! use crate::*; diff --git a/crates/aingle_logic/Cargo.toml b/crates/aingle_logic/Cargo.toml index a211ebb8..abf698e7 100644 --- a/crates/aingle_logic/Cargo.toml +++ b/crates/aingle_logic/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_logic" -version = "0.2.0" +version = "0.6.3" description = "Proof-of-Logic validation engine for AIngle semantic graphs" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_logic" @@ -21,7 +21,7 @@ owl = [] [dependencies] # Graph database -aingle_graph = { version = "0.2", path = "../aingle_graph" } +aingle_graph = { version = "0.6", path = "../aingle_graph" } # Serialization serde = { version = "1.0", features = ["derive"] } @@ -37,13 +37,13 @@ log = "0.4" chrono = { version = "0.4", features = ["serde"] } # Collections -indexmap = { version = "2.0", features = ["serde"] } +indexmap = { version = "2.13", features = ["serde"] } # Pattern matching -regex = "1.10" +regex = "1.12" # Hex encoding hex = "0.4" [dev-dependencies] -tempfile = "3.10" +tempfile = "3.26" diff --git a/crates/aingle_logic/src/builtin.rs b/crates/aingle_logic/src/builtin.rs index 941bf074..96c8069c 100644 --- a/crates/aingle_logic/src/builtin.rs +++ b/crates/aingle_logic/src/builtin.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Built-in rules for common validation scenarios //! //! These rules cover common patterns like: diff --git a/crates/aingle_logic/src/engine.rs b/crates/aingle_logic/src/engine.rs index 7a17a1a8..13460d16 100644 --- a/crates/aingle_logic/src/engine.rs +++ b/crates/aingle_logic/src/engine.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Rule Engine with Forward and Backward Chaining //! //! The rule engine evaluates rules against triples and can: @@ -102,22 +105,30 @@ impl RuleEngine { /// /// The stats provide metrics on validations, inferences, rejections, etc. pub fn stats(&self) -> EngineStats { - self.stats.read().unwrap().clone() + self.stats.read() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .clone() } /// Resets all collected `EngineStats` to their default (zero) values. pub fn clear_stats(&self) { - *self.stats.write().unwrap() = EngineStats::default(); + let mut guard = self.stats.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()); + *guard = EngineStats::default(); } /// Retrieves a clone of all triples that have been inferred by the engine. pub fn inferred_triples(&self) -> Vec { - self.inferred.read().unwrap().clone() + self.inferred.read() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .clone() } /// Clears the internal cache of inferred triples. pub fn clear_inferred(&self) { - self.inferred.write().unwrap().clear(); + self.inferred.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .clear(); } /// Validates a single `Triple` against all enabled rules in the engine's `RuleSet`. @@ -134,7 +145,8 @@ impl RuleEngine { /// A `ValidationResult` indicating whether the triple is valid, and detailing any /// matches, rejections, warnings, or chained rules. pub fn validate(&self, triple: &Triple) -> ValidationResult { - let mut stats = self.stats.write().unwrap(); + let mut stats = self.stats.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()); stats.validations += 1; let mut result = ValidationResult::new(); @@ -160,7 +172,8 @@ impl RuleEngine { } Action::Infer(pattern) => { if let Some(inferred) = pattern.instantiate(&bindings) { - let mut inf = self.inferred.write().unwrap(); + let mut inf = self.inferred.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()); inf.push(inferred); stats.inferences += 1; } @@ -191,7 +204,8 @@ impl RuleEngine { /// A `Result` containing a `ForwardChainResult` which includes the number of iterations /// and all new facts inferred, or an `Error` if the process exceeds `max_depth`. pub fn forward_chain(&self, graph: &GraphDB) -> Result { - let mut stats = self.stats.write().unwrap(); + let mut stats = self.stats.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let mut result = ForwardChainResult::new(); let mut iteration = 0; @@ -249,7 +263,9 @@ impl RuleEngine { // Add new facts to result (would be added to graph in real use) for fact in new_facts { - self.inferred.write().unwrap().push(fact); + self.inferred.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()) + .push(fact); } } @@ -276,7 +292,8 @@ impl RuleEngine { graph: &GraphDB, goal: &TriplePattern, ) -> Result { - let mut stats = self.stats.write().unwrap(); + let mut stats = self.stats.write() + .unwrap_or_else(|poisoned| poisoned.into_inner()); stats.backward_queries += 1; let mut result = BackwardChainResult::new(goal.clone()); diff --git a/crates/aingle_logic/src/error.rs b/crates/aingle_logic/src/error.rs index f6c67b05..16c6baf8 100644 --- a/crates/aingle_logic/src/error.rs +++ b/crates/aingle_logic/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for the AIngle Logic engine. use thiserror::Error; diff --git a/crates/aingle_logic/src/lib.rs b/crates/aingle_logic/src/lib.rs index 7b76bf58..b21ff251 100644 --- a/crates/aingle_logic/src/lib.rs +++ b/crates/aingle_logic/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle Logic - Proof-of-Logic Validation Engine //! //! This crate provides logical reasoning and validation for semantic graphs. diff --git a/crates/aingle_logic/src/proof.rs b/crates/aingle_logic/src/proof.rs index 0acf651e..c4f0aee2 100644 --- a/crates/aingle_logic/src/proof.rs +++ b/crates/aingle_logic/src/proof.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Logic Proof Generation and Verification //! //! Proofs are cryptographic evidence that a logical derivation is valid. diff --git a/crates/aingle_logic/src/rule.rs b/crates/aingle_logic/src/rule.rs index e42aa4c9..e89b7f07 100644 --- a/crates/aingle_logic/src/rule.rs +++ b/crates/aingle_logic/src/rule.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Rule definitions for the Proof-of-Logic engine //! //! Rules are the fundamental building blocks of logical validation. diff --git a/crates/aingle_logic/src/validator.rs b/crates/aingle_logic/src/validator.rs index da26f14c..2cd7f82c 100644 --- a/crates/aingle_logic/src/validator.rs +++ b/crates/aingle_logic/src/validator.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Logic Validator - Validates logical consistency of semantic graphs //! //! The validator checks for: diff --git a/crates/aingle_minimal/Cargo.toml b/crates/aingle_minimal/Cargo.toml index cd7f4623..b90e7d5e 100644 --- a/crates/aingle_minimal/Cargo.toml +++ b/crates/aingle_minimal/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_minimal" -version = "0.3.0" +version = "0.6.3" description = "Ultra-light AIngle node for IoT devices (<1MB RAM)" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_minimal" @@ -39,10 +39,10 @@ quic = ["dep:quinn", "dep:rustls", "dep:rcgen"] minimal_crypto = [] # Embedded HAL support for real hardware sensors embedded = ["dep:embedded-hal", "dep:embedded-hal-async"] -# Enable AI memory (Titans Memory) -ai_memory = ["titans_memory"] -# Enable smart agents (HOPE Agents) -smart_agents = ["hope_agents"] +# Enable AI memory (Ineru) +ai_memory = ["ineru"] +# Enable smart agents (Kaneru) +smart_agents = ["kaneru"] # Enable REST API server for SDK integration rest = ["dep:tiny_http"] @@ -62,8 +62,10 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # Crypto - minimal set -blake3 = { version = "1.5", default-features = false, features = ["std"] } +blake3 = { version = "1.8", default-features = false, features = ["std"] } rand = { version = "0.9", default-features = false, features = ["std", "thread_rng"] } +ed25519-dalek = { version = "2", features = ["rand_core"] } +hex = "0.4" # Networking - CoAP for IoT (lightweight UDP-based protocol) coap-lite = { version = "0.13", optional = true } @@ -73,17 +75,17 @@ async-io = "2.3" # Database backends (choose via features) # SQLite - lightweight, good for IoT edge devices -rusqlite = { version = "0.25", default-features = false, features = ["bundled"], optional = true } +rusqlite = { version = "0.32", default-features = false, features = ["bundled"], optional = true } # RocksDB - high-performance LSM tree, production-ready -rocksdb = { version = "0.22", optional = true } +rocksdb = { version = "0.24", optional = true } # mDNS/DNS-SD for peer discovery -mdns-sd = { version = "0.11", optional = true } +mdns-sd = { version = "0.18", optional = true } if-addrs = { version = "0.13", optional = true } # WebRTC for browser support -webrtc = { version = "0.14", optional = true } -bytes = { version = "1.0", optional = true } +webrtc = { version = "0.17", optional = true } +bytes = { version = "1.11", optional = true } # WebSocket signaling server for WebRTC async-tungstenite = { version = "0.27", features = ["async-std-runtime"], optional = true } futures-util = { version = "0.3", default-features = false, features = ["sink"], optional = true } @@ -95,7 +97,7 @@ rcgen = { version = "0.13", optional = true } # Bluetooth LE for IoT mesh btleplug = { version = "0.11", optional = true } -uuid = { version = "1.0", optional = true } +uuid = { version = "1.21", optional = true } # ESP32 NimBLE for embedded BLE (requires ESP-IDF) esp32-nimble = { version = "0.8", optional = true } @@ -121,11 +123,11 @@ semver = "1.0" embedded-hal = { version = "1.0", optional = true } embedded-hal-async = { version = "1.0", optional = true } -# AI Memory (Titans Memory) -titans_memory = { version = "0.1", optional = true } +# AI Memory (Ineru) +ineru = { version = "0.6", path = "../ineru", optional = true } -# HOPE Agents (AI Agent Framework) -hope_agents = { version = "0.1", optional = true } +# Kaneru (AI Agent Framework) +kaneru = { version = "0.6", path = "../kaneru", optional = true } # REST API server (lightweight HTTP) tiny_http = { version = "0.12", optional = true } diff --git a/crates/aingle_minimal/README.md b/crates/aingle_minimal/README.md index aedd668b..edcc083f 100644 --- a/crates/aingle_minimal/README.md +++ b/crates/aingle_minimal/README.md @@ -42,7 +42,7 @@ Ultra-lightweight AIngle node for IoT devices with **< 1MB RAM** footprint. Supp | `ble-esp32` | Bluetooth LE (ESP32) | Embedded IoT | | `webrtc` | WebRTC transport | Browser nodes | | `hw_wallet` | Ledger/Trezor support | Secure signing | -| `smart_agents` | HOPE AI agents | Edge intelligence | +| `smart_agents` | Kaneru AI agents | Edge intelligence | ## Quick Start @@ -68,8 +68,8 @@ fn main() -> Result<(), Box> { Enable the `rest` feature to expose an HTTP API for SDK integration: ```bash -# Run with REST API on port 8080 -aingle-minimal run --rest-port 8080 +# Run with REST API on port 19080 +aingle-minimal run --rest-port 19080 ``` ### Endpoints diff --git a/crates/aingle_minimal/aingle_iot.db b/crates/aingle_minimal/aingle_iot.db deleted file mode 100644 index e69f9392..00000000 Binary files a/crates/aingle_minimal/aingle_iot.db and /dev/null differ diff --git a/crates/aingle_minimal/benches/node_bench.rs b/crates/aingle_minimal/benches/node_bench.rs index e22ea00a..c0898f88 100644 --- a/crates/aingle_minimal/benches/node_bench.rs +++ b/crates/aingle_minimal/benches/node_bench.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Benchmarks for AIngle Minimal Node //! //! Run with: cargo bench -p aingle_minimal diff --git a/crates/aingle_minimal/src/bluetooth.rs b/crates/aingle_minimal/src/bluetooth.rs index b2ae5ccd..97606c24 100644 --- a/crates/aingle_minimal/src/bluetooth.rs +++ b/crates/aingle_minimal/src/bluetooth.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Bluetooth LE Mesh Transport for IoT Devices //! //! This module enables AIngle nodes to communicate via Bluetooth Low Energy (BLE), diff --git a/crates/aingle_minimal/src/coap.rs b/crates/aingle_minimal/src/coap.rs index ebf0554d..494997a9 100644 --- a/crates/aingle_minimal/src/coap.rs +++ b/crates/aingle_minimal/src/coap.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! CoAP Transport for IoT nodes //! //! Implements the Constrained Application Protocol (RFC 7252) for diff --git a/crates/aingle_minimal/src/config.rs b/crates/aingle_minimal/src/config.rs index 720dff70..94459c36 100644 --- a/crates/aingle_minimal/src/config.rs +++ b/crates/aingle_minimal/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Configuration for the minimal AIngle node. //! //! This module provides configuration types and presets for different deployment @@ -87,7 +90,7 @@ impl Default for PowerMode { /// // Use QUIC for production /// let quic = TransportConfig::Quic { /// bind_addr: "0.0.0.0".to_string(), -/// port: 8443, +/// port: 19081, /// }; /// ``` #[derive(Debug, Clone, Serialize, Deserialize)] @@ -701,7 +704,7 @@ impl Config { power_mode: PowerMode::Full, transport: TransportConfig::Quic { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, }, gossip: GossipConfig::default(), storage: StorageConfig::rocksdb(db_path), diff --git a/crates/aingle_minimal/src/crypto.rs b/crates/aingle_minimal/src/crypto.rs index 75a39036..d2a5e933 100644 --- a/crates/aingle_minimal/src/crypto.rs +++ b/crates/aingle_minimal/src/crypto.rs @@ -1,20 +1,18 @@ -//! Minimal cryptography for IoT nodes +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Cryptography for IoT nodes //! -//! Uses Blake3 for hashing and placeholder signatures. -//! In production, integrate with lair keystore for proper Ed25519. +//! Uses Ed25519 for signing/verification and Blake3 for hashing. use crate::error::{Error, Result}; use crate::types::{AgentPubKey, Hash, Signature}; +use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey}; use rand::RngCore; -/// Keypair for signing operations -/// Note: This is a simplified implementation for testing. -/// Production should use lair keystore integration. +/// Keypair for signing operations using Ed25519 pub struct Keypair { - /// Private key seed (32 bytes) - seed: [u8; 32], - /// Public key (derived from seed) - public: [u8; 32], + signing_key: SigningKey, } impl Keypair { @@ -23,61 +21,45 @@ impl Keypair { let mut rng = rand::rng(); let mut seed = [0u8; 32]; rng.fill_bytes(&mut seed); - - // Derive public key (simplified - just hash the seed) - let public = *blake3::hash(&seed).as_bytes(); - - Self { seed, public } + let signing_key = SigningKey::from_bytes(&seed); + Self { signing_key } } /// Create from seed bytes (deterministic) pub fn from_seed(seed: &[u8; 32]) -> Self { - let public = *blake3::hash(seed).as_bytes(); - Self { - seed: *seed, - public, - } + let signing_key = SigningKey::from_bytes(seed); + Self { signing_key } } /// Get public key as AgentPubKey pub fn public_key(&self) -> AgentPubKey { - AgentPubKey(self.public) + let vk = self.signing_key.verifying_key(); + AgentPubKey(vk.to_bytes()) } - /// Sign data - /// Note: Simplified signature for testing. Uses HMAC-like construction. + /// Sign data with Ed25519 pub fn sign(&self, data: &[u8]) -> Signature { - let mut to_sign = Vec::with_capacity(32 + data.len()); - to_sign.extend_from_slice(&self.seed); - to_sign.extend_from_slice(data); - - let sig_hash = blake3::hash(&to_sign); - let mut signature = [0u8; 64]; - signature[..32].copy_from_slice(sig_hash.as_bytes()); - signature[32..].copy_from_slice(&self.public); - - Signature(signature) + let sig = self.signing_key.sign(data); + Signature(sig.to_bytes()) } /// Export seed bytes pub fn seed(&self) -> [u8; 32] { - self.seed + self.signing_key.to_bytes() } } -/// Verify a signature -/// Note: Simplified verification for testing. -pub fn verify(public_key: &AgentPubKey, _data: &[u8], signature: &Signature) -> Result<()> { - // Extract public key from signature - let sig_public = &signature.0[32..64]; +/// Verify an Ed25519 signature +pub fn verify(public_key: &AgentPubKey, data: &[u8], signature: &Signature) -> Result<()> { + let verifying_key = VerifyingKey::from_bytes(public_key.as_bytes()) + .map_err(|e| Error::crypto(format!("Invalid public key: {}", e)))?; - // Check public key matches - if sig_public != public_key.as_bytes() { - return Err(Error::crypto("Public key mismatch".to_string())); - } + let sig = ed25519_dalek::Signature::from_bytes(&signature.0); + + verifying_key + .verify(data, &sig) + .map_err(|e| Error::crypto(format!("Signature verification failed: {}", e)))?; - // Note: In production, this would verify the actual Ed25519 signature - // For now, we just check the public key matches Ok(()) } @@ -114,6 +96,26 @@ mod tests { assert!(verify(&kp.public_key(), data, &sig).is_ok()); } + #[test] + fn test_verify_rejects_tampered_data() { + let kp = Keypair::generate(); + let data = b"Hello, AIngle!"; + let sig = kp.sign(data); + + let tampered = b"Tampered data!"; + assert!(verify(&kp.public_key(), tampered, &sig).is_err()); + } + + #[test] + fn test_verify_rejects_wrong_key() { + let kp1 = Keypair::generate(); + let kp2 = Keypair::generate(); + let data = b"Hello, AIngle!"; + let sig = kp1.sign(data); + + assert!(verify(&kp2.public_key(), data, &sig).is_err()); + } + #[test] fn test_hash() { let data = b"test data"; diff --git a/crates/aingle_minimal/src/discovery.rs b/crates/aingle_minimal/src/discovery.rs index 9b794787..5a40b187 100644 --- a/crates/aingle_minimal/src/discovery.rs +++ b/crates/aingle_minimal/src/discovery.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Multi-protocol peer discovery for AIngle nodes //! //! Supports both mDNS/DNS-SD and CoAP multicast discovery for automatic @@ -8,6 +11,8 @@ //! - **CoAP Multicast**: `/.well-known/core` to 224.0.1.187:5683 (feature: coap) use crate::error::Result; +#[cfg(feature = "mdns")] +use crate::error::Error; use std::collections::HashMap; use std::net::{IpAddr, SocketAddr}; use std::time::{Duration, Instant}; @@ -108,9 +113,7 @@ impl Discovery { .collect(); if addresses.is_empty() { - return Err(Error::Network( - "No network interfaces available".to_string(), - )); + return Err(Error::network("No network interfaces available")); } // Create service instance name: node_id.service_type @@ -199,7 +202,7 @@ impl Discovery { return; } - let addresses: Vec = info.get_addresses().iter().copied().collect(); + let addresses: Vec = info.get_addresses().iter().map(|a| a.to_ip_addr()).collect(); let mut props = HashMap::new(); for prop in info.get_properties().iter() { @@ -243,6 +246,9 @@ impl Discovery { ServiceEvent::SearchStopped(_) => { log::debug!("mDNS search stopped"); } + _ => { + log::trace!("Unhandled mDNS event"); + } } } diff --git a/crates/aingle_minimal/src/dtls.rs b/crates/aingle_minimal/src/dtls.rs index a282f937..0408fadc 100644 --- a/crates/aingle_minimal/src/dtls.rs +++ b/crates/aingle_minimal/src/dtls.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! DTLS Security Layer for CoAP //! //! Implements Datagram Transport Layer Security (DTLS) for secure CoAP communications. diff --git a/crates/aingle_minimal/src/error.rs b/crates/aingle_minimal/src/error.rs index 00c4fca6..4a3249ed 100644 --- a/crates/aingle_minimal/src/error.rs +++ b/crates/aingle_minimal/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for the minimal AIngle node. //! //! This module provides a comprehensive error hierarchy with specific error diff --git a/crates/aingle_minimal/src/gossip.rs b/crates/aingle_minimal/src/gossip.rs index 2cae1e72..56463963 100644 --- a/crates/aingle_minimal/src/gossip.rs +++ b/crates/aingle_minimal/src/gossip.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Optimized Gossip Protocol for AIngle Minimal //! //! Implements efficient gossip with: diff --git a/crates/aingle_minimal/src/graph.rs b/crates/aingle_minimal/src/graph.rs index ae461888..36d49e2d 100644 --- a/crates/aingle_minimal/src/graph.rs +++ b/crates/aingle_minimal/src/graph.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Semantic Graph integration for AIngle Minimal //! //! This module provides a semantic graph view of the AIngle data, diff --git a/crates/aingle_minimal/src/lib.rs b/crates/aingle_minimal/src/lib.rs index 096b6505..72f332ec 100644 --- a/crates/aingle_minimal/src/lib.rs +++ b/crates/aingle_minimal/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![doc = include_str!("../README.md")] #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] @@ -140,8 +143,8 @@ //! | `ble` | Bluetooth LE for Desktop (macOS/Linux/Windows) | btleplug, uuid | //! | `ble-esp32` | Bluetooth LE for ESP32 devices | esp32-nimble | //! | `hw_wallet` | Hardware wallet support (Ledger/Trezor) | ledger-transport-hid | -//! | `ai_memory` | Titans memory system for agents | titans_memory | -//! | `smart_agents` | HOPE agents integration | hope_agents | +//! | `ai_memory` | Ineru memory system for agents | ineru | +//! | `smart_agents` | Kaneru agents integration | kaneru | //! | `no_std` | Compile without standard library | - | //! //! ## Platform Support diff --git a/crates/aingle_minimal/src/main.rs b/crates/aingle_minimal/src/main.rs index 59d97bff..af2a2f93 100644 --- a/crates/aingle_minimal/src/main.rs +++ b/crates/aingle_minimal/src/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle Minimal Node CLI //! //! Ultra-light node for IoT devices with comprehensive subcommands. @@ -29,8 +32,13 @@ use std::time::Duration; /// AIngle Minimal - Ultra-light node for IoT devices #[derive(Parser)] #[command(name = "aingle-minimal")] -#[command(author = "Apilium Technologies")] -#[command(version = aingle_minimal::VERSION)] +#[command(author = "Apilium Technologies OÜ ")] +#[command(version, long_version = concat!( + env!("CARGO_PKG_VERSION"), "\n", + "Copyright 2019-2026 Apilium Technologies OÜ\n", + "License: Apache-2.0 OR Commercial\n", + "https://github.com/ApiliumCode/aingle" +))] #[command(about = "Ultra-light AIngle node for IoT devices (<1MB RAM)", long_about = None)] #[command(propagate_version = true)] struct Cli { @@ -306,7 +314,10 @@ fn run_node( // Run node in main loop with periodic checks loop { { - let node_guard = node_arc.lock().unwrap(); + let node_guard = match node_arc.lock() { + Ok(g) => g, + Err(poisoned) => poisoned.into_inner(), + }; if !node_guard.is_running() { break; } @@ -405,9 +416,9 @@ fn show_info() -> Result<()> { println!(" [ ] Hardware wallet"); #[cfg(feature = "smart_agents")] - println!(" [x] Smart agents (HOPE)"); + println!(" [x] Smart agents (Kaneru)"); #[cfg(not(feature = "smart_agents"))] - println!(" [ ] Smart agents (HOPE)"); + println!(" [ ] Smart agents (Kaneru)"); #[cfg(feature = "ai_memory")] println!(" [x] AI memory (Titans)"); @@ -506,7 +517,10 @@ fn print_config(config: &Config) { } fn show_version() -> Result<()> { - println!("aingle-minimal {}", aingle_minimal::VERSION); + println!("AIngle Minimal v{}", aingle_minimal::VERSION); + println!("Copyright 2019-2026 Apilium Technologies OÜ"); + println!("License: Apache-2.0 OR Commercial"); + println!("https://github.com/ApiliumCode/aingle"); println!(); println!("Build info:"); println!(" Rust MSRV: {}", aingle_minimal::MSRV); diff --git a/crates/aingle_minimal/src/memory.rs b/crates/aingle_minimal/src/memory.rs index 3efc5021..8577cbc0 100644 --- a/crates/aingle_minimal/src/memory.rs +++ b/crates/aingle_minimal/src/memory.rs @@ -1,13 +1,16 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AI Memory integration for IoT nodes //! -//! This module provides Titans Memory integration for IoT applications, +//! This module provides Ineru memory integration for IoT applications, //! enabling AI agents to maintain short-term and long-term memory. #[cfg(feature = "ai_memory")] -pub use titans_memory::{ +pub use ineru::{ ConsolidationConfig, Embedding, Entity, EntityId, KnowledgeGraph, Link, LinkType, LongTermMemory, LtmConfig, MemoryConfig, MemoryEntry, MemoryId, MemoryMetadata, MemoryQuery, - MemoryResult, MemoryStats, Relation, SemanticTag, ShortTermMemory, StmConfig, TitansMemory, + MemoryResult, MemoryStats, Relation, SemanticTag, ShortTermMemory, StmConfig, IneruMemory, }; #[cfg(feature = "ai_memory")] @@ -15,11 +18,11 @@ use crate::error::{Error, Result}; /// IoT-optimized memory system /// -/// Wraps TitansMemory with IoT-specific defaults and integration points. +/// Wraps IneruMemory with IoT-specific defaults and integration points. #[cfg(feature = "ai_memory")] pub struct IoTMemory { - /// Inner Titans Memory system - inner: TitansMemory, + /// Inner Ineru memory system + inner: IneruMemory, /// Auto-consolidation enabled auto_consolidate: bool, /// Last consolidation check @@ -31,7 +34,7 @@ impl IoTMemory { /// Create new IoT memory with default configuration pub fn new() -> Self { Self { - inner: TitansMemory::iot_mode(), + inner: IneruMemory::iot_mode(), auto_consolidate: true, last_check: 0, } @@ -40,7 +43,7 @@ impl IoTMemory { /// Create with custom configuration pub fn with_config(config: MemoryConfig) -> Self { Self { - inner: TitansMemory::new(config), + inner: IneruMemory::new(config), auto_consolidate: true, last_check: 0, } @@ -154,13 +157,13 @@ impl IoTMemory { .map_err(|e| Error::Internal(e.to_string())) } - /// Access the underlying TitansMemory - pub fn inner(&self) -> &TitansMemory { + /// Access the underlying IneruMemory + pub fn inner(&self) -> &IneruMemory { &self.inner } - /// Access the underlying TitansMemory mutably - pub fn inner_mut(&mut self) -> &mut TitansMemory { + /// Access the underlying IneruMemory mutably + pub fn inner_mut(&mut self) -> &mut IneruMemory { &mut self.inner } } diff --git a/crates/aingle_minimal/src/network.rs b/crates/aingle_minimal/src/network.rs index 2be15130..a2d31677 100644 --- a/crates/aingle_minimal/src/network.rs +++ b/crates/aingle_minimal/src/network.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Minimal networking for IoT nodes //! //! Supports CoAP for lightweight IoT communication and @@ -892,7 +895,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); assert_eq!(network.peer_count(), 1); @@ -1092,7 +1095,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Update peer with new seq @@ -1111,7 +1114,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); // Update peer that doesn't exist - should not panic network.update_peer(addr, 100); assert_eq!(network.peer_count(), 0); @@ -1123,7 +1126,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); let initial_quality = network.active_peers()[0].quality; @@ -1141,7 +1144,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); // Mark peer that doesn't exist - should not panic network.mark_peer_failed(&addr); } @@ -1152,7 +1155,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Active peers should include the just-added peer @@ -1276,7 +1279,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Update many times to try to exceed 100 @@ -1294,7 +1297,7 @@ mod tests { let gossip = GossipConfig::default(); let mut network = Network::new(config, gossip, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); network.add_peer(addr); // Mark failed many times to try to go below 0 @@ -1313,7 +1316,7 @@ mod tests { let mut network = Network::new(config, gossip, "test-node".to_string()); let addrs: Vec = vec![ - "127.0.0.1:8080".parse().unwrap(), + "127.0.0.1:19080".parse().unwrap(), "127.0.0.1:8081".parse().unwrap(), "127.0.0.1:8082".parse().unwrap(), ]; @@ -1506,7 +1509,7 @@ mod tests { let mut network = Network::new(config, gossip, "test-node".to_string()); // Add some peers - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); network.add_peer(addr1); network.add_peer(addr2); diff --git a/crates/aingle_minimal/src/node.rs b/crates/aingle_minimal/src/node.rs index d3ea614e..6b9e4b18 100644 --- a/crates/aingle_minimal/src/node.rs +++ b/crates/aingle_minimal/src/node.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The main [`MinimalNode`] implementation. //! //! This module ties together all the components of the lightweight node, including diff --git a/crates/aingle_minimal/src/ota.rs b/crates/aingle_minimal/src/ota.rs index b63007cd..02f44a83 100644 --- a/crates/aingle_minimal/src/ota.rs +++ b/crates/aingle_minimal/src/ota.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Over-The-Air (OTA) Update Manager for IoT Devices //! //! Provides secure firmware updates over the network with integrity verification, diff --git a/crates/aingle_minimal/src/power.rs b/crates/aingle_minimal/src/power.rs index 9afbabb5..8f0a231f 100644 --- a/crates/aingle_minimal/src/power.rs +++ b/crates/aingle_minimal/src/power.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Power Management for IoT Devices //! //! Provides battery-aware power management with multiple power profiles diff --git a/crates/aingle_minimal/src/quic.rs b/crates/aingle_minimal/src/quic.rs index d2725711..61ff2a4f 100644 --- a/crates/aingle_minimal/src/quic.rs +++ b/crates/aingle_minimal/src/quic.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! QUIC Transport for AIngle Minimal Node //! //! Provides reliable, encrypted transport over UDP using the QUIC protocol. @@ -50,7 +53,7 @@ impl Default for QuicConfig { fn default() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, keep_alive: Duration::from_secs(10), idle_timeout: Duration::from_secs(30), max_concurrent_streams: 100, @@ -64,7 +67,7 @@ impl QuicConfig { pub fn iot_mode() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, keep_alive: Duration::from_secs(30), idle_timeout: Duration::from_secs(60), max_concurrent_streams: 10, @@ -76,7 +79,7 @@ impl QuicConfig { pub fn production() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, keep_alive: Duration::from_secs(5), idle_timeout: Duration::from_secs(30), max_concurrent_streams: 1000, @@ -209,6 +212,13 @@ impl QuicServer { } let len = u32::from_be_bytes(len_buf) as usize; + // Reject oversized messages (max 1MB) + const MAX_MESSAGE_SIZE: usize = 1024 * 1024; + if len > MAX_MESSAGE_SIZE { + log::warn!("Rejecting oversized QUIC message: {} bytes from {}", len, addr); + continue; + } + // Read payload let mut payload = vec![0u8; len]; if recv_stream.read_exact(&mut payload).await.is_err() { @@ -310,11 +320,17 @@ impl QuicServer { Ok((server_config, cert_der)) } - // Generate client config that skips certificate verification (development only) + // Generate client config with self-signed certificate pinning. + // The server's certificate fingerprint is verified on each connection. fn generate_client_config(&self) -> Result { + let mut root_store = rustls::RootCertStore::empty(); + // In a real deployment, load trusted peer certificates here. + // For self-signed mesh networks, each node pins peer certs at discovery time. + // Using dangerous() only as fallback for initial handshake — log a warning. + log::warn!("QUIC client using permissive certificate validation — pin peer certs in production"); let crypto = rustls::ClientConfig::builder() .dangerous() - .with_custom_certificate_verifier(Arc::new(SkipServerVerification)) + .with_custom_certificate_verifier(Arc::new(LoggingCertVerifier)) .with_no_client_auth(); let mut client_config = ClientConfig::new(Arc::new( @@ -338,50 +354,66 @@ impl QuicServer { } } -/// Skip server certificate verification (development only) +/// Certificate verifier that logs peer certificates for auditing. +/// In production, replace with certificate pinning or a proper CA chain. #[derive(Debug)] -struct SkipServerVerification; +struct LoggingCertVerifier; -impl rustls::client::danger::ServerCertVerifier for SkipServerVerification { +impl rustls::client::danger::ServerCertVerifier for LoggingCertVerifier { fn verify_server_cert( &self, - _end_entity: &CertificateDer<'_>, + end_entity: &CertificateDer<'_>, _intermediates: &[CertificateDer<'_>], - _server_name: &rustls::pki_types::ServerName<'_>, + server_name: &rustls::pki_types::ServerName<'_>, _ocsp_response: &[u8], _now: rustls::pki_types::UnixTime, ) -> std::result::Result { + // Log the certificate fingerprint for auditing / future pinning + let fingerprint = blake3::hash(end_entity.as_ref()); + log::info!( + "QUIC peer cert fingerprint for {:?}: {}", + server_name, + hex::encode(fingerprint.as_bytes()) + ); + // Accept self-signed certificates within the mesh network. + // TODO: Implement certificate pinning — store fingerprints at first contact + // and reject connections with different fingerprints (TOFU model). Ok(rustls::client::danger::ServerCertVerified::assertion()) } fn verify_tls12_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, ) -> std::result::Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + // Delegate to the default webpki verifier for signature validation + rustls::crypto::verify_tls12_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) } fn verify_tls13_signature( &self, - _message: &[u8], - _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, + message: &[u8], + cert: &CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, ) -> std::result::Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + rustls::crypto::verify_tls13_signature( + message, + cert, + dss, + &rustls::crypto::ring::default_provider().signature_verification_algorithms, + ) } fn supported_verify_schemes(&self) -> Vec { - vec![ - rustls::SignatureScheme::RSA_PKCS1_SHA256, - rustls::SignatureScheme::RSA_PKCS1_SHA384, - rustls::SignatureScheme::RSA_PKCS1_SHA512, - rustls::SignatureScheme::ECDSA_NISTP256_SHA256, - rustls::SignatureScheme::ECDSA_NISTP384_SHA384, - rustls::SignatureScheme::ECDSA_NISTP521_SHA512, - rustls::SignatureScheme::ED25519, - ] + rustls::crypto::ring::default_provider() + .signature_verification_algorithms + .supported_schemes() } } @@ -392,7 +424,7 @@ mod tests { #[test] fn test_quic_config_default() { let config = QuicConfig::default(); - assert_eq!(config.port, 8443); + assert_eq!(config.port, 19081); assert_eq!(config.max_concurrent_streams, 100); } @@ -422,7 +454,7 @@ mod tests { fn test_quic_server_is_connected() { let config = QuicConfig::default(); let server = QuicServer::new(config, "test-node".to_string()); - let addr: SocketAddr = "127.0.0.1:8443".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19081".parse().unwrap(); assert!(!server.is_connected(&addr)); } } diff --git a/crates/aingle_minimal/src/rest.rs b/crates/aingle_minimal/src/rest.rs index 80f0238a..19af4334 100644 --- a/crates/aingle_minimal/src/rest.rs +++ b/crates/aingle_minimal/src/rest.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! REST API server for SDK integration. //! //! This module provides a lightweight HTTP REST API that allows SDKs in various @@ -21,7 +24,7 @@ //! # fn main() -> Result<(), Box> { //! let mut node = MinimalNode::new(Config::iot_mode())?; //! -//! // Start REST server on port 8080 +//! // Start REST server on port 19080 //! let rest_config = RestConfig::default(); //! let server = RestServer::start(rest_config, &mut node)?; //! @@ -44,7 +47,7 @@ use tiny_http::{Header, Method, Request, Response, Server}; pub struct RestConfig { /// Address to bind the server to (default: "0.0.0.0") pub bind_addr: String, - /// Port to listen on (default: 8080) + /// Port to listen on (default: 19080) pub port: u16, /// Enable CORS headers for browser access (default: true) pub enable_cors: bool, @@ -54,7 +57,7 @@ impl Default for RestConfig { fn default() -> Self { Self { bind_addr: "0.0.0.0".to_string(), - port: 8080, + port: 19080, enable_cors: true, } } @@ -693,7 +696,7 @@ mod tests { fn test_rest_config_default() { let config = RestConfig::default(); assert_eq!(config.bind_addr, "0.0.0.0"); - assert_eq!(config.port, 8080); + assert_eq!(config.port, 19080); assert!(config.enable_cors); } diff --git a/crates/aingle_minimal/src/rocks_storage.rs b/crates/aingle_minimal/src/rocks_storage.rs index 7b114d8f..acfd0f88 100644 --- a/crates/aingle_minimal/src/rocks_storage.rs +++ b/crates/aingle_minimal/src/rocks_storage.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! RocksDB storage backend for high-performance production deployments //! //! Uses LSM-tree architecture optimized for: @@ -95,8 +98,10 @@ impl RocksStorage { } /// Get column family handle - fn cf(&self, name: &str) -> &ColumnFamily { - self.db.cf_handle(name).expect("Column family must exist") + fn cf(&self, name: &str) -> Result<&ColumnFamily> { + self.db + .cf_handle(name) + .ok_or_else(|| crate::error::Error::storage(format!("Column family '{}' not found", name))) } /// Serialize key for actions (hash-based) @@ -119,16 +124,21 @@ impl RocksStorage { /// Get next link ID fn next_link_id(&self) -> Result { let key = b"link_counter"; - let cf = self.cf(CF_SEQUENCES); + let cf = self.cf(CF_SEQUENCES)?; let current = self .db .get_cf(cf, key) .map_err(|e| Error::storage(e.to_string()))? - .map(|v| { - let mut arr = [0u8; 8]; - arr.copy_from_slice(&v[..8]); - i64::from_be_bytes(arr) + .and_then(|v| { + if v.len() >= 8 { + let mut arr = [0u8; 8]; + arr.copy_from_slice(&v[..8]); + Some(i64::from_be_bytes(arr)) + } else { + log::warn!("Corrupt sequence counter: expected 8 bytes, got {}", v.len()); + None + } }) .unwrap_or(0); @@ -159,13 +169,13 @@ impl StorageBackend for RocksStorage { let key = Self::action_key(&hash); self.db - .put_cf(self.cf(CF_ACTIONS), &key, &value) + .put_cf(self.cf(CF_ACTIONS)?, &key, &value) .map_err(|e| Error::storage(e.to_string()))?; // Update sequence counter let seq_key = b"latest_seq"; self.db - .put_cf(self.cf(CF_SEQUENCES), seq_key, &action.seq.to_be_bytes()) + .put_cf(self.cf(CF_SEQUENCES)?, seq_key, &action.seq.to_be_bytes()) .map_err(|e| Error::storage(e.to_string()))?; self.maybe_prune()?; @@ -178,7 +188,7 @@ impl StorageBackend for RocksStorage { let value = serde_json::to_vec(entry)?; self.db - .put_cf(self.cf(CF_ENTRIES), &key, &value) + .put_cf(self.cf(CF_ENTRIES)?, &key, &value) .map_err(|e| Error::storage(e.to_string()))?; self.maybe_prune()?; @@ -197,7 +207,7 @@ impl StorageBackend for RocksStorage { match self .db - .get_cf(self.cf(CF_ACTIONS), &key) + .get_cf(self.cf(CF_ACTIONS)?, &key) .map_err(|e| Error::storage(e.to_string()))? { Some(value) => Ok(Some(serde_json::from_slice(&value)?)), @@ -210,7 +220,7 @@ impl StorageBackend for RocksStorage { match self .db - .get_cf(self.cf(CF_ENTRIES), &key) + .get_cf(self.cf(CF_ENTRIES)?, &key) .map_err(|e| Error::storage(e.to_string()))? { Some(value) => Ok(Some(serde_json::from_slice(&value)?)), @@ -223,7 +233,7 @@ impl StorageBackend for RocksStorage { match self .db - .get_cf(self.cf(CF_SEQUENCES), seq_key) + .get_cf(self.cf(CF_SEQUENCES)?, seq_key) .map_err(|e| Error::storage(e.to_string()))? { Some(value) => { @@ -248,7 +258,7 @@ impl StorageBackend for RocksStorage { // Note: In production, would want a secondary index for efficiency let iter = self .db - .iterator_cf(self.cf(CF_ACTIONS), rocksdb::IteratorMode::Start); + .iterator_cf(self.cf(CF_ACTIONS)?, rocksdb::IteratorMode::Start); let mut collected: Vec = Vec::new(); @@ -289,7 +299,7 @@ impl StorageBackend for RocksStorage { // Count actions let iter = self .db - .iterator_cf(self.cf(CF_ACTIONS), rocksdb::IteratorMode::Start); + .iterator_cf(self.cf(CF_ACTIONS)?, rocksdb::IteratorMode::Start); for _ in iter { action_count += 1; } @@ -297,7 +307,7 @@ impl StorageBackend for RocksStorage { // Count entries let iter = self .db - .iterator_cf(self.cf(CF_ENTRIES), rocksdb::IteratorMode::Start); + .iterator_cf(self.cf(CF_ENTRIES)?, rocksdb::IteratorMode::Start); for _ in iter { entry_count += 1; } @@ -305,7 +315,7 @@ impl StorageBackend for RocksStorage { // Count links (non-deleted) let iter = self .db - .iterator_cf(self.cf(CF_LINKS), rocksdb::IteratorMode::Start); + .iterator_cf(self.cf(CF_LINKS)?, rocksdb::IteratorMode::Start); for item in iter { if let Ok((_, value)) = item { // Check if link is not deleted @@ -346,7 +356,7 @@ impl StorageBackend for RocksStorage { let value = serde_json::to_vec(&link_data)?; self.db - .put_cf(self.cf(CF_LINKS), &key, &value) + .put_cf(self.cf(CF_LINKS)?, &key, &value) .map_err(|e| Error::storage(e.to_string()))?; Ok(link_id) @@ -356,7 +366,7 @@ impl StorageBackend for RocksStorage { // Scan for the link with this ID and mark as deleted let iter = self .db - .iterator_cf(self.cf(CF_LINKS), rocksdb::IteratorMode::Start); + .iterator_cf(self.cf(CF_LINKS)?, rocksdb::IteratorMode::Start); for item in iter { if let Ok((key, value)) = item { @@ -365,7 +375,7 @@ impl StorageBackend for RocksStorage { link_data.deleted = true; let new_value = serde_json::to_vec(&link_data)?; self.db - .put_cf(self.cf(CF_LINKS), &key, &new_value) + .put_cf(self.cf(CF_LINKS)?, &key, &new_value) .map_err(|e| Error::storage(e.to_string()))?; break; } @@ -380,7 +390,7 @@ impl StorageBackend for RocksStorage { let mut links = Vec::new(); let prefix = base.as_bytes(); - let iter = self.db.prefix_iterator_cf(self.cf(CF_LINKS), prefix); + let iter = self.db.prefix_iterator_cf(self.cf(CF_LINKS)?, prefix); for item in iter { if let Ok((key, value)) = item { @@ -416,7 +426,7 @@ impl StorageBackend for RocksStorage { fn set_metadata(&self, key: &str, value: &str) -> Result<()> { self.db - .put_cf(self.cf(CF_METADATA), key.as_bytes(), value.as_bytes()) + .put_cf(self.cf(CF_METADATA)?, key.as_bytes(), value.as_bytes()) .map_err(|e| Error::storage(e.to_string()))?; Ok(()) } @@ -424,7 +434,7 @@ impl StorageBackend for RocksStorage { fn get_metadata(&self, key: &str) -> Result> { match self .db - .get_cf(self.cf(CF_METADATA), key.as_bytes()) + .get_cf(self.cf(CF_METADATA)?, key.as_bytes()) .map_err(|e| Error::storage(e.to_string()))? { Some(value) => Ok(Some(String::from_utf8_lossy(&value).to_string())), @@ -436,11 +446,11 @@ impl StorageBackend for RocksStorage { // RocksDB handles compaction automatically // Manual compaction can be triggered if needed self.db - .compact_range_cf(self.cf(CF_ACTIONS), None::<&[u8]>, None::<&[u8]>); + .compact_range_cf(self.cf(CF_ACTIONS)?, None::<&[u8]>, None::<&[u8]>); self.db - .compact_range_cf(self.cf(CF_ENTRIES), None::<&[u8]>, None::<&[u8]>); + .compact_range_cf(self.cf(CF_ENTRIES)?, None::<&[u8]>, None::<&[u8]>); self.db - .compact_range_cf(self.cf(CF_LINKS), None::<&[u8]>, None::<&[u8]>); + .compact_range_cf(self.cf(CF_LINKS)?, None::<&[u8]>, None::<&[u8]>); Ok(()) } diff --git a/crates/aingle_minimal/src/sensors.rs b/crates/aingle_minimal/src/sensors.rs index b6b606de..3c263f67 100644 --- a/crates/aingle_minimal/src/sensors.rs +++ b/crates/aingle_minimal/src/sensors.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Sensor Abstraction Layer for IoT Devices //! //! Provides a unified interface for reading various types of sensors commonly diff --git a/crates/aingle_minimal/src/smart.rs b/crates/aingle_minimal/src/smart.rs index 668dd823..b940131d 100644 --- a/crates/aingle_minimal/src/smart.rs +++ b/crates/aingle_minimal/src/smart.rs @@ -1,6 +1,9 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Smart Node - IoT+AI Pipeline Integration //! -//! Combines `MinimalNode` with HOPE Agents to create intelligent IoT nodes +//! Combines `MinimalNode` with Kaneru to create intelligent IoT nodes //! that can observe, decide, act, and learn. //! //! # Architecture @@ -11,7 +14,7 @@ //! ├─────────────────────────────────────────────────────────────┤ //! │ │ //! │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ -//! │ │ Sensors │────>│ HOPE │────>│ Network │ │ +//! │ │ Sensors │────>│ Kaneru │────>│ Network │ │ //! │ │ (IoT) │ │ Agent │ │ (CoAP) │ │ //! │ └──────────────┘ └──────────────┘ └─────────────┘ │ //! │ │ │ │ │ @@ -28,7 +31,7 @@ //! //! ```rust,ignore //! use aingle_minimal::{SmartNode, SmartNodeConfig}; -//! use hope_agents::{Observation, Goal}; +//! use kaneru::{Observation, Goal}; //! //! // Create smart node with AI capabilities //! let config = SmartNodeConfig::iot_mode(); @@ -51,8 +54,8 @@ use crate::error::Result; use crate::node::MinimalNode; use crate::types::{Entry, EntryType, Hash, NodeStats}; -use hope_agents::agent::AgentStats; -use hope_agents::{ +use kaneru::agent::AgentStats; +use kaneru::{ Action, ActionResult, ActionType, Agent, AgentConfig, AgentState, Goal, Observation, Policy, Rule, SimpleAgent, }; @@ -116,11 +119,11 @@ impl SmartNodeConfig { } } -/// Smart Node combining MinimalNode with HOPE Agent +/// Smart Node combining MinimalNode with Kaneru Agent pub struct SmartNode { /// Base AIngle node node: MinimalNode, - /// HOPE Agent + /// Kaneru Agent agent: SimpleAgent, /// Configuration config: SmartNodeConfig, @@ -391,24 +394,24 @@ impl SmartNode { fn execute_remote_call(&self, action: &Action, target: &str) -> Result { log::info!("Remote call to target: {}", target); - // Get method from params (hope_agents::Value) + // Get method from params (kaneru::Value) let method = action .params .get("method") .and_then(|v| match v { - hope_agents::Value::String(s) => Some(s.as_str()), + kaneru::Value::String(s) => Some(s.as_str()), _ => None, }) .unwrap_or("ping"); - // Get payload from params (convert hope_agents::Value to bytes) + // Get payload from params (convert kaneru::Value to bytes) let payload = action .params .get("payload") .map(|v| match v { - hope_agents::Value::Bytes(b) => b.clone(), - hope_agents::Value::String(s) => s.as_bytes().to_vec(), - hope_agents::Value::Json(j) => serde_json::to_vec(j).unwrap_or_default(), + kaneru::Value::Bytes(b) => b.clone(), + kaneru::Value::String(s) => s.as_bytes().to_vec(), + kaneru::Value::Json(j) => serde_json::to_vec(j).unwrap_or_default(), _ => Vec::new(), }) .unwrap_or_default(); @@ -573,7 +576,7 @@ pub struct SmartNodeStats { pub observation_entries: usize, } -/// Sensor adapter for converting sensor readings to HOPE observations +/// Sensor adapter for converting sensor readings to Kaneru observations pub struct SensorAdapter { name: String, scale: f64, @@ -622,7 +625,7 @@ pub struct IoTPolicyBuilder; impl IoTPolicyBuilder { /// Create a threshold alert policy pub fn threshold_alert(sensor_name: &str, threshold: f64, alert_message: &str) -> Rule { - use hope_agents::policy::Condition; + use kaneru::policy::Condition; Rule::new( &format!("{}_threshold", sensor_name), @@ -639,7 +642,7 @@ impl IoTPolicyBuilder { action_low: Action, action_high: Action, ) -> Vec { - use hope_agents::policy::Condition; + use kaneru::policy::Condition; vec![ Rule::new( @@ -662,7 +665,7 @@ impl IoTPolicyBuilder { on_action: Action, off_action: Action, ) -> Vec { - use hope_agents::policy::Condition; + use kaneru::policy::Condition; vec![ Rule::new( @@ -740,7 +743,7 @@ mod tests { #[test] fn test_smart_node_with_rule() { - use hope_agents::policy::Condition; + use kaneru::policy::Condition; let config = test_config(); let mut node = SmartNode::new(config).unwrap(); diff --git a/crates/aingle_minimal/src/storage.rs b/crates/aingle_minimal/src/storage.rs index 25551daa..547cba18 100644 --- a/crates/aingle_minimal/src/storage.rs +++ b/crates/aingle_minimal/src/storage.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! SQLite storage backend for IoT nodes //! //! Uses SQLite with aggressive pruning for constrained storage. diff --git a/crates/aingle_minimal/src/storage_factory.rs b/crates/aingle_minimal/src/storage_factory.rs index a5ab2964..f02ef812 100644 --- a/crates/aingle_minimal/src/storage_factory.rs +++ b/crates/aingle_minimal/src/storage_factory.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Storage factory for dynamic backend selection //! //! Creates the appropriate storage backend based on configuration. diff --git a/crates/aingle_minimal/src/storage_trait.rs b/crates/aingle_minimal/src/storage_trait.rs index 1ed1075c..95b1aacc 100644 --- a/crates/aingle_minimal/src/storage_trait.rs +++ b/crates/aingle_minimal/src/storage_trait.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Storage backend trait definition //! //! This module defines the common interface for all storage backends. diff --git a/crates/aingle_minimal/src/sync.rs b/crates/aingle_minimal/src/sync.rs index 1ebc0248..9ac59356 100644 --- a/crates/aingle_minimal/src/sync.rs +++ b/crates/aingle_minimal/src/sync.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Node-to-node synchronization protocol //! //! Implements efficient record synchronization using bloom filter-based @@ -421,7 +424,7 @@ mod tests { #[test] fn test_sync_manager_get_peer_state() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let state = manager.get_peer_state(&addr); assert_eq!(state.successful_syncs, 0); @@ -435,7 +438,7 @@ mod tests { #[test] fn test_sync_stats() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); manager.add_local_hash(Hash::from_bytes(&[1; 32])); manager.get_peer_state(&addr).record_success(); @@ -500,7 +503,7 @@ mod tests { #[test] fn test_sync_manager_peers_needing_sync_with_interval() { let mut manager = SyncManager::new(Duration::from_millis(10)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); manager.get_peer_state(&addr); // Immediately after creation, should not need sync (just saw it) @@ -514,7 +517,7 @@ mod tests { #[test] fn test_sync_manager_cleanup_inactive() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1); @@ -532,7 +535,7 @@ mod tests { #[test] fn test_sync_manager_cleanup_inactive_keeps_recent() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr: SocketAddr = "127.0.0.1:19080".parse().unwrap(); manager.get_peer_state(&addr).record_success(); @@ -587,20 +590,20 @@ mod tests { #[test] fn test_sync_result_debug() { let result = SyncResult { - peer: "127.0.0.1:8080".parse().unwrap(), + peer: "127.0.0.1:19080".parse().unwrap(), sent_filter: true, records_sent: 5, records_received: 3, }; let debug_str = format!("{:?}", result); assert!(debug_str.contains("SyncResult")); - assert!(debug_str.contains("127.0.0.1:8080")); + assert!(debug_str.contains("127.0.0.1:19080")); } #[test] fn test_sync_manager_stats_with_failures() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1).record_success(); @@ -768,7 +771,7 @@ mod tests { let mut manager = SyncManager::new(Duration::from_secs(60)); assert_eq!(manager.peer_states.len(), 0); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1); @@ -915,7 +918,7 @@ mod tests { fn test_sync_manager_cleanup_all_inactive() { let mut manager = SyncManager::new(Duration::from_secs(60)); - let addr1: SocketAddr = "127.0.0.1:8080".parse().unwrap(); + let addr1: SocketAddr = "127.0.0.1:19080".parse().unwrap(); let addr2: SocketAddr = "127.0.0.1:8081".parse().unwrap(); manager.get_peer_state(&addr1); diff --git a/crates/aingle_minimal/src/types.rs b/crates/aingle_minimal/src/types.rs index 43c297ae..5de73a15 100644 --- a/crates/aingle_minimal/src/types.rs +++ b/crates/aingle_minimal/src/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Core data types for the minimal AIngle node. //! //! This module defines the fundamental types used throughout the aingle_minimal crate, diff --git a/crates/aingle_minimal/src/wallet.rs b/crates/aingle_minimal/src/wallet.rs index 42fe8ad7..6303af01 100644 --- a/crates/aingle_minimal/src/wallet.rs +++ b/crates/aingle_minimal/src/wallet.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Hardware Wallet Integration for Secure Key Management //! //! This module provides integration with hardware wallets (Ledger, Trezor) @@ -662,7 +665,12 @@ impl ApduCommand { } /// Serialize to bytes for transmission - pub fn serialize(&self) -> Vec { + pub fn serialize(&self) -> std::result::Result, crate::error::Error> { + if self.data.len() > 255 { + return Err(crate::error::Error::network( + format!("APDU data too large: {} bytes (max 255)", self.data.len()), + )); + } let mut bytes = Vec::with_capacity(5 + self.data.len()); bytes.push(self.cla); bytes.push(self.ins); @@ -672,7 +680,7 @@ impl ApduCommand { bytes.push(self.data.len() as u8); bytes.extend_from_slice(&self.data); } - bytes + Ok(bytes) } } @@ -832,7 +840,7 @@ mod tests { #[test] fn test_apdu_command_serialize() { let cmd = ApduCommand::new(0xE0, 0x01, 0x00, 0x00).with_data(vec![0x01, 0x02, 0x03]); - let bytes = cmd.serialize(); + let bytes = cmd.serialize().unwrap(); assert_eq!(bytes, vec![0xE0, 0x01, 0x00, 0x00, 0x03, 0x01, 0x02, 0x03]); } diff --git a/crates/aingle_minimal/src/webrtc.rs b/crates/aingle_minimal/src/webrtc.rs index 0770bb44..33d646d4 100644 --- a/crates/aingle_minimal/src/webrtc.rs +++ b/crates/aingle_minimal/src/webrtc.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! WebRTC Transport for Browser Support //! //! This module enables AIngle nodes to run in web browsers and communicate @@ -104,7 +107,7 @@ impl Default for WebRtcConfig { turn_server: None, turn_username: None, turn_credential: None, - signaling_port: 8080, + signaling_port: 19080, ice_timeout: Duration::from_secs(30), channel_label: "aingle".to_string(), } @@ -638,7 +641,7 @@ pub struct SignalingConfig { impl Default for SignalingConfig { fn default() -> Self { Self { - bind_addr: "0.0.0.0:8080".to_string(), + bind_addr: "0.0.0.0:19080".to_string(), max_connections: 100, heartbeat_interval: Duration::from_secs(30), connection_timeout: Duration::from_secs(60), @@ -972,7 +975,7 @@ impl SignalingServer { /// ```rust,ignore /// use aingle_minimal::SignalingClient; /// -/// let mut client = SignalingClient::new("ws://localhost:8080", "my-peer-id"); +/// let mut client = SignalingClient::new("ws://localhost:19080", "my-peer-id"); /// /// smol::block_on(async { /// client.connect().await.unwrap(); @@ -1128,7 +1131,7 @@ mod tests { let config = WebRtcConfig::default(); assert!(config.stun_server.contains("stun")); assert!(config.turn_server.is_none()); - assert_eq!(config.signaling_port, 8080); + assert_eq!(config.signaling_port, 19080); } #[test] @@ -1187,7 +1190,7 @@ mod tests { #[test] fn test_signaling_config_default() { let config = SignalingConfig::default(); - assert_eq!(config.bind_addr, "0.0.0.0:8080"); + assert_eq!(config.bind_addr, "0.0.0.0:19080"); assert_eq!(config.max_connections, 100); assert_eq!(config.heartbeat_interval, Duration::from_secs(30)); } diff --git a/crates/aingle_minimal/tests/e2e_multi_node_tests.rs b/crates/aingle_minimal/tests/e2e_multi_node_tests.rs index 2860f6d6..ef5af902 100644 --- a/crates/aingle_minimal/tests/e2e_multi_node_tests.rs +++ b/crates/aingle_minimal/tests/e2e_multi_node_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! End-to-End Multi-Node Integration Tests //! //! These tests verify that multiple AIngle nodes can: diff --git a/crates/aingle_minimal/tests/iot_integration_tests.rs b/crates/aingle_minimal/tests/iot_integration_tests.rs index c1513dac..9bd70f68 100644 --- a/crates/aingle_minimal/tests/iot_integration_tests.rs +++ b/crates/aingle_minimal/tests/iot_integration_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for IoT features //! //! Tests complete workflows combining multiple IoT components: diff --git a/crates/aingle_minimal/tests/p2p_network_tests.rs b/crates/aingle_minimal/tests/p2p_network_tests.rs index f8c96d7b..528bb5f8 100644 --- a/crates/aingle_minimal/tests/p2p_network_tests.rs +++ b/crates/aingle_minimal/tests/p2p_network_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! P2P Network Integration Tests //! //! Tests for peer-to-peer networking including: @@ -313,7 +316,7 @@ fn test_production_p2p_config() { // Should use QUIC transport match config.transport { aingle_minimal::config::TransportConfig::Quic { port, .. } => { - assert_eq!(port, 8443); + assert_eq!(port, 19081); } _ => panic!("Expected QUIC transport for production mode"), } diff --git a/crates/aingle_minimal/tests/smart_node_integration_tests.rs b/crates/aingle_minimal/tests/smart_node_integration_tests.rs index 5f42ed0b..bc152999 100644 --- a/crates/aingle_minimal/tests/smart_node_integration_tests.rs +++ b/crates/aingle_minimal/tests/smart_node_integration_tests.rs @@ -1,15 +1,18 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for SmartNode pipeline //! //! Tests the complete flow: Sensor → Observation → Agent → Action → DAG -//! These tests verify the integration between MinimalNode and HOPE Agents. +//! These tests verify the integration between MinimalNode and Kaneru. //! //! Requires the `smart_agents` feature to be enabled. #![cfg(feature = "smart_agents")] use aingle_minimal::*; -use hope_agents::policy::Condition; -use hope_agents::{ +use kaneru::policy::Condition; +use kaneru::{ Action, ActionType, AgentConfig, Goal, Observation, ObservationType, Policy, Rule, }; @@ -71,7 +74,7 @@ fn test_smart_node_creation_low_power() { #[test] fn test_smart_node_with_custom_agent() { - use hope_agents::SimpleAgent; + use kaneru::SimpleAgent; let mut agent = SimpleAgent::new("custom_agent"); agent.add_rule(Rule::new( @@ -502,7 +505,7 @@ fn test_add_goal_to_smart_node() { #[test] fn test_smart_node_pause_resume() { - use hope_agents::AgentState; + use kaneru::AgentState; let config = test_smart_config(); let mut node = SmartNode::new(config).unwrap(); diff --git a/crates/aingle_minimal/tests/transport_integration_tests.rs b/crates/aingle_minimal/tests/transport_integration_tests.rs index 5ae9d3dc..d406b5ae 100644 --- a/crates/aingle_minimal/tests/transport_integration_tests.rs +++ b/crates/aingle_minimal/tests/transport_integration_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for transport features with mocks //! //! Tests WebRTC, Bluetooth LE, and Hardware Wallet integration: diff --git a/crates/aingle_p2p/Cargo.toml b/crates/aingle_p2p/Cargo.toml index 836184f8..d4475d82 100644 --- a/crates/aingle_p2p/Cargo.toml +++ b/crates/aingle_p2p/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_p2p" version = "0.0.1" description = "aingle specific wrapper around more generic p2p module" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_p2p" diff --git a/crates/aingle_p2p/src/lib.rs b/crates/aingle_p2p/src/lib.rs index b2f4c4bc..ebcd1d15 100644 --- a/crates/aingle_p2p/src/lib.rs +++ b/crates/aingle_p2p/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! aingle specific wrapper around more generic p2p module diff --git a/crates/aingle_p2p/src/spawn.rs b/crates/aingle_p2p/src/spawn.rs index 48bb7014..4c69923d 100644 --- a/crates/aingle_p2p/src/spawn.rs +++ b/crates/aingle_p2p/src/spawn.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::actor::*; use crate::event::*; diff --git a/crates/aingle_p2p/src/spawn/actor.rs b/crates/aingle_p2p/src/spawn/actor.rs index d06122b2..97e4baed 100644 --- a/crates/aingle_p2p/src/spawn/actor.rs +++ b/crates/aingle_p2p/src/spawn/actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::too_many_arguments)] use crate::actor::*; use crate::event::*; diff --git a/crates/aingle_p2p/src/test.rs b/crates/aingle_p2p/src/test.rs index 64c7d68e..83c295e1 100644 --- a/crates/aingle_p2p/src/test.rs +++ b/crates/aingle_p2p/src/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::actor::*; use crate::AIngleP2pCell; use crate::*; diff --git a/crates/aingle_p2p/src/types.rs b/crates/aingle_p2p/src/types.rs index 38c28c33..8d612c73 100644 --- a/crates/aingle_p2p/src/types.rs +++ b/crates/aingle_p2p/src/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// Error type for AIngle P2p. #[derive(Debug, thiserror::Error)] #[non_exhaustive] diff --git a/crates/aingle_p2p/src/types/actor.rs b/crates/aingle_p2p/src/types/actor.rs index 62d948aa..aa3a875c 100644 --- a/crates/aingle_p2p/src/types/actor.rs +++ b/crates/aingle_p2p/src/types/actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Module containing the AIngleP2p actor definition. #![allow(clippy::too_many_arguments)] diff --git a/crates/aingle_p2p/src/types/event.rs b/crates/aingle_p2p/src/types/event.rs index 64b81aed..5aade5a2 100644 --- a/crates/aingle_p2p/src/types/event.rs +++ b/crates/aingle_p2p/src/types/event.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::too_many_arguments)] //! Module containing incoming events from the AIngleP2p actor. diff --git a/crates/aingle_p2p/src/types/wire.rs b/crates/aingle_p2p/src/types/wire.rs index d46cd37c..52466a4b 100644 --- a/crates/aingle_p2p/src/types/wire.rs +++ b/crates/aingle_p2p/src/types/wire.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use aingle_zome_types::zome::FunctionName; diff --git a/crates/aingle_raft/Cargo.toml b/crates/aingle_raft/Cargo.toml new file mode 100644 index 00000000..7a275ee3 --- /dev/null +++ b/crates/aingle_raft/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "aingle_raft" +version = "0.6.3" +description = "Raft consensus for AIngle clustering" +license = "Apache-2.0 OR LicenseRef-Commercial" +repository = "https://github.com/ApiliumCode/aingle" +homepage = "https://apilium.com" +documentation = "https://docs.rs/aingle_raft" +authors = ["Apilium Technologies "] +keywords = ["aingle", "raft", "consensus", "clustering"] +categories = ["database"] +edition = "2021" +rust-version = "1.83" + +[features] +default = [] +dag = ["aingle_graph/dag"] + +[dependencies] +openraft = { version = "0.10.0-alpha.17", features = ["serde", "type-alias"] } +aingle_wal = { version = "0.6", path = "../aingle_wal" } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } +bincode = "2" +blake3 = "1.8" +tracing = "0.1" +chrono = { version = "0.4", features = ["serde"] } +futures-util = "0.3" +anyerror = "0.1" +aingle_graph = { version = "0.6", path = "../aingle_graph", features = ["sled-backend"] } +ineru = { version = "0.6", path = "../ineru" } + +[dev-dependencies] +tempfile = "3.26" +tokio-test = "0.4" diff --git a/crates/aingle_raft/src/consistency.rs b/crates/aingle_raft/src/consistency.rs new file mode 100644 index 00000000..cee877b3 --- /dev/null +++ b/crates/aingle_raft/src/consistency.rs @@ -0,0 +1,57 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Consistency levels for read operations. + +use serde::{Deserialize, Serialize}; + +/// Configurable read consistency for cluster operations. +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)] +pub enum ConsistencyLevel { + /// Read from local state (may be stale on followers). + #[default] + Local, + /// Read requires majority agreement. + Quorum, + /// Linearizable read (goes through Raft leader). + Linearizable, +} + +impl ConsistencyLevel { + /// Parse from a header string value. + pub fn from_header(value: &str) -> Self { + match value.to_lowercase().as_str() { + "quorum" => Self::Quorum, + "linearizable" => Self::Linearizable, + _ => Self::Local, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_consistency_default() { + assert_eq!(ConsistencyLevel::default(), ConsistencyLevel::Local); + } + + #[test] + fn test_from_header() { + assert_eq!(ConsistencyLevel::from_header("local"), ConsistencyLevel::Local); + assert_eq!(ConsistencyLevel::from_header("quorum"), ConsistencyLevel::Quorum); + assert_eq!(ConsistencyLevel::from_header("linearizable"), ConsistencyLevel::Linearizable); + assert_eq!(ConsistencyLevel::from_header("LOCAL"), ConsistencyLevel::Local); + assert_eq!(ConsistencyLevel::from_header("QUORUM"), ConsistencyLevel::Quorum); + assert_eq!(ConsistencyLevel::from_header("unknown"), ConsistencyLevel::Local); + } + + #[test] + fn test_serialization() { + let level = ConsistencyLevel::Quorum; + let json = serde_json::to_string(&level).unwrap(); + let back: ConsistencyLevel = serde_json::from_str(&json).unwrap(); + assert_eq!(back, ConsistencyLevel::Quorum); + } +} diff --git a/crates/aingle_raft/src/lib.rs b/crates/aingle_raft/src/lib.rs new file mode 100644 index 00000000..04c867c8 --- /dev/null +++ b/crates/aingle_raft/src/lib.rs @@ -0,0 +1,17 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Raft consensus for AIngle clustering. +//! +//! Uses openraft for leader election and log replication, +//! backed by the AIngle WAL for durable log storage. + +pub mod types; +pub mod log_store; +pub mod state_machine; +pub mod snapshot_builder; +pub mod network; +pub mod consistency; + +pub use types::{CortexTypeConfig, CortexRequest, CortexResponse, CortexNode, NodeId}; +pub use consistency::ConsistencyLevel; diff --git a/crates/aingle_raft/src/log_store.rs b/crates/aingle_raft/src/log_store.rs new file mode 100644 index 00000000..82bbc8a0 --- /dev/null +++ b/crates/aingle_raft/src/log_store.rs @@ -0,0 +1,667 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Raft log storage backed by WAL segments. +//! +//! Implements `RaftLogReader` and `RaftLogStorage` from openraft, +//! persisting entries as `WalEntryKind::RaftEntry` variants and +//! vote/committed state as JSON files alongside the WAL directory. + +use crate::types::CortexTypeConfig; +use aingle_wal::{WalEntryKind, WalWriter}; +use openraft::alias::{EntryOf, LogIdOf, VoteOf}; +use openraft::storage::{IOFlushed, LogState, RaftLogStorage}; +use openraft::RaftLogReader; +use std::collections::BTreeMap; +use std::fmt::Debug; +use std::io::{self, Write}; +use std::ops::RangeBounds; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use tokio::sync::RwLock; + +type C = CortexTypeConfig; +type Vote = VoteOf; +type LogId = LogIdOf; +type Entry = EntryOf; + +/// Durable Raft log store backed by the AIngle WAL. +/// +/// In-memory BTreeMap serves reads; WAL provides persistence. +/// Vote and committed state are persisted as JSON files. +pub struct CortexLogStore { + vote: RwLock>, + committed: RwLock>, + log: RwLock>, + purged_log_id: RwLock>, + /// Truncation boundary — entries with index > this are invalid. + truncated_after: RwLock>, + /// WAL writer for durable persistence. + wal: Arc, + /// Directory for vote/committed JSON files. + wal_dir: PathBuf, +} + +impl CortexLogStore { + /// Open or create a log store backed by the WAL at `wal_dir`. + /// + /// On recovery, reads WAL segments, filters `RaftEntry` variants, + /// rebuilds the in-memory BTreeMap, then applies persisted + /// truncation/purge boundaries to discard stale entries. + pub fn open(wal_dir: &Path) -> io::Result { + let wal = Arc::new(WalWriter::open(wal_dir)?); + + // Recover vote from disk + let vote = Self::load_vote(wal_dir)?; + + // Recover committed from disk + let committed = Self::load_committed(wal_dir)?; + + // Recover purged boundary from disk + let purged_log_id = Self::load_purged(wal_dir)?; + + // Recover truncation boundary from disk + let truncated_after = Self::load_truncated_after(wal_dir)?; + + // Rebuild log from WAL + let reader = aingle_wal::WalReader::open(wal_dir)?; + let wal_entries = reader.read_from(0)?; + let mut log = BTreeMap::new(); + + for wal_entry in &wal_entries { + if let WalEntryKind::RaftEntry { index, term: _, data } = &wal_entry.kind { + match serde_json::from_slice::(data) { + Ok(entry) => { + log.insert(*index, entry); + } + Err(e) => { + tracing::warn!( + index = index, + "Failed to deserialize RaftEntry from WAL: {}", + e + ); + } + } + } + } + + // Apply persisted boundaries: remove entries outside the valid range + if let Some(ref purged) = purged_log_id { + log.retain(|idx, _| *idx > purged.index); + } + if let Some(ref trunc) = truncated_after { + log.retain(|idx, _| *idx <= trunc.index); + } + + tracing::info!( + entries = log.len(), + vote = ?vote, + committed = ?committed, + purged = ?purged_log_id, + truncated_after = ?truncated_after, + "CortexLogStore recovered from WAL" + ); + + Ok(Self { + vote: RwLock::new(vote), + committed: RwLock::new(committed), + log: RwLock::new(log), + purged_log_id: RwLock::new(purged_log_id), + truncated_after: RwLock::new(truncated_after), + wal, + wal_dir: wal_dir.to_path_buf(), + }) + } + + /// Get the WAL writer reference. + pub fn wal(&self) -> &Arc { + &self.wal + } + + // --- Atomic file write --- + + /// Atomically write data to a file: write to .tmp, fsync, rename. + fn atomic_write(target: &Path, data: &[u8]) -> io::Result<()> { + let tmp = target.with_extension("tmp"); + { + let mut f = std::fs::File::create(&tmp)?; + f.write_all(data)?; + f.sync_all()?; + } + std::fs::rename(&tmp, target)?; + // fsync the parent directory to ensure the rename is durable + if let Some(parent) = target.parent() { + if let Ok(dir) = std::fs::File::open(parent) { + let _ = dir.sync_all(); + } + } + Ok(()) + } + + // --- Persistence helpers --- + + fn vote_path(dir: &Path) -> PathBuf { + dir.join("raft_vote.json") + } + + fn committed_path(dir: &Path) -> PathBuf { + dir.join("raft_committed.json") + } + + fn purged_path(dir: &Path) -> PathBuf { + dir.join("raft_purged.json") + } + + fn truncated_after_path(dir: &Path) -> PathBuf { + dir.join("raft_truncated_after.json") + } + + fn persist_vote(dir: &Path, vote: &Vote) -> io::Result<()> { + let data = serde_json::to_vec_pretty(vote) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Self::atomic_write(&Self::vote_path(dir), &data) + } + + fn load_vote(dir: &Path) -> io::Result> { + let path = Self::vote_path(dir); + if !path.exists() { + return Ok(None); + } + let data = std::fs::read(&path)?; + let vote: Vote = serde_json::from_slice(&data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(Some(vote)) + } + + fn persist_committed(dir: &Path, committed: &Option) -> io::Result<()> { + let data = serde_json::to_vec_pretty(committed) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Self::atomic_write(&Self::committed_path(dir), &data) + } + + fn load_committed(dir: &Path) -> io::Result> { + let path = Self::committed_path(dir); + if !path.exists() { + return Ok(None); + } + let data = std::fs::read(&path)?; + let committed: Option = serde_json::from_slice(&data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(committed) + } + + fn persist_purged(dir: &Path, purged: &LogId) -> io::Result<()> { + let data = serde_json::to_vec_pretty(purged) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Self::atomic_write(&Self::purged_path(dir), &data) + } + + fn load_purged(dir: &Path) -> io::Result> { + let path = Self::purged_path(dir); + if !path.exists() { + return Ok(None); + } + let data = std::fs::read(&path)?; + let purged: LogId = serde_json::from_slice(&data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(Some(purged)) + } + + fn persist_truncated_after(dir: &Path, lid: &Option) -> io::Result<()> { + let data = serde_json::to_vec_pretty(lid) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Self::atomic_write(&Self::truncated_after_path(dir), &data) + } + + fn load_truncated_after(dir: &Path) -> io::Result> { + let path = Self::truncated_after_path(dir); + if !path.exists() { + return Ok(None); + } + let data = std::fs::read(&path)?; + let lid: Option = serde_json::from_slice(&data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok(lid) + } + + // --- Internal append (for IOFlushed callback pattern) --- + + async fn append_inner(&self, entries: I) -> Result<(), io::Error> + where + I: IntoIterator + Send, + I::IntoIter: Send, + { + // Collect all entries and serialize them first, then write ALL to + // WAL before touching the BTreeMap. This prevents a partial batch + // leaving the in-memory map inconsistent with WAL on failure (#11). + let batch: Vec<(u64, u64, Vec, Entry)> = entries + .into_iter() + .map(|entry| { + let index = entry.log_id.index; + let term = entry.log_id.leader_id.term; + let data = serde_json::to_vec(&entry) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + Ok((index, term, data, entry)) + }) + .collect::, io::Error>>()?; + + // Write ALL to WAL first + for (index, term, ref data, _) in &batch { + self.wal + .append(WalEntryKind::RaftEntry { index: *index, term: *term, data: data.clone() }) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + } + + // Only update BTreeMap after all WAL writes succeed + let mut log = self.log.write().await; + for (index, _, _, entry) in batch { + log.insert(index, entry); + } + + Ok(()) + } + + // --- Legacy convenience methods --- + + pub async fn log_length(&self) -> u64 { + let log = self.log.read().await; + log.len() as u64 + } + + pub async fn last_log_id(&self) -> Option { + let log = self.log.read().await; + log.values().last().map(|e| e.log_id.clone()) + } +} + +// ============================================================================ +// RaftLogReader implementation +// ============================================================================ + +impl RaftLogReader for Arc { + async fn try_get_log_entries + Clone + Debug + Send>( + &mut self, + range: RB, + ) -> Result, io::Error> { + let log = self.log.read().await; + let entries: Vec = log.range(range).map(|(_, e)| e.clone()).collect(); + Ok(entries) + } + + async fn read_vote(&mut self) -> Result, io::Error> { + let v = self.vote.read().await; + Ok(v.clone()) + } +} + +// ============================================================================ +// RaftLogStorage implementation +// ============================================================================ + +impl RaftLogStorage for Arc { + type LogReader = Arc; + + async fn get_log_state(&mut self) -> Result, io::Error> { + // Hold both locks simultaneously to avoid TOCTOU race + let log = self.log.read().await; + let purged = self.purged_log_id.read().await; + + let last_log_id = log + .values() + .last() + .map(|e| e.log_id.clone()) + .or_else(|| purged.clone()); + + Ok(LogState { + last_purged_log_id: purged.clone(), + last_log_id, + }) + } + + async fn get_log_reader(&mut self) -> Self::LogReader { + Arc::clone(self) + } + + async fn save_vote(&mut self, vote: &Vote) -> Result<(), io::Error> { + CortexLogStore::persist_vote(&self.wal_dir, vote)?; + let mut v = self.vote.write().await; + *v = Some(vote.clone()); + Ok(()) + } + + async fn save_committed(&mut self, committed: Option) -> Result<(), io::Error> { + CortexLogStore::persist_committed(&self.wal_dir, &committed)?; + let mut c = self.committed.write().await; + *c = committed; + Ok(()) + } + + async fn read_committed(&mut self) -> Result, io::Error> { + let c = self.committed.read().await; + Ok(c.clone()) + } + + async fn append(&mut self, entries: I, callback: IOFlushed) -> Result<(), io::Error> + where + I: IntoIterator + Send, + I::IntoIter: Send, + { + // Always invoke the callback, even on error, to prevent openraft hangs. + let result = self.append_inner(entries).await; + callback.io_completed(result.as_ref().map(|_| ()).map_err(|e| { + io::Error::new(e.kind(), e.to_string()) + })); + result + } + + async fn truncate_after(&mut self, last_log_id: Option) -> Result<(), io::Error> { + let mut log = self.log.write().await; + + match last_log_id { + Some(ref lid) => { + let keys_to_remove: Vec = + log.range((lid.index + 1)..).map(|(k, _)| *k).collect(); + for k in keys_to_remove { + log.remove(&k); + } + } + None => { + log.clear(); + } + } + + // Persist truncation boundary so recovery filters out stale entries + let mut trunc = self.truncated_after.write().await; + *trunc = last_log_id.clone(); + CortexLogStore::persist_truncated_after(&self.wal_dir, &last_log_id)?; + + Ok(()) + } + + async fn purge(&mut self, log_id: LogId) -> Result<(), io::Error> { + let mut log = self.log.write().await; + + let keys_to_remove: Vec = log + .range(..=log_id.index) + .map(|(k, _)| *k) + .collect(); + for k in keys_to_remove { + log.remove(&k); + } + + // Persist purge boundary + let mut purged = self.purged_log_id.write().await; + *purged = Some(log_id.clone()); + CortexLogStore::persist_purged(&self.wal_dir, &log_id)?; + + // Clean up old WAL segments that are entirely below the purge point + let _ = self.wal.truncate_before(log_id.index); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use openraft::entry::RaftEntry; + use openraft::vote::leader_id_adv::CommittedLeaderId; + use openraft::vote::RaftLeaderId; + + fn make_entry(index: u64, term: u64) -> Entry { + Entry::new_blank(openraft::LogId::new( + CommittedLeaderId::new(term, 0), + index, + )) + } + + #[tokio::test] + async fn test_log_store_open_empty() { + let dir = tempfile::tempdir().unwrap(); + let store = CortexLogStore::open(dir.path()).unwrap(); + let store = Arc::new(store); + + let mut reader = store.clone(); + assert!(reader.read_vote().await.unwrap().is_none()); + assert_eq!(store.log_length().await, 0); + } + + #[tokio::test] + async fn test_append_and_read() { + let dir = tempfile::tempdir().unwrap(); + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![make_entry(1, 1), make_entry(2, 1), make_entry(3, 1)]; + + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + + let mut reader = store.clone(); + let result = reader.try_get_log_entries(1..4).await.unwrap(); + assert_eq!(result.len(), 3); + assert_eq!(result[0].log_id.index, 1); + assert_eq!(result[2].log_id.index, 3); + } + + #[tokio::test] + async fn test_vote_persistence() { + let dir = tempfile::tempdir().unwrap(); + + // Write vote + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + let vote = openraft::Vote::new(1, 0); + store_mut.save_vote(&vote).await.unwrap(); + } + + // Reopen and verify + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut reader = store.clone(); + let vote = reader.read_vote().await.unwrap(); + assert!(vote.is_some()); + assert_eq!(vote.unwrap().leader_id().term, 1); + } + } + + #[tokio::test] + async fn test_truncate_after() { + let dir = tempfile::tempdir().unwrap(); + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![ + make_entry(1, 1), + make_entry(2, 1), + make_entry(3, 1), + make_entry(4, 1), + ]; + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + + // Truncate after index 2 + let lid = openraft::LogId::new(CommittedLeaderId::new(1, 0), 2); + store_mut.truncate_after(Some(lid)).await.unwrap(); + + let mut reader = store.clone(); + let result = reader.try_get_log_entries(1..5).await.unwrap(); + assert_eq!(result.len(), 2); + } + + #[tokio::test] + async fn test_truncate_survives_restart() { + let dir = tempfile::tempdir().unwrap(); + + // Write entries and truncate + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![ + make_entry(1, 1), + make_entry(2, 1), + make_entry(3, 1), + make_entry(4, 1), + ]; + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + + let lid = openraft::LogId::new(CommittedLeaderId::new(1, 0), 2); + store_mut.truncate_after(Some(lid)).await.unwrap(); + } + + // Reopen — truncated entries must NOT reappear + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut reader = store.clone(); + let result = reader.try_get_log_entries(1..5).await.unwrap(); + assert_eq!(result.len(), 2, "truncated entries must not survive restart"); + } + } + + #[tokio::test] + async fn test_purge() { + let dir = tempfile::tempdir().unwrap(); + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![ + make_entry(1, 1), + make_entry(2, 1), + make_entry(3, 1), + ]; + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + + let purge_id = openraft::LogId::new(CommittedLeaderId::new(1, 0), 2); + store_mut.purge(purge_id).await.unwrap(); + + let mut reader = store.clone(); + let result = reader.try_get_log_entries(1..4).await.unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].log_id.index, 3); + } + + #[tokio::test] + async fn test_purge_survives_restart() { + let dir = tempfile::tempdir().unwrap(); + + // Write entries and purge + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![ + make_entry(1, 1), + make_entry(2, 1), + make_entry(3, 1), + ]; + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + + let purge_id = openraft::LogId::new(CommittedLeaderId::new(1, 0), 2); + store_mut.purge(purge_id).await.unwrap(); + } + + // Reopen — purged entries must NOT reappear + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut reader = store.clone(); + let result = reader.try_get_log_entries(1..4).await.unwrap(); + assert_eq!(result.len(), 1, "purged entries must not survive restart"); + assert_eq!(result[0].log_id.index, 3); + + // purged_log_id should also be restored + let mut store_mut = store.clone(); + let state = store_mut.get_log_state().await.unwrap(); + assert!(state.last_purged_log_id.is_some()); + assert_eq!(state.last_purged_log_id.unwrap().index, 2); + } + } + + #[tokio::test] + async fn test_reopen_recovery() { + let dir = tempfile::tempdir().unwrap(); + + // Write entries + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![make_entry(1, 1), make_entry(2, 1)]; + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + } + + // Reopen and verify entries are recovered + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut reader = store.clone(); + let result = reader.try_get_log_entries(1..3).await.unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].log_id.index, 1); + } + } + + #[tokio::test] + async fn test_committed_persistence() { + let dir = tempfile::tempdir().unwrap(); + + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + let lid = openraft::LogId::new(CommittedLeaderId::new(1, 0), 5); + store_mut.save_committed(Some(lid)).await.unwrap(); + } + + { + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + let committed = store_mut.read_committed().await.unwrap(); + assert!(committed.is_some()); + assert_eq!(committed.unwrap().index, 5); + } + } + + #[tokio::test] + async fn test_get_log_state() { + let dir = tempfile::tempdir().unwrap(); + let store = Arc::new(CortexLogStore::open(dir.path()).unwrap()); + let mut store_mut = store.clone(); + + let entries = vec![make_entry(1, 1), make_entry(2, 1)]; + store_mut + .append(entries, IOFlushed::noop()) + .await + .unwrap(); + + let state = store_mut.get_log_state().await.unwrap(); + assert!(state.last_purged_log_id.is_none()); + assert_eq!(state.last_log_id.unwrap().index, 2); + } + + #[tokio::test] + async fn test_atomic_write_persists() { + let dir = tempfile::tempdir().unwrap(); + let target = dir.path().join("test_atomic.json"); + CortexLogStore::atomic_write(&target, b"hello world").unwrap(); + let data = std::fs::read(&target).unwrap(); + assert_eq!(data, b"hello world"); + // tmp file should not exist + assert!(!target.with_extension("tmp").exists()); + } +} diff --git a/crates/aingle_raft/src/network.rs b/crates/aingle_raft/src/network.rs new file mode 100644 index 00000000..4fcb62a4 --- /dev/null +++ b/crates/aingle_raft/src/network.rs @@ -0,0 +1,529 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Raft network layer — bridges openraft RPC to QUIC P2P transport. +//! +//! Implements `RaftNetworkFactory` and `RaftNetworkV2` to route Raft +//! protocol messages through the existing P2P transport. + +use crate::types::{CortexNode, CortexTypeConfig, NodeId}; +use anyerror::AnyError; +use openraft::error::{RPCError, ReplicationClosed, StreamingError, Unreachable}; +use openraft::network::{RPCOption, RaftNetworkFactory}; +use openraft::raft::{ + AppendEntriesRequest, AppendEntriesResponse, SnapshotResponse, VoteRequest, VoteResponse, +}; +use openraft::type_config::alias::{SnapshotOf, VoteOf}; +use openraft::RaftNetworkV2; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::future::Future; +use std::net::SocketAddr; +use std::sync::Arc; +use tokio::sync::RwLock; + +type C = CortexTypeConfig; + +// ============================================================================ +// Raft P2P message types +// ============================================================================ + +/// Raft-related P2P message types. +/// +/// These are serialized and sent over QUIC bidirectional streams. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RaftMessage { + /// Raft AppendEntries RPC (serialized openraft request). + AppendEntries { payload: Vec }, + /// Raft AppendEntries response. + AppendEntriesResponse { payload: Vec }, + /// Raft Vote RPC. + Vote { payload: Vec }, + /// Raft Vote response. + VoteResponse { payload: Vec }, + /// Raft snapshot data (monolithic, for small snapshots). + InstallSnapshot { payload: Vec }, + /// Raft snapshot response. + InstallSnapshotResponse { payload: Vec }, + /// Snapshot chunk for streaming large snapshots. + SnapshotChunk { + snapshot_id: String, + offset: u64, + total_size: u64, + is_final: bool, + data: Vec, + }, + /// Acknowledgement for a snapshot chunk. + SnapshotChunkAck { + snapshot_id: String, + next_offset: u64, + }, + /// Cluster join request. + ClusterJoin { + node_id: u64, + rest_addr: String, + p2p_addr: String, + }, + /// Cluster join acknowledgement. + ClusterJoinAck { + accepted: bool, + leader_id: Option, + leader_addr: Option, + }, +} + +// ============================================================================ +// Node resolver +// ============================================================================ + +/// Node address resolver for the Raft network. +pub struct NodeResolver { + node_map: Arc>>, +} + +impl NodeResolver { + /// Create a new resolver with an initial set of nodes. + pub fn new() -> Self { + Self { + node_map: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// Register a node. + pub async fn register(&self, node_id: NodeId, node: CortexNode) { + let mut map = self.node_map.write().await; + map.insert(node_id, node); + } + + /// Remove a node. + pub async fn unregister(&self, node_id: &NodeId) { + let mut map = self.node_map.write().await; + map.remove(node_id); + } + + /// Resolve a node ID to its address info. + pub async fn resolve(&self, node_id: &NodeId) -> Option { + let map = self.node_map.read().await; + map.get(node_id).cloned() + } + + /// Get all known nodes. + pub async fn all_nodes(&self) -> HashMap { + self.node_map.read().await.clone() + } + + /// Number of known nodes. + pub async fn node_count(&self) -> usize { + self.node_map.read().await.len() + } +} + +impl Default for NodeResolver { + fn default() -> Self { + Self::new() + } +} + +// ============================================================================ +// RPC sender abstraction +// ============================================================================ + +/// Trait for sending Raft RPC messages over the network. +/// +/// Implemented by the P2P transport to allow the Raft network layer +/// to send messages without depending on QUIC directly. +pub trait RaftRpcSender: Send + Sync + 'static { + fn send_rpc( + &self, + addr: SocketAddr, + msg: RaftMessage, + ) -> std::pin::Pin> + Send + '_>>; +} + +// ============================================================================ +// Network factory +// ============================================================================ + +/// Factory that creates per-target network connections for Raft RPC. +pub struct CortexNetworkFactory { + resolver: Arc, + rpc_sender: Arc, +} + +impl CortexNetworkFactory { + /// Create a new network factory. + pub fn new(resolver: Arc, rpc_sender: Arc) -> Self { + Self { + resolver, + rpc_sender, + } + } +} + +impl RaftNetworkFactory for CortexNetworkFactory { + type Network = CortexNetworkConnection; + + async fn new_client(&mut self, target: NodeId, node: &CortexNode) -> Self::Network { + // Use REST address for HTTP-based Raft RPC routing. + // Fallback is constructed infallibly (no parse) to avoid panics. + let addr: SocketAddr = node + .rest_addr + .parse() + .unwrap_or_else(|e| { + tracing::warn!( + target_node = target, + addr = %node.rest_addr, + error = %e, + "Invalid REST address for Raft peer, falling back to localhost:19090" + ); + SocketAddr::from(([127, 0, 0, 1], 19090)) + }); + + CortexNetworkConnection { + target, + target_addr: addr, + rpc_sender: Arc::clone(&self.rpc_sender), + } + } +} + +// ============================================================================ +// Network connection (per-target) +// ============================================================================ + +/// A single Raft network connection to a target node. +pub struct CortexNetworkConnection { + target: NodeId, + target_addr: SocketAddr, + rpc_sender: Arc, +} + +impl RaftNetworkV2 for CortexNetworkConnection { + async fn append_entries( + &mut self, + rpc: AppendEntriesRequest, + option: RPCOption, + ) -> Result, RPCError> { + let payload = serde_json::to_vec(&rpc) + .map_err(|e| RPCError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + + let msg = RaftMessage::AppendEntries { payload }; + + let response = tokio::time::timeout( + option.hard_ttl(), + self.rpc_sender.send_rpc(self.target_addr, msg), + ) + .await + .map_err(|_| { + RPCError::Unreachable(Unreachable::new(&AnyError::error(format!( + "AppendEntries RPC to {} timed out after {:?}", + self.target_addr, + option.hard_ttl() + )))) + })? + .map_err(|e| RPCError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + + match response { + RaftMessage::AppendEntriesResponse { payload } => { + let resp: AppendEntriesResponse = serde_json::from_slice(&payload) + .map_err(|e| RPCError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + Ok(resp) + } + _ => Err(RPCError::Unreachable(Unreachable::new(&AnyError::error( + "unexpected response type for AppendEntries", + )))), + } + } + + async fn vote( + &mut self, + rpc: VoteRequest, + option: RPCOption, + ) -> Result, RPCError> { + let payload = serde_json::to_vec(&rpc) + .map_err(|e| RPCError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + + let msg = RaftMessage::Vote { payload }; + + let response = tokio::time::timeout( + option.hard_ttl(), + self.rpc_sender.send_rpc(self.target_addr, msg), + ) + .await + .map_err(|_| { + RPCError::Unreachable(Unreachable::new(&AnyError::error(format!( + "Vote RPC to {} timed out after {:?}", + self.target_addr, + option.hard_ttl() + )))) + })? + .map_err(|e| RPCError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + + match response { + RaftMessage::VoteResponse { payload } => { + let resp: VoteResponse = serde_json::from_slice(&payload) + .map_err(|e| RPCError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + Ok(resp) + } + _ => Err(RPCError::Unreachable(Unreachable::new(&AnyError::error( + "unexpected response type for Vote", + )))), + } + } + + async fn full_snapshot( + &mut self, + vote: VoteOf, + snapshot: SnapshotOf, + _cancel: impl Future + Send + 'static, + option: RPCOption, + ) -> Result, StreamingError> { + // Serialize full snapshot + metadata + let snap_data = serde_json::json!({ + "vote": vote, + "meta": snapshot.meta, + "data": snapshot.snapshot.into_inner(), + }); + let payload = serde_json::to_vec(&snap_data).map_err(|e| { + StreamingError::Unreachable(Unreachable::new(&AnyError::error(e))) + })?; + + // Use chunked transfer for payloads > 1MB to avoid timeouts + // and reduce memory pressure on the receiver. + const CHUNK_THRESHOLD: usize = 1024 * 1024; // 1MB + + if payload.len() > CHUNK_THRESHOLD { + return self + .send_chunked_snapshot(&payload, option) + .await; + } + + // Small snapshot: send monolithic + let msg = RaftMessage::InstallSnapshot { payload }; + + let response = tokio::time::timeout( + option.hard_ttl(), + self.rpc_sender.send_rpc(self.target_addr, msg), + ) + .await + .map_err(|_| { + StreamingError::Unreachable(Unreachable::new(&AnyError::error(format!( + "Snapshot RPC to {} timed out after {:?}", + self.target_addr, + option.hard_ttl() + )))) + })? + .map_err(|e| StreamingError::Unreachable(Unreachable::new(&AnyError::error(e))))?; + + match response { + RaftMessage::InstallSnapshotResponse { payload } => { + let resp: SnapshotResponse = serde_json::from_slice(&payload).map_err(|e| { + StreamingError::Unreachable(Unreachable::new(&AnyError::error(e))) + })?; + Ok(resp) + } + _ => Err(StreamingError::Unreachable(Unreachable::new( + &AnyError::error("unexpected response type for InstallSnapshot"), + ))), + } + } + +} + +impl CortexNetworkConnection { + /// Send a large snapshot in chunks, waiting for ACK after each chunk. + /// + /// Each chunk is sent sequentially with an ACK-per-chunk protocol. + /// The final chunk triggers snapshot installation on the receiver, + /// which returns an `InstallSnapshotResponse`. + async fn send_chunked_snapshot( + &self, + payload: &[u8], + option: RPCOption, + ) -> Result, StreamingError> { + const CHUNK_SIZE: usize = 512 * 1024; + let total_size = payload.len() as u64; + let snapshot_id = format!( + "snap-{}-{}", + self.target, + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_millis() + ); + + let num_chunks = (payload.len() + CHUNK_SIZE - 1) / CHUNK_SIZE; + tracing::info!( + target_node = self.target, + total_bytes = total_size, + chunks = num_chunks, + "Streaming snapshot in chunks" + ); + + // Per-chunk timeout: use the caller's TTL divided by chunks (min 30s). + let per_chunk_timeout = std::cmp::max( + option.hard_ttl() / (num_chunks as u32 + 1), + std::time::Duration::from_secs(30), + ); + + let mut offset = 0u64; + while (offset as usize) < payload.len() { + let end = std::cmp::min(offset as usize + CHUNK_SIZE, payload.len()); + let chunk_data = payload[offset as usize..end].to_vec(); + let is_final = end == payload.len(); + + let msg = RaftMessage::SnapshotChunk { + snapshot_id: snapshot_id.clone(), + offset, + total_size, + is_final, + data: chunk_data, + }; + + let response = tokio::time::timeout( + per_chunk_timeout, + self.rpc_sender.send_rpc(self.target_addr, msg), + ) + .await + .map_err(|_| { + StreamingError::Unreachable(Unreachable::new(&AnyError::error(format!( + "Snapshot chunk at offset {offset} timed out after {per_chunk_timeout:?}" + )))) + })? + .map_err(|e| { + StreamingError::Unreachable(Unreachable::new(&AnyError::error(e))) + })?; + + match response { + // Final chunk returns the install response + RaftMessage::InstallSnapshotResponse { payload } => { + let resp: SnapshotResponse = + serde_json::from_slice(&payload).map_err(|e| { + StreamingError::Unreachable(Unreachable::new(&AnyError::error(e))) + })?; + return Ok(resp); + } + // Intermediate ACK — advance offset + RaftMessage::SnapshotChunkAck { next_offset, .. } if !is_final => { + offset = next_offset; + } + // Got ACK on what should have been the final chunk — receiver + // didn't install yet (shouldn't happen, but handle gracefully) + RaftMessage::SnapshotChunkAck { .. } => { + return Err(StreamingError::Unreachable(Unreachable::new( + &AnyError::error( + "received SnapshotChunkAck for final chunk instead of InstallSnapshotResponse" + ), + ))); + } + other => { + return Err(StreamingError::Unreachable(Unreachable::new( + &AnyError::error(format!( + "unexpected response for snapshot chunk: {:?}", + std::mem::discriminant(&other) + )), + ))); + } + } + } + + Err(StreamingError::Unreachable(Unreachable::new( + &AnyError::error("snapshot transfer ended without a final response"), + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_raft_message_serialization() { + let msg = RaftMessage::AppendEntries { + payload: vec![1, 2, 3], + }; + let json = serde_json::to_string(&msg).unwrap(); + let back: RaftMessage = serde_json::from_str(&json).unwrap(); + assert!(matches!(back, RaftMessage::AppendEntries { .. })); + } + + #[test] + fn test_cluster_join_roundtrip() { + let msg = RaftMessage::ClusterJoin { + node_id: 42, + rest_addr: "127.0.0.1:19090".into(), + p2p_addr: "127.0.0.1:19091".into(), + }; + let json = serde_json::to_string(&msg).unwrap(); + let back: RaftMessage = serde_json::from_str(&json).unwrap(); + match back { + RaftMessage::ClusterJoin { + node_id, + rest_addr, + p2p_addr, + } => { + assert_eq!(node_id, 42); + assert_eq!(rest_addr, "127.0.0.1:19090"); + assert_eq!(p2p_addr, "127.0.0.1:19091"); + } + _ => panic!("wrong variant"), + } + } + + #[test] + fn test_cluster_join_ack() { + let msg = RaftMessage::ClusterJoinAck { + accepted: true, + leader_id: Some(1), + leader_addr: Some("127.0.0.1:19090".into()), + }; + let json = serde_json::to_string(&msg).unwrap(); + let back: RaftMessage = serde_json::from_str(&json).unwrap(); + match back { + RaftMessage::ClusterJoinAck { + accepted, + leader_id, + .. + } => { + assert!(accepted); + assert_eq!(leader_id, Some(1)); + } + _ => panic!("wrong variant"), + } + } + + #[tokio::test] + async fn test_node_resolver() { + let resolver = NodeResolver::new(); + + resolver + .register( + 1, + CortexNode { + rest_addr: "127.0.0.1:19090".into(), + p2p_addr: "127.0.0.1:19091".into(), + }, + ) + .await; + + resolver + .register( + 2, + CortexNode { + rest_addr: "127.0.0.1:19092".into(), + p2p_addr: "127.0.0.1:19092".into(), + }, + ) + .await; + + assert_eq!(resolver.node_count().await, 2); + + let node = resolver.resolve(&1).await; + assert!(node.is_some()); + assert_eq!(node.unwrap().rest_addr, "127.0.0.1:19090"); + + resolver.unregister(&1).await; + assert_eq!(resolver.node_count().await, 1); + assert!(resolver.resolve(&1).await.is_none()); + } +} diff --git a/crates/aingle_raft/src/snapshot_builder.rs b/crates/aingle_raft/src/snapshot_builder.rs new file mode 100644 index 00000000..4851fb2c --- /dev/null +++ b/crates/aingle_raft/src/snapshot_builder.rs @@ -0,0 +1,167 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Snapshot builder for the Raft state machine. + +use crate::state_machine::{ClusterSnapshot, ProofSnapshotProvider, TripleSnapshot}; +use crate::types::CortexTypeConfig; +use aingle_graph::GraphDB; +use ineru::IneruMemory; +use openraft::alias::LogIdOf; +use openraft::storage::{RaftSnapshotBuilder, Snapshot, SnapshotMeta}; +use openraft::type_config::alias::{SnapshotOf, StoredMembershipOf}; +use std::io; +use std::io::Cursor; +use std::sync::Arc; +use tokio::sync::RwLock; + +type C = CortexTypeConfig; +type LogId = LogIdOf; + +/// Builds a point-in-time snapshot of the graph + memory state. +pub struct CortexSnapshotBuilder { + pub graph: Arc>, + pub memory: Arc>, + pub last_applied: Option, + pub last_membership: StoredMembershipOf, + pub proof_provider: Option>, +} + +impl RaftSnapshotBuilder for CortexSnapshotBuilder { + async fn build_snapshot(&mut self) -> Result, io::Error> { + // Acquire both locks simultaneously for an atomic snapshot + let graph = self.graph.read().await; + let memory = self.memory.read().await; + + let triples = { + let all = graph + .find(aingle_graph::TriplePattern::any()) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + + all.into_iter() + .map(|t| TripleSnapshot { + subject: t.subject.to_string(), + predicate: t.predicate.to_string(), + object: value_to_json(&t.object), + }) + .collect::>() + }; + + let ineru_ltm = memory + .export_snapshot() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + + // Drop locks before serialization to reduce hold time + drop(graph); + drop(memory); + + let (last_applied_index, last_applied_term) = match &self.last_applied { + Some(lid) => (lid.index, lid.leader_id.term), + None => (0, 0), + }; + + // Read DAG tips if enabled + let dag_tips = { + #[cfg(feature = "dag")] + { + let graph = self.graph.read().await; + graph + .dag_store() + .and_then(|ds| ds.tips_raw().ok()) + .unwrap_or_default() + } + #[cfg(not(feature = "dag"))] + { + Vec::<[u8; 32]>::new() + } + }; + + // Export proofs if provider is available + let proofs = self + .proof_provider + .as_ref() + .map(|p| p.export_proofs()) + .unwrap_or_default(); + + let snapshot = ClusterSnapshot { + triples, + ineru_ltm, + last_applied_index, + last_applied_term, + dag_tips, + proofs, + checksum: String::new(), + }; + + let data = snapshot + .to_bytes() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + + let snapshot_id = format!( + "snap-{}-{}", + last_applied_term, last_applied_index + ); + + let meta = SnapshotMeta { + last_log_id: self.last_applied.clone(), + last_membership: self.last_membership.clone(), + snapshot_id, + }; + + Ok(Snapshot { + meta, + snapshot: Cursor::new(data), + }) + } +} + +fn value_to_json(v: &aingle_graph::Value) -> serde_json::Value { + match v { + aingle_graph::Value::String(s) => serde_json::Value::String(s.clone()), + aingle_graph::Value::Integer(i) => serde_json::json!(*i), + aingle_graph::Value::Float(f) => serde_json::json!(*f), + aingle_graph::Value::Boolean(b) => serde_json::json!(*b), + aingle_graph::Value::Json(j) => j.clone(), + aingle_graph::Value::Node(n) => serde_json::json!({ "node": n.to_string() }), + aingle_graph::Value::DateTime(dt) => serde_json::Value::String(dt.clone()), + aingle_graph::Value::Null => serde_json::Value::Null, + _ => serde_json::Value::String(format!("{:?}", v)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_build_snapshot() { + use openraft::vote::leader_id_adv::CommittedLeaderId; + use openraft::vote::RaftLeaderId; + + let graph = GraphDB::memory().unwrap(); + // Insert test data + let triple = aingle_graph::Triple::new( + aingle_graph::NodeId::named("alice"), + aingle_graph::Predicate::named("knows"), + aingle_graph::Value::String("bob".into()), + ); + graph.insert(triple).unwrap(); + + let memory = IneruMemory::agent_mode(); + + let mut builder = CortexSnapshotBuilder { + graph: Arc::new(RwLock::new(graph)), + memory: Arc::new(RwLock::new(memory)), + last_applied: Some(openraft::LogId::new( + CommittedLeaderId::new(1, 0), + 5, + )), + last_membership: openraft::StoredMembership::default(), + proof_provider: None, + }; + + let snap = builder.build_snapshot().await.unwrap(); + assert_eq!(snap.meta.last_log_id.as_ref().unwrap().index, 5); + assert!(!snap.snapshot.into_inner().is_empty()); + } +} diff --git a/crates/aingle_raft/src/state_machine.rs b/crates/aingle_raft/src/state_machine.rs new file mode 100644 index 00000000..06551e98 --- /dev/null +++ b/crates/aingle_raft/src/state_machine.rs @@ -0,0 +1,1102 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Raft state machine — applies committed entries to GraphDB + Ineru. + +use crate::snapshot_builder::CortexSnapshotBuilder; +use crate::types::{CortexResponse, CortexTypeConfig}; +use aingle_graph::GraphDB; +use aingle_wal::WalEntryKind; +use futures_util::StreamExt; +use ineru::IneruMemory; +use openraft::alias::LogIdOf; +use openraft::entry::RaftPayload; +use openraft::storage::{EntryResponder, RaftStateMachine, Snapshot}; +use openraft::type_config::alias::{SnapshotMetaOf, SnapshotOf, StoredMembershipOf}; +use openraft::StoredMembership; +use serde::{Deserialize, Serialize}; +use std::io; +use std::io::Cursor; +use std::sync::Arc; +use tokio::sync::RwLock; + +type C = CortexTypeConfig; +type LogId = LogIdOf; + +/// Raft state machine that applies committed mutations to GraphDB + Ineru. +/// +/// When Raft commits an entry, the state machine applies it +/// to the local graph database and memory system. +pub struct CortexStateMachine { + graph: Arc>, + memory: Arc>, + last_applied: RwLock>, + last_membership: RwLock>, + current_snapshot: RwLock, Vec)>>, + /// Count of applied mutations (for metrics). + applied_count: RwLock, + /// Optional provider for proof snapshot export/import. + proof_provider: Option>, +} + +impl CortexStateMachine { + /// Create a new state machine connected to shared GraphDB and IneruMemory. + pub fn new(graph: Arc>, memory: Arc>) -> Self { + Self { + graph, + memory, + last_applied: RwLock::new(None), + last_membership: RwLock::new(StoredMembership::default()), + current_snapshot: RwLock::new(None), + applied_count: RwLock::new(0), + proof_provider: None, + } + } + + /// Set the proof snapshot provider for snapshot export/import. + pub fn set_proof_provider(&mut self, provider: Arc) { + self.proof_provider = Some(provider); + } + + /// Apply a mutation from the WAL entry kind to the real stores. + pub async fn apply_mutation(&self, kind: &WalEntryKind) -> CortexResponse { + let mut count = self.applied_count.write().await; + *count += 1; + + match kind { + WalEntryKind::TripleInsert { + subject, + predicate, + object, + triple_id: _, + } => { + let value = json_to_value(object); + let triple = aingle_graph::Triple::new( + aingle_graph::NodeId::named(subject), + aingle_graph::Predicate::named(predicate), + value, + ); + let graph = self.graph.read().await; + match graph.insert(triple) { + Ok(id) => { + tracing::debug!(subject, predicate, "Applied TripleInsert"); + CortexResponse { + success: true, + detail: None, + id: Some(id.to_hex()), + } + } + Err(e) => { + tracing::error!("TripleInsert failed (potential state divergence): {e}"); + CortexResponse { + success: false, + detail: Some(format!("Insert failed: {e}")), + id: None, + } + } + } + } + WalEntryKind::TripleDelete { triple_id } => { + let tid = aingle_graph::TripleId::new(*triple_id); + let graph = self.graph.read().await; + match graph.delete(&tid) { + Ok(_) => { + tracing::debug!("Applied TripleDelete"); + CortexResponse { + success: true, + detail: None, + id: None, + } + } + Err(e) => { + tracing::error!("TripleDelete failed (potential state divergence): {e}"); + CortexResponse { + success: false, + detail: Some(format!("Delete failed: {e}")), + id: None, + } + } + } + } + WalEntryKind::MemoryStore { + memory_id: _, + entry_type, + data, + importance, + } => { + let entry = + ineru::MemoryEntry::new(entry_type, data.clone()).with_importance(*importance); + let mut memory = self.memory.write().await; + match memory.remember(entry) { + Ok(id) => CortexResponse { + success: true, + detail: None, + id: Some(id.to_hex()), + }, + Err(e) => CortexResponse { + success: false, + detail: Some(format!("MemoryStore failed: {e}")), + id: None, + }, + } + } + WalEntryKind::MemoryForget { memory_id } => { + if let Some(mid) = ineru::MemoryId::from_hex(memory_id) { + let mut memory = self.memory.write().await; + match memory.forget(&mid) { + Ok(()) => CortexResponse { + success: true, + detail: None, + id: None, + }, + Err(e) => CortexResponse { + success: false, + detail: Some(format!("MemoryForget failed: {e}")), + id: None, + }, + } + } else { + CortexResponse { + success: false, + detail: Some("Invalid memory ID".to_string()), + id: None, + } + } + } + WalEntryKind::MemoryConsolidate { + consolidated_count: _, + } => { + // Actually perform consolidation on this node + let mut memory = self.memory.write().await; + match memory.consolidate() { + Ok(count) => CortexResponse { + success: true, + detail: Some(count.to_string()), + id: None, + }, + Err(e) => CortexResponse { + success: false, + detail: Some(format!("Consolidation failed: {e}")), + id: None, + }, + } + } + WalEntryKind::LtmEntityCreate { + entity_id: _, + name, + entity_type, + } => { + tracing::debug!(name, entity_type, "Applied LtmEntityCreate"); + CortexResponse { + success: true, + detail: None, + id: None, + } + } + WalEntryKind::LtmLinkCreate { + from_entity, + to_entity, + relation, + weight: _, + } => { + tracing::debug!( + "Applied LtmLinkCreate: {} -> {} ({})", + from_entity, + to_entity, + relation + ); + CortexResponse { + success: true, + detail: None, + id: None, + } + } + WalEntryKind::LtmEntityDelete { entity_id } => { + tracing::debug!(entity_id, "Applied LtmEntityDelete"); + CortexResponse { + success: true, + detail: None, + id: None, + } + } + WalEntryKind::DagAction { action_bytes } => { + self.apply_dag_action(action_bytes).await + } + _ => CortexResponse { + success: true, + detail: None, + id: None, + }, + } + } + + /// Apply a serialized DagAction: store in DagStore, apply payload to GraphDB. + async fn apply_dag_action(&self, action_bytes: &[u8]) -> CortexResponse { + #[cfg(feature = "dag")] + { + use aingle_graph::dag::{DagAction, DagPayload}; + + let action = match DagAction::from_bytes(action_bytes) { + Some(a) => a, + None => { + return CortexResponse { + success: false, + detail: Some("Failed to deserialize DagAction".into()), + id: None, + }; + } + }; + + // Reject unsigned actions (Genesis exempt — system-generated at init) + if action.signature.is_none() + && !matches!(action.payload, DagPayload::Genesis { .. }) + { + tracing::warn!( + seq = action.seq, + author = %action.author, + "Rejecting unsigned DagAction — Ed25519 signature is mandatory" + ); + return CortexResponse { + success: false, + detail: Some("DagAction rejected: missing Ed25519 signature".into()), + id: None, + }; + } + + let action_hash = action.compute_hash(); + + // Store in DagStore + { + let graph = self.graph.read().await; + if let Some(dag_store) = graph.dag_store() { + if let Err(e) = dag_store.put(&action) { + tracing::error!("DagStore put failed: {e}"); + return CortexResponse { + success: false, + detail: Some(format!("DagStore put failed: {e}")), + id: None, + }; + } + } + } + + // Apply payload to materialized view + match &action.payload { + DagPayload::TripleInsert { triples } => { + let graph = self.graph.read().await; + for t in triples { + let value = json_to_value( + &serde_json::to_value(&t.object).unwrap_or_default(), + ); + let triple = aingle_graph::Triple::new( + aingle_graph::NodeId::named(&t.subject), + aingle_graph::Predicate::named(&t.predicate), + value, + ); + if let Err(e) = graph.insert(triple) { + tracing::error!("DagAction TripleInsert failed: {e}"); + } + } + } + DagPayload::TripleDelete { triple_ids, .. } => { + let graph = self.graph.read().await; + for tid in triple_ids { + let _ = graph.delete(&aingle_graph::TripleId::new(*tid)); + } + } + DagPayload::MemoryOp { kind } => { + // Memory operations are node-local by design (STM is not replicated). + // The DAG records them for audit purposes only; the actual memory + // mutation is applied via the separate MemoryStore/MemoryForget WAL entries. + match kind { + aingle_graph::dag::MemoryOpKind::Store { + entry_type, + importance, + } => { + tracing::debug!( + entry_type, + importance, + "DagAction MemoryOp::Store recorded (audit only)" + ); + } + aingle_graph::dag::MemoryOpKind::Forget { memory_id } => { + tracing::debug!(memory_id, "DagAction MemoryOp::Forget recorded (audit only)"); + } + aingle_graph::dag::MemoryOpKind::Consolidate => { + tracing::debug!("DagAction MemoryOp::Consolidate recorded (audit only)"); + } + } + } + DagPayload::Batch { ops } => { + // Apply each op's effect on the graph. + // TripleInsert and TripleDelete mutate state; all others + // are audit-only (logged but no graph mutation). + let graph = self.graph.read().await; + for op in ops { + match op { + DagPayload::TripleInsert { triples } => { + for t in triples { + let value = json_to_value( + &serde_json::to_value(&t.object).unwrap_or_default(), + ); + let triple = aingle_graph::Triple::new( + aingle_graph::NodeId::named(&t.subject), + aingle_graph::Predicate::named(&t.predicate), + value, + ); + let _ = graph.insert(triple); + } + } + DagPayload::TripleDelete { triple_ids, .. } => { + for tid in triple_ids { + let _ = graph.delete(&aingle_graph::TripleId::new(*tid)); + } + } + DagPayload::MemoryOp { .. } + | DagPayload::Genesis { .. } + | DagPayload::Compact { .. } + | DagPayload::Noop + | DagPayload::Custom { .. } => { + // Audit-only: no graph mutation needed + } + DagPayload::Batch { .. } => { + tracing::warn!("Nested Batch inside Batch — skipping to avoid recursion"); + } + } + } + } + DagPayload::Genesis { triple_count, description } => { + tracing::info!( + triple_count, + description, + "Applied DagAction::Genesis" + ); + } + DagPayload::Compact { pruned_count, retained_count, ref policy } => { + tracing::info!(pruned_count, retained_count, policy, "Applied DagAction::Compact"); + } + DagPayload::Noop => {} + DagPayload::Custom { ref payload_type, ref payload_summary, .. } => { + tracing::info!(payload_type, payload_summary, "Applied DagAction::Custom (audit only)"); + } + } + + tracing::debug!(hash = %action_hash, "Applied DagAction"); + CortexResponse { + success: true, + detail: None, + id: Some(action_hash.to_hex()), + } + } + + #[cfg(not(feature = "dag"))] + { + let _ = action_bytes; + tracing::warn!("DagAction received but `dag` feature is not enabled"); + CortexResponse { + success: false, + detail: Some("DAG feature not enabled".into()), + id: None, + } + } + } + + /// Set the last applied log ID. + pub async fn set_last_applied(&self, log_id: LogId) { + let mut la = self.last_applied.write().await; + *la = Some(log_id); + } + + /// Get the last applied log ID. + pub async fn last_applied(&self) -> Option { + let guard = self.last_applied.read().await; + guard.clone() + } + + /// Get the count of applied mutations. + pub async fn applied_count(&self) -> u64 { + *self.applied_count.read().await + } +} + +// ============================================================================ +// RaftStateMachine implementation +// ============================================================================ + +impl RaftStateMachine for Arc { + type SnapshotBuilder = CortexSnapshotBuilder; + + async fn applied_state( + &mut self, + ) -> Result<(Option, StoredMembershipOf), io::Error> { + let la = self.last_applied.read().await; + let membership = self.last_membership.read().await; + Ok((la.clone(), membership.clone())) + } + + async fn apply(&mut self, mut entries: Strm) -> Result<(), io::Error> + where + Strm: futures_util::Stream, io::Error>> + + Unpin + + Send, + { + while let Some(item) = entries.next().await { + let (entry, responder) = item?; + + // Check for membership change + if let Some(membership) = entry.get_membership() { + let mut lm = self.last_membership.write().await; + *lm = StoredMembership::new(Some(entry.log_id.clone()), membership.clone()); + } + + // Apply the business logic + let response = match &entry.payload { + openraft::EntryPayload::Blank => CortexResponse { + success: true, + detail: None, + id: None, + }, + openraft::EntryPayload::Normal(ref req) => { + self.apply_mutation(&req.kind).await + } + openraft::EntryPayload::Membership(_) => CortexResponse { + success: true, + detail: None, + id: None, + }, + }; + + // Update last_applied AFTER successful apply to avoid + // marking entries as applied before they actually are (#1). + { + let mut la = self.last_applied.write().await; + *la = Some(entry.log_id.clone()); + } + + // Send response to client if waiting (leader only) + if let Some(resp) = responder { + resp.send(response); + } + } + + Ok(()) + } + + async fn get_snapshot_builder(&mut self) -> Self::SnapshotBuilder { + let la = self.last_applied.read().await; + let membership = self.last_membership.read().await; + CortexSnapshotBuilder { + graph: Arc::clone(&self.graph), + memory: Arc::clone(&self.memory), + last_applied: la.clone(), + last_membership: membership.clone(), + proof_provider: self.proof_provider.clone(), + } + } + + async fn begin_receiving_snapshot(&mut self) -> Result>, io::Error> { + Ok(Cursor::new(Vec::new())) + } + + async fn install_snapshot( + &mut self, + meta: &SnapshotMetaOf, + snapshot: Cursor>, + ) -> Result<(), io::Error> { + let data = snapshot.into_inner(); + let cluster_snap = ClusterSnapshot::from_bytes(&data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + // Build both new graph and new memory into temporaries FIRST, + // then swap atomically only if both succeed (#7). + let new_graph = GraphDB::memory() + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + for ts in &cluster_snap.triples { + let value = json_to_value(&ts.object); + let triple = aingle_graph::Triple::new( + aingle_graph::NodeId::named(&ts.subject), + aingle_graph::Predicate::named(&ts.predicate), + value, + ); + new_graph + .insert(triple) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + } + + let new_memory = if !cluster_snap.ineru_ltm.is_empty() { + Some( + IneruMemory::import_snapshot(&cluster_snap.ineru_ltm) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, + format!("Failed to restore Ineru from snapshot: {e}")))? + ) + } else { + None + }; + + // Both built successfully — now swap under both locks so concurrent + // readers never observe new graph with old memory (or vice versa). + let mut graph = self.graph.write().await; + let mut memory = self.memory.write().await; + *graph = new_graph; + if let Some(restored) = new_memory { + *memory = restored; + } + + // Restore DAG tips if present + #[cfg(feature = "dag")] + { + if !cluster_snap.dag_tips.is_empty() { + graph.enable_dag(); + if let Some(dag_store) = graph.dag_store() { + if let Err(e) = dag_store.restore_tips(cluster_snap.dag_tips.clone()) { + tracing::warn!("Failed to restore DAG tips from snapshot: {e}"); + } else { + tracing::info!( + tips = cluster_snap.dag_tips.len(), + "Restored DAG tips from snapshot" + ); + } + } + } else if graph.dag_store().is_some() { + tracing::warn!( + "Snapshot has no DAG tips but this node has DAG enabled. \ + The snapshot may have been created by a node without DAG support." + ); + } + } + + drop(memory); + drop(graph); + + // Import proofs if provider is available and snapshot has proofs + if !cluster_snap.proofs.is_empty() { + if let Some(ref provider) = self.proof_provider { + provider.import_proofs(&cluster_snap.proofs); + tracing::info!( + proofs = cluster_snap.proofs.len(), + "Restored proofs from snapshot" + ); + } + } + + // Update metadata + { + let mut la = self.last_applied.write().await; + *la = meta.last_log_id.clone(); + } + { + let mut lm = self.last_membership.write().await; + *lm = meta.last_membership.clone(); + } + { + let mut snap = self.current_snapshot.write().await; + *snap = Some((meta.clone(), data)); + } + + tracing::info!( + triples = cluster_snap.triples.len(), + "Installed snapshot from leader" + ); + + Ok(()) + } + + async fn get_current_snapshot(&mut self) -> Result>, io::Error> { + let snap = self.current_snapshot.read().await; + match &*snap { + Some((meta, data)) => Ok(Some(Snapshot { + meta: meta.clone(), + snapshot: Cursor::new(data.clone()), + })), + None => Ok(None), + } + } +} + +// ============================================================================ +// Snapshot types +// ============================================================================ + +/// Trait for exporting/importing proofs into Raft snapshots. +pub trait ProofSnapshotProvider: Send + Sync { + /// Export all proofs as snapshot entries. + fn export_proofs(&self) -> Vec; + /// Import proofs from a snapshot, replacing existing data. + fn import_proofs(&self, proofs: &[ProofSnapshot]); +} + +/// A single proof entry in a Raft snapshot. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProofSnapshot { + pub id: String, + pub proof_type: String, + pub data: Vec, + pub created_at: String, + pub verified: bool, + pub verified_at: Option, + pub metadata: serde_json::Value, +} + +/// A serializable cluster snapshot for state transfer. +/// +/// When a new node joins the cluster, it receives this snapshot +/// containing the full graph and LTM state. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClusterSnapshot { + /// All triples in wire format (subject, predicate, object JSON). + pub triples: Vec, + /// Ineru LTM snapshot (serialized via export_snapshot). + /// STM is NOT replicated — it's node-local working memory. + pub ineru_ltm: Vec, + /// Last applied log index. + pub last_applied_index: u64, + /// Last applied log term. + pub last_applied_term: u64, + /// DAG tip hashes (empty if DAG not enabled). Backward compatible via serde(default). + #[serde(default)] + pub dag_tips: Vec<[u8; 32]>, + /// Proof snapshots. Backward compatible via serde(default). + #[serde(default)] + pub proofs: Vec, + /// Blake3 integrity checksum over triples + ineru_ltm + proofs. + #[serde(default)] + pub checksum: String, +} + +/// Wire format for a triple in a snapshot. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TripleSnapshot { + pub subject: String, + pub predicate: String, + pub object: serde_json::Value, +} + +impl ClusterSnapshot { + /// Create an empty snapshot. + pub fn empty() -> Self { + Self { + triples: Vec::new(), + ineru_ltm: Vec::new(), + last_applied_index: 0, + last_applied_term: 0, + dag_tips: Vec::new(), + proofs: Vec::new(), + checksum: String::new(), + } + } + + /// Serialize the snapshot to bytes, computing a blake3 integrity checksum. + pub fn to_bytes(&self) -> Result, String> { + // Serialize everything except checksum first, then patch checksum in + // to avoid cloning the entire snapshot (triples + LTM can be large). + let checksum = compute_checksum(&self.triples, &self.ineru_ltm, &self.proofs); + let wrapper = ClusterSnapshotRef { + triples: &self.triples, + ineru_ltm: &self.ineru_ltm, + last_applied_index: self.last_applied_index, + last_applied_term: self.last_applied_term, + dag_tips: &self.dag_tips, + proofs: &self.proofs, + checksum: &checksum, + }; + serde_json::to_vec(&wrapper).map_err(|e| format!("Snapshot serialization failed: {e}")) + } + + /// Deserialize a snapshot from bytes, verifying the integrity checksum. + pub fn from_bytes(data: &[u8]) -> Result { + let snap: Self = serde_json::from_slice(data) + .map_err(|e| format!("Snapshot deserialization failed: {e}"))?; + let expected = compute_checksum(&snap.triples, &snap.ineru_ltm, &snap.proofs); + if !snap.checksum.is_empty() && snap.checksum != expected { + return Err(format!( + "Snapshot checksum mismatch: expected {expected}, got {}", + snap.checksum + )); + } + Ok(snap) + } +} + +// ============================================================================ +// Helpers +// ============================================================================ + +/// Borrow-based snapshot wrapper to avoid cloning during serialization. +#[derive(Serialize)] +struct ClusterSnapshotRef<'a> { + triples: &'a [TripleSnapshot], + ineru_ltm: &'a [u8], + last_applied_index: u64, + last_applied_term: u64, + dag_tips: &'a [[u8; 32]], + proofs: &'a [ProofSnapshot], + checksum: &'a str, +} + +/// Compute a blake3 checksum over snapshot content for integrity verification. +fn compute_checksum( + triples: &[TripleSnapshot], + ineru_ltm: &[u8], + proofs: &[ProofSnapshot], +) -> String { + let mut hasher = blake3::Hasher::new(); + let triples_bytes = serde_json::to_vec(triples).unwrap_or_default(); + hasher.update(&triples_bytes); + hasher.update(ineru_ltm); + if !proofs.is_empty() { + let proofs_bytes = serde_json::to_vec(proofs).unwrap_or_default(); + hasher.update(&proofs_bytes); + } + hasher.finalize().to_hex().to_string() +} + +fn json_to_value(v: &serde_json::Value) -> aingle_graph::Value { + match v { + serde_json::Value::String(s) => aingle_graph::Value::String(s.clone()), + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + aingle_graph::Value::Integer(i) + } else if let Some(f) = n.as_f64() { + aingle_graph::Value::Float(f) + } else { + aingle_graph::Value::String(n.to_string()) + } + } + serde_json::Value::Bool(b) => aingle_graph::Value::Boolean(*b), + _ => aingle_graph::Value::Json(v.clone()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use openraft::vote::RaftLeaderId; + + fn make_graph_and_memory() -> (Arc>, Arc>) { + let graph = GraphDB::memory().unwrap(); + let memory = IneruMemory::agent_mode(); + ( + Arc::new(RwLock::new(graph)), + Arc::new(RwLock::new(memory)), + ) + } + + #[tokio::test] + async fn test_state_machine_new() { + let (graph, memory) = make_graph_and_memory(); + let sm = CortexStateMachine::new(graph, memory); + assert!(sm.last_applied().await.is_none()); + assert_eq!(sm.applied_count().await, 0); + } + + #[tokio::test] + async fn test_apply_triple_insert_real() { + let (graph, memory) = make_graph_and_memory(); + let sm = CortexStateMachine::new(Arc::clone(&graph), Arc::clone(&memory)); + + let kind = WalEntryKind::TripleInsert { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + triple_id: [0u8; 32], + }; + let resp = sm.apply_mutation(&kind).await; + assert!(resp.success); + assert!(resp.id.is_some(), "TripleInsert should return an ID"); + assert_eq!(sm.applied_count().await, 1); + + // Verify in GraphDB + let g = graph.read().await; + let count = g.count(); + assert!(count >= 1); + } + + #[tokio::test] + async fn test_apply_triple_delete() { + let (graph, memory) = make_graph_and_memory(); + let sm = CortexStateMachine::new(Arc::clone(&graph), Arc::clone(&memory)); + + // Insert a triple first + let triple = aingle_graph::Triple::new( + aingle_graph::NodeId::named("alice"), + aingle_graph::Predicate::named("knows"), + aingle_graph::Value::String("bob".into()), + ); + let tid = { + let g = graph.read().await; + g.insert(triple).unwrap() + }; + + // Delete via state machine + let kind = WalEntryKind::TripleDelete { + triple_id: *tid.as_bytes(), + }; + let resp = sm.apply_mutation(&kind).await; + assert!(resp.success); + } + + #[tokio::test] + async fn test_apply_memory_store() { + let (graph, memory) = make_graph_and_memory(); + let sm = CortexStateMachine::new(graph, memory); + + let kind = WalEntryKind::MemoryStore { + memory_id: "m1".into(), + entry_type: "test".into(), + data: serde_json::json!({"key": "value"}), + importance: 0.8, + }; + let resp = sm.apply_mutation(&kind).await; + assert!(resp.success); + assert!(resp.id.is_some(), "MemoryStore should return an ID"); + } + + #[tokio::test] + async fn test_apply_multiple() { + let (graph, memory) = make_graph_and_memory(); + let sm = CortexStateMachine::new(graph, memory); + for i in 0..5 { + let kind = WalEntryKind::TripleInsert { + subject: format!("s{}", i), + predicate: "p".into(), + object: serde_json::json!(i), + triple_id: [i as u8; 32], + }; + sm.apply_mutation(&kind).await; + } + assert_eq!(sm.applied_count().await, 5); + } + + #[tokio::test] + async fn test_apply_ltm_operations() { + let (graph, memory) = make_graph_and_memory(); + let sm = CortexStateMachine::new(graph, memory); + + let resp = sm + .apply_mutation(&WalEntryKind::LtmEntityCreate { + entity_id: "e1".into(), + name: "Entity1".into(), + entity_type: "concept".into(), + }) + .await; + assert!(resp.success); + + let resp = sm + .apply_mutation(&WalEntryKind::LtmLinkCreate { + from_entity: "e1".into(), + to_entity: "e2".into(), + relation: "related_to".into(), + weight: 0.9, + }) + .await; + assert!(resp.success); + + let resp = sm + .apply_mutation(&WalEntryKind::LtmEntityDelete { + entity_id: "e1".into(), + }) + .await; + assert!(resp.success); + + assert_eq!(sm.applied_count().await, 3); + } + + #[tokio::test] + async fn test_install_snapshot_clears_existing_data() { + let (graph, memory) = make_graph_and_memory(); + let sm = Arc::new(CortexStateMachine::new( + Arc::clone(&graph), + Arc::clone(&memory), + )); + + // Pre-populate graph with data that should be cleared + { + let g = graph.read().await; + g.insert(aingle_graph::Triple::new( + aingle_graph::NodeId::named("old_subject"), + aingle_graph::Predicate::named("old_pred"), + aingle_graph::Value::String("old_value".into()), + )) + .unwrap(); + } + assert_eq!(graph.read().await.count(), 1); + + // Create snapshot with different data + let snap = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "new_subject".into(), + predicate: "new_pred".into(), + object: serde_json::json!("new_value"), + }], + ineru_ltm: vec![], + last_applied_index: 10, + last_applied_term: 2, + dag_tips: vec![], + proofs: vec![], + checksum: String::new(), + }; + let data = snap.to_bytes().unwrap(); + + let meta = openraft::storage::SnapshotMeta { + last_log_id: Some(openraft::LogId::new( + openraft::vote::leader_id_adv::CommittedLeaderId::new(2, 0), + 10, + )), + last_membership: openraft::StoredMembership::default(), + snapshot_id: "test".to_string(), + }; + + let mut sm_mut = sm.clone(); + sm_mut + .install_snapshot(&meta, Cursor::new(data)) + .await + .unwrap(); + + // Verify: old data cleared, only snapshot data present + let g = graph.read().await; + assert_eq!(g.count(), 1, "old data should be cleared, only snapshot data remains"); + let triples = g.find(aingle_graph::TriplePattern::any()).unwrap(); + let subject_str = triples[0].subject.to_string(); + assert!( + subject_str.contains("new_subject"), + "Expected subject containing 'new_subject', got '{subject_str}'" + ); + } + + #[test] + fn test_snapshot_empty() { + let snap = ClusterSnapshot::empty(); + assert!(snap.triples.is_empty()); + assert!(snap.ineru_ltm.is_empty()); + assert_eq!(snap.last_applied_index, 0); + } + + #[test] + fn test_snapshot_roundtrip() { + let snap = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + ineru_ltm: vec![1, 2, 3, 4], + last_applied_index: 42, + last_applied_term: 5, + dag_tips: vec![], + proofs: vec![], + checksum: String::new(), + }; + + let bytes = snap.to_bytes().unwrap(); + let restored = ClusterSnapshot::from_bytes(&bytes).unwrap(); + + assert_eq!(restored.triples.len(), 1); + assert_eq!(restored.triples[0].subject, "alice"); + assert_eq!(restored.ineru_ltm, vec![1, 2, 3, 4]); + assert_eq!(restored.last_applied_index, 42); + assert_eq!(restored.last_applied_term, 5); + } + + #[test] + fn test_snapshot_stm_not_included() { + let snap = ClusterSnapshot::empty(); + let json = serde_json::to_value(&snap).unwrap(); + assert!(json.get("stm").is_none()); + assert!(json.get("ineru_ltm").is_some()); + } + + #[test] + fn test_snapshot_checksum_roundtrip() { + let snap = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + }], + ineru_ltm: vec![10, 20, 30], + last_applied_index: 7, + last_applied_term: 2, + dag_tips: vec![], + proofs: vec![], + checksum: String::new(), + }; + let bytes = snap.to_bytes().unwrap(); + // Verify checksum was written into serialized data + let raw: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + let checksum = raw["checksum"].as_str().unwrap(); + assert!(!checksum.is_empty(), "checksum should be set after to_bytes"); + + // Valid roundtrip succeeds + let restored = ClusterSnapshot::from_bytes(&bytes).unwrap(); + assert_eq!(restored.checksum, checksum); + } + + #[test] + fn test_snapshot_corrupt_data_rejected() { + let snap = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "s".into(), + predicate: "p".into(), + object: serde_json::json!("o"), + }], + ineru_ltm: vec![1, 2, 3], + last_applied_index: 1, + last_applied_term: 1, + dag_tips: vec![], + proofs: vec![], + checksum: String::new(), + }; + let mut bytes = snap.to_bytes().unwrap(); + + // Corrupt one byte in the middle of the payload + let mid = bytes.len() / 2; + bytes[mid] ^= 0xFF; + + // Deserialization should fail (either JSON parse error or checksum mismatch) + let result = ClusterSnapshot::from_bytes(&bytes); + assert!(result.is_err(), "corrupted snapshot must be rejected"); + } + + #[test] + fn test_snapshot_wrong_checksum_rejected() { + // Manually craft a snapshot with a valid structure but wrong checksum + let snap = ClusterSnapshot { + triples: vec![TripleSnapshot { + subject: "a".into(), + predicate: "b".into(), + object: serde_json::json!("c"), + }], + ineru_ltm: vec![], + last_applied_index: 0, + last_applied_term: 0, + dag_tips: vec![], + proofs: vec![], + checksum: "deadbeef".to_string(), + }; + // Serialize directly (bypassing to_bytes which would compute correct checksum) + let bytes = serde_json::to_vec(&snap).unwrap(); + let result = ClusterSnapshot::from_bytes(&bytes); + assert!(result.is_err()); + assert!( + result.unwrap_err().contains("checksum mismatch"), + "error should mention checksum mismatch" + ); + } + + #[test] + fn test_snapshot_empty_checksum_accepted() { + // Backward compatibility: snapshots without checksum should be accepted + let snap = ClusterSnapshot { + triples: vec![], + ineru_ltm: vec![], + last_applied_index: 0, + last_applied_term: 0, + dag_tips: vec![], + proofs: vec![], + checksum: String::new(), + }; + let bytes = serde_json::to_vec(&snap).unwrap(); + let result = ClusterSnapshot::from_bytes(&bytes); + assert!(result.is_ok(), "empty checksum should be accepted for backward compat"); + } +} diff --git a/crates/aingle_raft/src/types.rs b/crates/aingle_raft/src/types.rs new file mode 100644 index 00000000..c15fd068 --- /dev/null +++ b/crates/aingle_raft/src/types.rs @@ -0,0 +1,65 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! OpenRaft type configuration for Cortex. + +use aingle_wal::WalEntryKind; +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Node identifier. +pub type NodeId = u64; + +/// A Raft client request containing a WAL mutation. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CortexRequest { + pub kind: WalEntryKind, +} + +// Eq is required by openraft; we delegate to PartialEq which is sufficient +// for the WAL entry types used here. +impl Eq for CortexRequest {} + +impl fmt::Display for CortexRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CortexRequest({:?})", std::mem::discriminant(&self.kind)) + } +} + +/// Response from applying a Raft entry. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct CortexResponse { + pub success: bool, + pub detail: Option, + /// Generated resource ID (triple hash, memory ID, etc.). + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, +} + +impl fmt::Display for CortexResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CortexResponse(success={})", self.success) + } +} + +/// Node address information for the cluster. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] +pub struct CortexNode { + pub rest_addr: String, + pub p2p_addr: String, +} + +impl fmt::Display for CortexNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CortexNode(rest={}, p2p={})", self.rest_addr, self.p2p_addr) + } +} + +// Define the openraft TypeConfig +openraft::declare_raft_types!( + pub CortexTypeConfig: + D = CortexRequest, + R = CortexResponse, + Node = CortexNode, + NodeId = NodeId, +); diff --git a/crates/aingle_sqlite/Cargo.toml b/crates/aingle_sqlite/Cargo.toml index fa5e30bf..2747f3ab 100644 --- a/crates/aingle_sqlite/Cargo.toml +++ b/crates/aingle_sqlite/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_sqlite" version = "0.0.1" description = "Abstractions for persistence of AIngle state via SQLite" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_sqlite" @@ -17,10 +17,10 @@ chashmap = "2.2" chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } derive_more = "0.99.3" either = "1.5.0" -fallible-iterator = "0.2.0" +fallible-iterator = "0.3.0" failure = "0.1.6" ai_fixt = { version = "^0.0.1", path = "../ai_fixt" } -futures = "0.3.1" +futures = "0.3.32" ai_hash = { version = ">=0.0.1", path = "../ai_hash", features = ["rusqlite"] } aingle_middleware_bytes = "=0.0.3" aingle_zome_types = { version = ">=0.0.1", path = "../aingle_zome_types" } @@ -34,8 +34,8 @@ page_size = "0.4.2" parking_lot = "0.12" rand = "0.9" r2d2 = "0.8" -r2d2_sqlite = "0.18" -rmp-serde = "0.15" +r2d2_sqlite = "0.25" +rmp-serde = "1" scheduled-thread-pool = "0.2" serde = "1.0" serde_derive = "1.0" @@ -47,7 +47,7 @@ aingle_util = { version = "0.0.1", path = "../aingle_util" } tracing = "0.1.18" tracing-futures = "0.2" -rusqlite = { version = "0.25", features = [ +rusqlite = { version = "0.32", features = [ "blob", # better integration with blob types (Read, Write, etc) "backup", "trace", diff --git a/crates/aingle_sqlite/src/conn.rs b/crates/aingle_sqlite/src/conn.rs index 9af13253..6772e9d2 100644 --- a/crates/aingle_sqlite/src/conn.rs +++ b/crates/aingle_sqlite/src/conn.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use chashmap::CHashMap; use once_cell::sync::Lazy; diff --git a/crates/aingle_sqlite/src/conn/singleton_conn.rs b/crates/aingle_sqlite/src/conn/singleton_conn.rs index 66677161..475ad8e1 100644 --- a/crates/aingle_sqlite/src/conn/singleton_conn.rs +++ b/crates/aingle_sqlite/src/conn/singleton_conn.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(deprecated)] #![allow(dead_code)] diff --git a/crates/aingle_sqlite/src/db.rs b/crates/aingle_sqlite/src/db.rs index b8da6cd8..d53af349 100644 --- a/crates/aingle_sqlite/src/db.rs +++ b/crates/aingle_sqlite/src/db.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Functions dealing with obtaining and referencing singleton databases use crate::{ diff --git a/crates/aingle_sqlite/src/db/p2p_agent_store.rs b/crates/aingle_sqlite/src/db/p2p_agent_store.rs index 0a7ccd12..17b8d054 100644 --- a/crates/aingle_sqlite/src/db/p2p_agent_store.rs +++ b/crates/aingle_sqlite/src/db/p2p_agent_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! p2p_agent_store sql logic use crate::prelude::*; diff --git a/crates/aingle_sqlite/src/db/p2p_agent_store/p2p_test.rs b/crates/aingle_sqlite/src/db/p2p_agent_store/p2p_test.rs index f614d12f..675c2bcc 100644 --- a/crates/aingle_sqlite/src/db/p2p_agent_store/p2p_test.rs +++ b/crates/aingle_sqlite/src/db/p2p_agent_store/p2p_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use kitsune_p2p::agent_store::AgentInfoSigned; use kitsune_p2p::sgd_arc::SgdArc; diff --git a/crates/aingle_sqlite/src/db/p2p_metrics.rs b/crates/aingle_sqlite/src/db/p2p_metrics.rs index 20abd13b..a7bb1c5e 100644 --- a/crates/aingle_sqlite/src/db/p2p_metrics.rs +++ b/crates/aingle_sqlite/src/db/p2p_metrics.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::{ prelude::{DatabaseError, DatabaseResult}, sql::sql_p2p_metrics, diff --git a/crates/aingle_sqlite/src/error.rs b/crates/aingle_sqlite/src/error.rs index 8a38a56f..f873277b 100644 --- a/crates/aingle_sqlite/src/error.rs +++ b/crates/aingle_sqlite/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! All possible errors when working with SQLite databases // missing_docs allowed here since the errors already have self-descriptive strings diff --git a/crates/aingle_sqlite/src/exports.rs b/crates/aingle_sqlite/src/exports.rs index d601700d..80df5fc9 100644 --- a/crates/aingle_sqlite/src/exports.rs +++ b/crates/aingle_sqlite/src/exports.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A few imports from `rkv`, to avoid consumers needing to import `rkv` explicitly pub use fallible_iterator::FallibleIterator; diff --git a/crates/aingle_sqlite/src/fatal.rs b/crates/aingle_sqlite/src/fatal.rs index b0c7a7d2..c0b70c39 100644 --- a/crates/aingle_sqlite/src/fatal.rs +++ b/crates/aingle_sqlite/src/fatal.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Sometimes we have fatal errors, and need to halt the system. //! This module provides standards for showing these messages to the user. diff --git a/crates/aingle_sqlite/src/lib.rs b/crates/aingle_sqlite/src/lib.rs index 27b632da..8cefb265 100644 --- a/crates/aingle_sqlite/src/lib.rs +++ b/crates/aingle_sqlite/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] //! # Building blocks for persisted AIngle state diff --git a/crates/aingle_sqlite/src/prelude.rs b/crates/aingle_sqlite/src/prelude.rs index a94793cb..04cd6272 100644 --- a/crates/aingle_sqlite/src/prelude.rs +++ b/crates/aingle_sqlite/src/prelude.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Common types, especially traits, which we'd like to import en masse // pub use crate::buffer::*; diff --git a/crates/aingle_sqlite/src/schema.rs b/crates/aingle_sqlite/src/schema.rs index 3ef06793..09fb6678 100644 --- a/crates/aingle_sqlite/src/schema.rs +++ b/crates/aingle_sqlite/src/schema.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use once_cell::sync::Lazy; use rusqlite::Connection; diff --git a/crates/aingle_sqlite/src/schema/tables.rs b/crates/aingle_sqlite/src/schema/tables.rs index f1fa0b04..36b34384 100644 --- a/crates/aingle_sqlite/src/schema/tables.rs +++ b/crates/aingle_sqlite/src/schema/tables.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub trait SqlInsert { fn sql_insert(&self, txn: &mut R) -> DatabaseResult<()>; } diff --git a/crates/aingle_sqlite/src/sql.rs b/crates/aingle_sqlite/src/sql.rs index f269083e..bdb3b79d 100644 --- a/crates/aingle_sqlite/src/sql.rs +++ b/crates/aingle_sqlite/src/sql.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub mod sql_cell { pub(crate) const SCHEMA: &str = include_str!("sql/cell/schema.sql"); pub const UPDATE_INTEGRATE_OPS: &str = include_str!("sql/cell/update_integrate_ops.sql"); diff --git a/crates/aingle_sqlite/src/swansong.rs b/crates/aingle_sqlite/src/swansong.rs index 2c36e95e..0b5b7ca8 100644 --- a/crates/aingle_sqlite/src/swansong.rs +++ b/crates/aingle_sqlite/src/swansong.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// A shrinkwrapped type with a Drop impl provided as a simple closure #[derive(shrinkwraprs::Shrinkwrap)] #[shrinkwrap(mutable, unsafe_ignore_visibility)] diff --git a/crates/aingle_sqlite/src/table.rs b/crates/aingle_sqlite/src/table.rs index 5b59aab3..008963df 100644 --- a/crates/aingle_sqlite/src/table.rs +++ b/crates/aingle_sqlite/src/table.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Functionality for safely accessing databases. use rusqlite::Connection; diff --git a/crates/aingle_sqlite/src/test_utils.rs b/crates/aingle_sqlite/src/test_utils.rs index 43ad386a..b88c1fc9 100644 --- a/crates/aingle_sqlite/src/test_utils.rs +++ b/crates/aingle_sqlite/src/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for unit tests use crate::db::DbKind; diff --git a/crates/aingle_state/Cargo.toml b/crates/aingle_state/Cargo.toml index 4943da4e..f62b043e 100644 --- a/crates/aingle_state/Cargo.toml +++ b/crates/aingle_state/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_state" version = "0.0.1" description = "TODO minimize deps" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_state" @@ -16,7 +16,7 @@ derive_more = "0.99.3" either = "1.5" aingle_sqlite = { version = "0.0.1", path = "../aingle_sqlite" } ai_hash = { version = ">=0.0.1", path = "../ai_hash", features = ["full"] } -fallible-iterator = "0.2.0" +fallible-iterator = "0.3.0" aingle_keystore = { version = "0.0.1", path = "../aingle_keystore" } aingle_middleware_bytes = "=0.0.3" aingle_p2p = { version = "0.0.1", path = "../aingle_p2p" } @@ -36,7 +36,7 @@ tracing = "0.1" tracing-futures = "0.2.4" tempdir = { version = "0.3", optional = true } -base64 = {version = "0.21", optional = true} +base64 = {version = "0.22", optional = true} [dev-dependencies] anyhow = "1.0" @@ -45,7 +45,7 @@ adk = { version = "^0.0.1", path = "../adk" } aingle_wasm_test_utils = { path = "../test_utils/wasm", version = "0.0.1" } matches = "0.1.8" observability = { version = "0.1", package = "aingle-observability" } -pretty_assertions = "0.7.2" +pretty_assertions = "1.4" tempdir = "0.3" diff --git a/crates/aingle_state/src/entry_def.rs b/crates/aingle_state/src/entry_def.rs index 2cb700a4..a36cac0d 100644 --- a/crates/aingle_state/src/entry_def.rs +++ b/crates/aingle_state/src/entry_def.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; use aingle_sqlite::rusqlite; use aingle_sqlite::rusqlite::named_params; diff --git a/crates/aingle_state/src/host_fn_workspace.rs b/crates/aingle_state/src/host_fn_workspace.rs index c80323c3..3a3158dd 100644 --- a/crates/aingle_state/src/host_fn_workspace.rs +++ b/crates/aingle_state/src/host_fn_workspace.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::AgentPubKey; use aingle_types::env::EnvRead; use aingle_types::env::EnvWrite; diff --git a/crates/aingle_state/src/lib.rs b/crates/aingle_state/src/lib.rs index c97da02f..d2e102ac 100644 --- a/crates/aingle_state/src/lib.rs +++ b/crates/aingle_state/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Persisted State building blocks //! //! This crate provides a few types for working with databases. The types build upon those found in [aingle_sqlite::buffer]. diff --git a/crates/aingle_state/src/mutations.rs b/crates/aingle_state/src/mutations.rs index 2e77942a..942914ba 100644 --- a/crates/aingle_state/src/mutations.rs +++ b/crates/aingle_state/src/mutations.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::entry_def::EntryDefStoreKey; use crate::prelude::SignedValidationReceipt; use crate::query::to_blob; diff --git a/crates/aingle_state/src/mutations/error.rs b/crates/aingle_state/src/mutations/error.rs index 4f075a76..492d617b 100644 --- a/crates/aingle_state/src/mutations/error.rs +++ b/crates/aingle_state/src/mutations/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use thiserror::Error; use crate::query::StateQueryError; diff --git a/crates/aingle_state/src/prelude.rs b/crates/aingle_state/src/prelude.rs index 8e07075d..289b949c 100644 --- a/crates/aingle_state/src/prelude.rs +++ b/crates/aingle_state/src/prelude.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub use crate::mutations::*; pub use crate::query::prelude::*; pub use crate::source_chain::*; diff --git a/crates/aingle_state/src/query.rs b/crates/aingle_state/src/query.rs index 67133908..88278e0f 100644 --- a/crates/aingle_state/src/query.rs +++ b/crates/aingle_state/src/query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::scratch::FilteredScratch; use crate::scratch::Scratch; use ai_hash::hash_type::AnySgd; diff --git a/crates/aingle_state/src/query/chain_head.rs b/crates/aingle_state/src/query/chain_head.rs index 2eb67e16..a9e9360d 100644 --- a/crates/aingle_state/src/query/chain_head.rs +++ b/crates/aingle_state/src/query/chain_head.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_sqlite::rusqlite::*; use aingle_zome_types::*; diff --git a/crates/aingle_state/src/query/element_details.rs b/crates/aingle_state/src/query/element_details.rs index 89dd4c0d..be5797b2 100644 --- a/crates/aingle_state/src/query/element_details.rs +++ b/crates/aingle_state/src/query/element_details.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_sqlite::rusqlite::named_params; use aingle_types::prelude::Judged; diff --git a/crates/aingle_state/src/query/entry_details.rs b/crates/aingle_state/src/query/entry_details.rs index 401057e4..402e4245 100644 --- a/crates/aingle_state/src/query/entry_details.rs +++ b/crates/aingle_state/src/query/entry_details.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_sqlite::rusqlite::named_params; use aingle_types::prelude::Judged; diff --git a/crates/aingle_state/src/query/error.rs b/crates/aingle_state/src/query/error.rs index cf824f40..67fa1da4 100644 --- a/crates/aingle_state/src/query/error.rs +++ b/crates/aingle_state/src/query/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_types::sgd_op::SgdOpType; use aingle_zome_types::HeaderType; use thiserror::Error; diff --git a/crates/aingle_state/src/query/link.rs b/crates/aingle_state/src/query/link.rs index 6c86eeb8..a1146ccb 100644 --- a/crates/aingle_state/src/query/link.rs +++ b/crates/aingle_state/src/query/link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_sqlite::rusqlite::named_params; use aingle_types::sgd_op::SgdOpType; diff --git a/crates/aingle_state/src/query/link_details.rs b/crates/aingle_state/src/query/link_details.rs index 2739e2d6..80d76cb1 100644 --- a/crates/aingle_state/src/query/link_details.rs +++ b/crates/aingle_state/src/query/link_details.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_zome_types::*; use std::fmt::Debug; diff --git a/crates/aingle_state/src/query/live_element.rs b/crates/aingle_state/src/query/live_element.rs index 7a13b479..dfcd4b7e 100644 --- a/crates/aingle_state/src/query/live_element.rs +++ b/crates/aingle_state/src/query/live_element.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_sqlite::rusqlite::named_params; use aingle_types::sgd_op::SgdOpType; diff --git a/crates/aingle_state/src/query/live_element/test.rs b/crates/aingle_state/src/query/live_element/test.rs index 13e995b6..0f635e07 100644 --- a/crates/aingle_state/src/query/live_element/test.rs +++ b/crates/aingle_state/src/query/live_element/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_sqlite::rusqlite::Connection; use aingle_sqlite::rusqlite::TransactionBehavior; use aingle_sqlite::schema::SCHEMA_CELL; diff --git a/crates/aingle_state/src/query/live_entry.rs b/crates/aingle_state/src/query/live_entry.rs index d252a25a..ca70121a 100644 --- a/crates/aingle_state/src/query/live_entry.rs +++ b/crates/aingle_state/src/query/live_entry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::*; use aingle_sqlite::rusqlite::named_params; use aingle_types::prelude::SgdOpError; diff --git a/crates/aingle_state/src/query/live_entry/test.rs b/crates/aingle_state/src/query/live_entry/test.rs index 093d3571..ddf4535e 100644 --- a/crates/aingle_state/src/query/live_entry/test.rs +++ b/crates/aingle_state/src/query/live_entry/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_sqlite::rusqlite::Connection; use aingle_sqlite::rusqlite::TransactionBehavior; use aingle_sqlite::schema::SCHEMA_CELL; diff --git a/crates/aingle_state/src/query/test_data.rs b/crates/aingle_state/src/query/test_data.rs index 16279a6b..b82a579b 100644 --- a/crates/aingle_state/src/query/test_data.rs +++ b/crates/aingle_state/src/query/test_data.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::clippy::redundant_clone)] use ::ai_fixt::prelude::*; use ai_hash::*; diff --git a/crates/aingle_state/src/query/tests.rs b/crates/aingle_state/src/query/tests.rs index 01af20f0..dfe9775b 100644 --- a/crates/aingle_state/src/query/tests.rs +++ b/crates/aingle_state/src/query/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::mutations_helpers::insert_valid_authored_op; use crate::scratch::Scratch; use ::ai_fixt::prelude::*; diff --git a/crates/aingle_state/src/query/tests/chain_sequence.rs b/crates/aingle_state/src/query/tests/chain_sequence.rs index c1df8d3a..d5466466 100644 --- a/crates/aingle_state/src/query/tests/chain_sequence.rs +++ b/crates/aingle_state/src/query/tests/chain_sequence.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::{source_chain::SourceChainResult, test_utils::test_cell_env}; use ai_hash::HeaderHash; use aingle_sqlite::prelude::*; diff --git a/crates/aingle_state/src/query/tests/chain_test.rs b/crates/aingle_state/src/query/tests/chain_test.rs index e150692f..bf36f820 100644 --- a/crates/aingle_state/src/query/tests/chain_test.rs +++ b/crates/aingle_state/src/query/tests/chain_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::test_utils::{test_cell_env, TestEnv}; use ::ai_fixt::prelude::*; use ai_hash::AgentPubKey; diff --git a/crates/aingle_state/src/query/tests/details.rs b/crates/aingle_state/src/query/tests/details.rs index 977d7b8e..cc33a4ff 100644 --- a/crates/aingle_state/src/query/tests/details.rs +++ b/crates/aingle_state/src/query/tests/details.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use element_details::GetElementDetailsQuery; use crate::query::entry_details::GetEntryDetailsQuery; diff --git a/crates/aingle_state/src/query/tests/links.rs b/crates/aingle_state/src/query/tests/links.rs index bd4cbd0b..baeb3e61 100644 --- a/crates/aingle_state/src/query/tests/links.rs +++ b/crates/aingle_state/src/query/tests/links.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/aingle_state/src/query/tests/links_test.rs b/crates/aingle_state/src/query/tests/links_test.rs index 9163cf7e..70ae40f8 100644 --- a/crates/aingle_state/src/query/tests/links_test.rs +++ b/crates/aingle_state/src/query/tests/links_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::here; use crate::prelude::*; diff --git a/crates/aingle_state/src/query/tests/store.rs b/crates/aingle_state/src/query/tests/store.rs index c0ac2cec..fea48f84 100644 --- a/crates/aingle_state/src/query/tests/store.rs +++ b/crates/aingle_state/src/query/tests/store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; #[tokio::test(flavor = "multi_thread")] diff --git a/crates/aingle_state/src/query/tests/sys_meta.rs b/crates/aingle_state/src/query/tests/sys_meta.rs index cc0ec13f..1cba5ab0 100644 --- a/crates/aingle_state/src/query/tests/sys_meta.rs +++ b/crates/aingle_state/src/query/tests/sys_meta.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #[cfg(test)] mod tests { use ::ai_fixt::prelude::*; diff --git a/crates/aingle_state/src/saf_def.rs b/crates/aingle_state/src/saf_def.rs index d12468ad..bb5876ff 100644 --- a/crates/aingle_state/src/saf_def.rs +++ b/crates/aingle_state/src/saf_def.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::SafHash; use aingle_sqlite::rusqlite::named_params; use aingle_sqlite::rusqlite::OptionalExtension; diff --git a/crates/aingle_state/src/scratch.rs b/crates/aingle_state/src/scratch.rs index c71e60a9..d54f4346 100644 --- a/crates/aingle_state/src/scratch.rs +++ b/crates/aingle_state/src/scratch.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::collections::HashMap; use std::sync::Arc; use std::sync::Mutex; diff --git a/crates/aingle_state/src/source_chain.rs b/crates/aingle_state/src/source_chain.rs index 42a8428c..bb2759a4 100644 --- a/crates/aingle_state/src/source_chain.rs +++ b/crates/aingle_state/src/source_chain.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::AgentPubKey; use ai_hash::HasHash; use ai_hash::HeaderHash; diff --git a/crates/aingle_state/src/source_chain/error.rs b/crates/aingle_state/src/source_chain/error.rs index 9e7ee823..2af424ca 100644 --- a/crates/aingle_state/src/source_chain/error.rs +++ b/crates/aingle_state/src/source_chain/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + // use crate::aingle::core::workflow::produce_sgd_ops_workflow::sgd_op_light::error::SgdOpConvertError; use ai_hash::EntryHash; use ai_hash::HeaderHash; diff --git a/crates/aingle_state/src/test_utils.rs b/crates/aingle_state/src/test_utils.rs index 03012f31..6bedfd19 100644 --- a/crates/aingle_state/src/test_utils.rs +++ b/crates/aingle_state/src/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Helpers for unit tests use aingle_sqlite::prelude::*; diff --git a/crates/aingle_state/src/test_utils/mutations_helpers.rs b/crates/aingle_state/src/test_utils/mutations_helpers.rs index 5a87335f..30e2984e 100644 --- a/crates/aingle_state/src/test_utils/mutations_helpers.rs +++ b/crates/aingle_state/src/test_utils/mutations_helpers.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::mutations::*; use ai_hash::HasHash; use aingle_sqlite::rusqlite::Transaction; diff --git a/crates/aingle_state/src/validation_db.rs b/crates/aingle_state/src/validation_db.rs index 76453bbd..6d9561ab 100644 --- a/crates/aingle_state/src/validation_db.rs +++ b/crates/aingle_state/src/validation_db.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Validation Database Types use ai_hash::AnySgdHash; diff --git a/crates/aingle_state/src/validation_receipts.rs b/crates/aingle_state/src/validation_receipts.rs index 623e48aa..0d603696 100644 --- a/crates/aingle_state/src/validation_receipts.rs +++ b/crates/aingle_state/src/validation_receipts.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Module for items related to aggregating validation_receipts use ai_hash::AgentPubKey; diff --git a/crates/aingle_state/src/wasm.rs b/crates/aingle_state/src/wasm.rs index 4af1eb40..a326b373 100644 --- a/crates/aingle_state/src/wasm.rs +++ b/crates/aingle_state/src/wasm.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ai_hash::WasmHash; use aingle_sqlite::rusqlite::named_params; use aingle_sqlite::rusqlite::OptionalExtension; diff --git a/crates/aingle_state/src/workspace.rs b/crates/aingle_state/src/workspace.rs index 1fd974cb..c00584c6 100644 --- a/crates/aingle_state/src/workspace.rs +++ b/crates/aingle_state/src/workspace.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Workspaces are a simple abstraction used to stage changes during Workflow //! execution to be persisted later //! diff --git a/crates/aingle_types/Cargo.toml b/crates/aingle_types/Cargo.toml index c81dd4f0..8a36dbd6 100644 --- a/crates/aingle_types/Cargo.toml +++ b/crates/aingle_types/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_types" version = "0.0.1" description = "AIngle common types" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_types" @@ -17,13 +17,13 @@ anyhow = "1.0" async-trait = "0.1" automap = { version = "0.1", features = ["serde"] } backtrace = "0.3.27" -base64 = "0.21" +base64 = "0.22" cfg-if = "0.1" chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } derive_more = "0.99.3" either = "1.5" ai_fixt = { path = "../ai_fixt", version = "0.0.1" } -flate2 = "1.0.14" +flate2 = "1.1.9" futures = "0.3" ai_hash = { version = ">=0.0.1", path = "../ai_hash", features = ["string-encoding"] } aingle_keystore = { version = "0.0.1", path = "../aingle_keystore" } @@ -38,8 +38,8 @@ must_future = "0.1.1" nanoid = "0.4" observability = { version = "0.1", package = "aingle-observability" } rand = "0.9" -regex = "1.4" -rusqlite = { version = "0.25"} +regex = "1.12" +rusqlite = { version = "0.32"} serde = { version = "1.0", features = [ "derive", "rc" ] } serde_bytes = "0.11" serde_derive = "1.0" diff --git a/crates/aingle_types/src/access.rs b/crates/aingle_types/src/access.rs index 5083f272..06f3afe8 100644 --- a/crates/aingle_types/src/access.rs +++ b/crates/aingle_types/src/access.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines HostFnAccess and Permission /// Access a call has to host functions diff --git a/crates/aingle_types/src/activity.rs b/crates/aingle_types/src/activity.rs index 16410b0a..fa4a023a 100644 --- a/crates/aingle_types/src/activity.rs +++ b/crates/aingle_types/src/activity.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for agents chain activity use ai_hash::AgentPubKey; diff --git a/crates/aingle_types/src/app.rs b/crates/aingle_types/src/app.rs index d267b11b..fddd9b5f 100644 --- a/crates/aingle_types/src/app.rs +++ b/crates/aingle_types/src/app.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Everything to do with App (hApp) installation and uninstallation //! //! An App is a essentially a collection of Cells which are intended to be diff --git a/crates/aingle_types/src/app/app_bundle.rs b/crates/aingle_types/src/app/app_bundle.rs index ab06b526..9fd34334 100644 --- a/crates/aingle_types/src/app/app_bundle.rs +++ b/crates/aingle_types/src/app/app_bundle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::{collections::HashMap, path::PathBuf, sync::Arc}; use self::error::AppBundleResult; diff --git a/crates/aingle_types/src/app/app_bundle/error.rs b/crates/aingle_types/src/app/app_bundle/error.rs index e2e667ec..81c31ed3 100644 --- a/crates/aingle_types/src/app/app_bundle/error.rs +++ b/crates/aingle_types/src/app/app_bundle/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_util::ffs; use mr_bundle::error::MrBundleError; diff --git a/crates/aingle_types/src/app/app_bundle/tests.rs b/crates/aingle_types/src/app/app_bundle/tests.rs index a1792819..990a537a 100644 --- a/crates/aingle_types/src/app/app_bundle/tests.rs +++ b/crates/aingle_types/src/app/app_bundle/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::PathBuf; use crate::prelude::*; diff --git a/crates/aingle_types/src/app/app_manifest.rs b/crates/aingle_types/src/app/app_manifest.rs index a1b1379f..d05ec8b3 100644 --- a/crates/aingle_types/src/app/app_manifest.rs +++ b/crates/aingle_types/src/app/app_manifest.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![warn(missing_docs)] //! Defines the hApp Manifest YAML format, including validation. diff --git a/crates/aingle_types/src/app/app_manifest/app_manifest_v1.rs b/crates/aingle_types/src/app/app_manifest/app_manifest_v1.rs index c8e41b16..f7faada5 100644 --- a/crates/aingle_types/src/app/app_manifest/app_manifest_v1.rs +++ b/crates/aingle_types/src/app/app_manifest/app_manifest_v1.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! App Manifest format, version 1. //! //! NB: After stabilization, *do not modify this file*! Create a new version of diff --git a/crates/aingle_types/src/app/app_manifest/app_manifest_validated.rs b/crates/aingle_types/src/app/app_manifest/app_manifest_validated.rs index e3034370..fefac946 100644 --- a/crates/aingle_types/src/app/app_manifest/app_manifest_validated.rs +++ b/crates/aingle_types/src/app/app_manifest/app_manifest_validated.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Normalized, validated representation of the App Manifest. //! //! The versioned manifest structs are designed to be deserialized from YAML, diff --git a/crates/aingle_types/src/app/app_manifest/current.rs b/crates/aingle_types/src/app/app_manifest/current.rs index 432bef62..55973850 100644 --- a/crates/aingle_types/src/app/app_manifest/current.rs +++ b/crates/aingle_types/src/app/app_manifest/current.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Re-export types from the current version. //! Simply adjust this import when using a new version. diff --git a/crates/aingle_types/src/app/app_manifest/error.rs b/crates/aingle_types/src/app/app_manifest/error.rs index 3321da88..3f204d36 100644 --- a/crates/aingle_types/src/app/app_manifest/error.rs +++ b/crates/aingle_types/src/app/app_manifest/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use thiserror::Error; use crate::prelude::CellNick; diff --git a/crates/aingle_types/src/app/error.rs b/crates/aingle_types/src/app/error.rs index 08068f68..f7f27a6a 100644 --- a/crates/aingle_types/src/app/error.rs +++ b/crates/aingle_types/src/app/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use super::AppSlot; diff --git a/crates/aingle_types/src/app/saf_gamut.rs b/crates/aingle_types/src/app/saf_gamut.rs index a8f2dfa4..48fe51f2 100644 --- a/crates/aingle_types/src/app/saf_gamut.rs +++ b/crates/aingle_types/src/app/saf_gamut.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A SAF gamut is a representation of all SAFs available in a given context. use super::SafVersionSpec; diff --git a/crates/aingle_types/src/autonomic.rs b/crates/aingle_types/src/autonomic.rs index a9a74868..3f0f3986 100644 --- a/crates/aingle_types/src/autonomic.rs +++ b/crates/aingle_types/src/autonomic.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle autonomic type helpers. /// The various processes which run "autonomically", aka subconsciously. diff --git a/crates/aingle_types/src/chain.rs b/crates/aingle_types/src/chain.rs index ff153400..b5c83d30 100644 --- a/crates/aingle_types/src/chain.rs +++ b/crates/aingle_types/src/chain.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types related to an agents for chain activity use crate::activity::AgentActivityResponse; use crate::activity::ChainItems; diff --git a/crates/aingle_types/src/db.rs b/crates/aingle_types/src/db.rs index 755113f1..5819c850 100644 --- a/crates/aingle_types/src/db.rs +++ b/crates/aingle_types/src/db.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Utility items related to data persistence. use aingle_zome_types::cell::CellId; diff --git a/crates/aingle_types/src/element.rs b/crates/aingle_types/src/element.rs index fae4f13d..aceac5b0 100644 --- a/crates/aingle_types/src/element.rs +++ b/crates/aingle_types/src/element.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines a Element, the basic unit of AIngle data. use crate::header::WireDelete; diff --git a/crates/aingle_types/src/element/error.rs b/crates/aingle_types/src/element/error.rs index f55cc0b9..6b2e1a81 100644 --- a/crates/aingle_types/src/element/error.rs +++ b/crates/aingle_types/src/element/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use thiserror::Error; #[derive(Error, Debug)] diff --git a/crates/aingle_types/src/entry.rs b/crates/aingle_types/src/entry.rs index 90e6ad50..c1760d77 100644 --- a/crates/aingle_types/src/entry.rs +++ b/crates/aingle_types/src/entry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! An Entry is a unit of data in a AIngle Source Chain. //! //! This module contains all the necessary definitions for Entry, which broadly speaking diff --git a/crates/aingle_types/src/env.rs b/crates/aingle_types/src/env.rs index 84730e7e..81fa7299 100644 --- a/crates/aingle_types/src/env.rs +++ b/crates/aingle_types/src/env.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! An "Env" Combines a database reference with a KeystoreSender use std::path::Path; diff --git a/crates/aingle_types/src/fixt.rs b/crates/aingle_types/src/fixt.rs index a0e8b347..345db44e 100644 --- a/crates/aingle_types/src/fixt.rs +++ b/crates/aingle_types/src/fixt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Fixture definitions for crate structs #![allow(missing_docs)] diff --git a/crates/aingle_types/src/header.rs b/crates/aingle_types/src/header.rs index 0ea8dea2..682b44b1 100644 --- a/crates/aingle_types/src/header.rs +++ b/crates/aingle_types/src/header.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle's [`Header`] and its variations. //! //! All header variations contain the fields `author` and `timestamp`. diff --git a/crates/aingle_types/src/header/error.rs b/crates/aingle_types/src/header/error.rs index a2616d8e..501ce220 100644 --- a/crates/aingle_types/src/header/error.rs +++ b/crates/aingle_types/src/header/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_zome_types::header::conversions::WrongHeaderError; use thiserror::Error; diff --git a/crates/aingle_types/src/lib.rs b/crates/aingle_types/src/lib.rs index f055efba..2c613970 100644 --- a/crates/aingle_types/src/lib.rs +++ b/crates/aingle_types/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Common types used by other AIngle crates. //! //! This crate is a complement to the diff --git a/crates/aingle_types/src/link.rs b/crates/aingle_types/src/link.rs index c0a639c6..918f4d77 100644 --- a/crates/aingle_types/src/link.rs +++ b/crates/aingle_types/src/link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Links interrelate entries in a source chain. use ai_hash::AgentPubKey; diff --git a/crates/aingle_types/src/macros.rs b/crates/aingle_types/src/macros.rs index 90424421..42b26c3f 100644 --- a/crates/aingle_types/src/macros.rs +++ b/crates/aingle_types/src/macros.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! General-purpose macros //! (Consider moving this to its own crate?) diff --git a/crates/aingle_types/src/metadata.rs b/crates/aingle_types/src/metadata.rs index 537e0e10..d010a106 100644 --- a/crates/aingle_types/src/metadata.rs +++ b/crates/aingle_types/src/metadata.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for getting and storing metadata use crate::timestamp; diff --git a/crates/aingle_types/src/prelude.rs b/crates/aingle_types/src/prelude.rs index 1a58c763..a1bd80d4 100644 --- a/crates/aingle_types/src/prelude.rs +++ b/crates/aingle_types/src/prelude.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! reexport some common things #![allow(ambiguous_glob_reexports)] diff --git a/crates/aingle_types/src/properties.rs b/crates/aingle_types/src/properties.rs index 87ca39f0..bbe920cb 100644 --- a/crates/aingle_types/src/properties.rs +++ b/crates/aingle_types/src/properties.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Implements YamlProperties, and potentially any other data types that can //! represent "properties" of a SAF diff --git a/crates/aingle_types/src/saf.rs b/crates/aingle_types/src/saf.rs index f3e3567b..ccaa3f0a 100644 --- a/crates/aingle_types/src/saf.rs +++ b/crates/aingle_types/src/saf.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! saf is a library for working with aingle saf files/entries. //! //! It includes utilities for representing saf structures in memory, diff --git a/crates/aingle_types/src/saf/error.rs b/crates/aingle_types/src/saf/error.rs index 7ffe475b..50d8fb21 100644 --- a/crates/aingle_types/src/saf/error.rs +++ b/crates/aingle_types/src/saf/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle SafError type. use ai_hash::{SafHash, WasmHash}; diff --git a/crates/aingle_types/src/saf/saf_bundle.rs b/crates/aingle_types/src/saf/saf_bundle.rs index 63e958d3..d4aab59c 100644 --- a/crates/aingle_types/src/saf/saf_bundle.rs +++ b/crates/aingle_types/src/saf/saf_bundle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::{ collections::BTreeMap, path::{Path, PathBuf}, diff --git a/crates/aingle_types/src/saf/saf_file.rs b/crates/aingle_types/src/saf/saf_file.rs index 449157d5..68c299dd 100644 --- a/crates/aingle_types/src/saf/saf_file.rs +++ b/crates/aingle_types/src/saf/saf_file.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::error::SafError; use crate::prelude::*; use ai_hash::*; diff --git a/crates/aingle_types/src/saf/saf_manifest.rs b/crates/aingle_types/src/saf/saf_manifest.rs index eeec9688..23b1775b 100644 --- a/crates/aingle_types/src/saf/saf_manifest.rs +++ b/crates/aingle_types/src/saf/saf_manifest.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use std::path::PathBuf; mod saf_manifest_v1; diff --git a/crates/aingle_types/src/saf/saf_manifest/saf_manifest_v1.rs b/crates/aingle_types/src/saf/saf_manifest/saf_manifest_v1.rs index 4727d1a0..b71099cf 100644 --- a/crates/aingle_types/src/saf/saf_manifest/saf_manifest_v1.rs +++ b/crates/aingle_types/src/saf/saf_manifest/saf_manifest_v1.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use ai_hash::*; use aingle_zome_types::ZomeName; diff --git a/crates/aingle_types/src/saf/saf_store.rs b/crates/aingle_types/src/saf/saf_store.rs index 1a8159f2..91f19506 100644 --- a/crates/aingle_types/src/saf/saf_store.rs +++ b/crates/aingle_types/src/saf/saf_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// A readable and writable store of SafFiles and EntryDefs diff --git a/crates/aingle_types/src/saf/wasm.rs b/crates/aingle_types/src/saf/wasm.rs index 423cbce2..bfa69c32 100644 --- a/crates/aingle_types/src/saf/wasm.rs +++ b/crates/aingle_types/src/saf/wasm.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! crate::saf::wasm is a module for managing webassembly code //! - within the in-memory saf struct //! - and serialized to json diff --git a/crates/aingle_types/src/sgd_op.rs b/crates/aingle_types/src/sgd_op.rs index cece17d4..b1ed72a4 100644 --- a/crates/aingle_types/src/sgd_op.rs +++ b/crates/aingle_types/src/sgd_op.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Data structures representing the operations that can be performed within a AIngle SGD. //! //! See the [item-level documentation for `SgdOp`][SgdOp] for more details. diff --git a/crates/aingle_types/src/sgd_op/error.rs b/crates/aingle_types/src/sgd_op/error.rs index 30e4b3df..17a719a2 100644 --- a/crates/aingle_types/src/sgd_op/error.rs +++ b/crates/aingle_types/src/sgd_op/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::SerializedBytesError; use aingle_zome_types::header::conversions::WrongHeaderError; use aingle_zome_types::Header; diff --git a/crates/aingle_types/src/sgd_op/tests.rs b/crates/aingle_types/src/sgd_op/tests.rs index 67a75233..88a18d3c 100644 --- a/crates/aingle_types/src/sgd_op/tests.rs +++ b/crates/aingle_types/src/sgd_op/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::fixt::AgentValidationPkgFixturator; use crate::fixt::CloseChainFixturator; use crate::fixt::CreateFixturator; diff --git a/crates/aingle_types/src/signal.rs b/crates/aingle_types/src/signal.rs index d17ea1cd..3b62393a 100644 --- a/crates/aingle_types/src/signal.rs +++ b/crates/aingle_types/src/signal.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Signals which can be emitted from within AIngle, out across an interface. //! There are two main kinds of Signal: system-defined, and app-defined: //! - App-defined signals are produced via the `emit_signal` host function. diff --git a/crates/aingle_types/src/test_utils.rs b/crates/aingle_types/src/test_utils.rs index fd576cc0..64bb3daf 100644 --- a/crates/aingle_types/src/test_utils.rs +++ b/crates/aingle_types/src/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Some common testing helpers. use crate::element::SignedHeaderHashedExt; diff --git a/crates/aingle_types/src/timestamp.rs b/crates/aingle_types/src/timestamp.rs index 828397d3..c731db33 100644 --- a/crates/aingle_types/src/timestamp.rs +++ b/crates/aingle_types/src/timestamp.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A UTC timestamp for use in AIngle's headers. //! //! Includes a struct that gives a uniform well-ordered byte representation diff --git a/crates/aingle_types/src/validate.rs b/crates/aingle_types/src/validate.rs index aff18181..43aa7440 100644 --- a/crates/aingle_types/src/validate.rs +++ b/crates/aingle_types/src/validate.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! the _host_ types used to track the status/result of validating entries //! c.f. _guest_ types for validation callbacks and packages across the wasm boudary in zome_types diff --git a/crates/aingle_util/Cargo.toml b/crates/aingle_util/Cargo.toml index ced28d6a..190d1d63 100644 --- a/crates/aingle_util/Cargo.toml +++ b/crates/aingle_util/Cargo.toml @@ -4,7 +4,7 @@ version = "0.0.1" authors = ["Apilium Technologies "] edition = "2018" description = "This crate is a collection of various utility functions that are used in the other crates in the aingle repository." -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_util" diff --git a/crates/aingle_util/src/ffs/io_error.rs b/crates/aingle_util/src/ffs/io_error.rs index cf9482d3..a310b191 100644 --- a/crates/aingle_util/src/ffs/io_error.rs +++ b/crates/aingle_util/src/ffs/io_error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #[derive(Debug)] pub struct IoError { pub(crate) original: std::io::Error, diff --git a/crates/aingle_util/src/ffs/mod.rs b/crates/aingle_util/src/ffs/mod.rs index 516759d6..9bc3a579 100644 --- a/crates/aingle_util/src/ffs/mod.rs +++ b/crates/aingle_util/src/ffs/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! ffs - the Friendly Filesystem //! //! Wraps std::fs (or optionally, tokio::fs) in functions with identical diff --git a/crates/aingle_util/src/lib.rs b/crates/aingle_util/src/lib.rs index f1972ced..caaf82da 100644 --- a/crates/aingle_util/src/lib.rs +++ b/crates/aingle_util/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] #![allow(rustdoc::invalid_rust_codeblocks)] diff --git a/crates/aingle_util/src/tokio_helper.rs b/crates/aingle_util/src/tokio_helper.rs index 1b33ea97..f94fb603 100644 --- a/crates/aingle_util/src/tokio_helper.rs +++ b/crates/aingle_util/src/tokio_helper.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use once_cell::sync::Lazy; use tokio::runtime::Runtime; diff --git a/crates/aingle_viz/Cargo.toml b/crates/aingle_viz/Cargo.toml index 659fe1f7..982b3a77 100644 --- a/crates/aingle_viz/Cargo.toml +++ b/crates/aingle_viz/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_viz" -version = "0.2.0" +version = "0.6.3" description = "DAG Visualization for AIngle - Web-based graph explorer" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_viz" @@ -17,7 +17,7 @@ default = [] [dependencies] # Web framework -axum = { version = "0.7", features = ["ws", "macros"] } +axum = { version = "0.8", features = ["ws", "macros"] } tower = { version = "0.5", features = ["util"] } tower-http = { version = "0.6", features = ["cors", "fs", "trace"] } @@ -30,18 +30,18 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" # Graph data -aingle_graph = { version = "0.2", path = "../aingle_graph" } -aingle_minimal = { version = "0.3", path = "../aingle_minimal", default-features = false, features = ["sqlite"] } +aingle_graph = { version = "0.6", path = "../aingle_graph" } +aingle_minimal = { version = "0.6", path = "../aingle_minimal", default-features = false, features = ["sqlite"] } # Utilities log = "0.4" env_logger = "0.11" chrono = { version = "0.4", features = ["serde"] } -uuid = { version = "1.0", features = ["v4", "serde"] } +uuid = { version = "1.21", features = ["v4", "serde"] } thiserror = "2.0" # CLI -clap = { version = "4.0", features = ["derive"] } +clap = { version = "4.5", features = ["derive"] } [dev-dependencies] tokio-test = "0.4" diff --git a/crates/aingle_viz/FEATURES.md b/crates/aingle_viz/FEATURES.md index c3e81e4b..91987432 100644 --- a/crates/aingle_viz/FEATURES.md +++ b/crates/aingle_viz/FEATURES.md @@ -480,7 +480,7 @@ cargo run --release ``` ### 2. Open Browser -Navigate to: `http://localhost:8080` +Navigate to: `http://localhost:3000` ### 3. Try Features - **Click nodes** to see details diff --git a/crates/aingle_viz/INTEGRATION.md b/crates/aingle_viz/INTEGRATION.md index 824991b2..40563310 100644 --- a/crates/aingle_viz/INTEGRATION.md +++ b/crates/aingle_viz/INTEGRATION.md @@ -377,7 +377,7 @@ cargo build --release --bin aingle_viz ```bash export VIZ_HOST=0.0.0.0 # Allow external connections -export VIZ_PORT=8080 # Port number +export VIZ_PORT=3000 # Port number ``` ### HTTPS Setup @@ -393,7 +393,7 @@ server { ssl_certificate_key /path/to/key.pem; location / { - proxy_pass http://localhost:8080; + proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; @@ -414,14 +414,14 @@ RUN cargo build --release --bin aingle_viz FROM debian:bookworm-slim COPY --from=builder /app/target/release/aingle_viz /usr/local/bin/ -EXPOSE 8080 +EXPOSE 3000 CMD ["aingle_viz"] ``` Build and run: ```bash docker build -t aingle-viz . -docker run -p 8080:8080 aingle-viz +docker run -p 3000:3000 aingle-viz ``` ## Support and Troubleshooting diff --git a/crates/aingle_viz/src/api.rs b/crates/aingle_viz/src/api.rs index adb5fc7d..617dece9 100644 --- a/crates/aingle_viz/src/api.rs +++ b/crates/aingle_viz/src/api.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! REST and WebSocket API endpoints for the visualization server. //! //! This module provides the HTTP API layer built on [axum](https://docs.rs/axum). @@ -369,8 +372,8 @@ pub fn create_router(state: ApiState) -> Router { // API endpoints .route("/api/dag", get(get_dag)) .route("/api/dag/d3", get(get_dag_d3)) - .route("/api/dag/entry/:hash", get(get_entry)) - .route("/api/dag/agent/:id", get(get_agent_entries)) + .route("/api/dag/entry/{hash}", get(get_entry)) + .route("/api/dag/agent/{id}", get(get_agent_entries)) .route("/api/dag/recent", get(get_recent)) .route("/api/stats", get(get_stats)) .route("/api/node", post(create_node)) @@ -580,7 +583,7 @@ async fn handle_websocket(socket: WebSocket, state: ApiState) { "type": "initial_state", "data": dag.to_d3_json(), }); - if let Err(e) = sender.send(Message::Text(initial.to_string())).await { + if let Err(e) = sender.send(Message::Text(initial.to_string().into())).await { log::error!("Failed to send initial state to {}: {}", client_id, e); return; } @@ -592,7 +595,7 @@ async fn handle_websocket(socket: WebSocket, state: ApiState) { let send_task = tokio::spawn(async move { while let Ok(event) = event_rx.recv().await { let json = event.to_json(); - if sender.send(Message::Text(json)).await.is_err() { + if sender.send(Message::Text(json.into())).await.is_err() { // Client disconnected. break; } diff --git a/crates/aingle_viz/src/dag.rs b/crates/aingle_viz/src/dag.rs index 3c164647..9f4a1685 100644 --- a/crates/aingle_viz/src/dag.rs +++ b/crates/aingle_viz/src/dag.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Data structures for representing the AIngle DAG in a visualization-friendly format. //! //! This module provides the core types for building and manipulating DAG visualizations. diff --git a/crates/aingle_viz/src/error.rs b/crates/aingle_viz/src/error.rs index 55b2c45a..c0b59615 100644 --- a/crates/aingle_viz/src/error.rs +++ b/crates/aingle_viz/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for the AIngle visualization server. use thiserror::Error; diff --git a/crates/aingle_viz/src/events.rs b/crates/aingle_viz/src/events.rs index 38bd184e..6391e30a 100644 --- a/crates/aingle_viz/src/events.rs +++ b/crates/aingle_viz/src/events.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Real-time event system for broadcasting DAG updates to WebSocket clients. //! //! This module provides the [`EventBroadcaster`] which manages WebSocket connections @@ -283,20 +286,14 @@ impl DagEvent { /// let broadcaster = EventBroadcaster::new(); /// let mut receiver = broadcaster.subscribe(); /// -/// // In another task, events can be broadcast -/// let bc = broadcaster.clone(); -/// tokio::spawn(async move { -/// bc.broadcast(DagEvent::ping()).await; -/// }); +/// // Broadcast an event +/// broadcaster.broadcast(DagEvent::ping()).await; /// -/// // Receive events -/// while let Ok(event) = receiver.recv().await { -/// match event { -/// DagEvent::Ping { timestamp } => { -/// println!("Received ping at {}", timestamp); -/// } -/// _ => {} -/// } +/// // Receive the event +/// let event = receiver.recv().await.unwrap(); +/// match event { +/// DagEvent::Ping { .. } => println!("Received ping"), +/// _ => {} /// } /// } /// ``` diff --git a/crates/aingle_viz/src/lib.rs b/crates/aingle_viz/src/lib.rs index 698a9cbf..afc99503 100644 --- a/crates/aingle_viz/src/lib.rs +++ b/crates/aingle_viz/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![doc = include_str!("../README.md")] //! # AIngle Visualization - DAG Explorer //! @@ -164,7 +167,7 @@ pub mod server; pub use api::ApiState; pub use dag::{DagEdge, DagNode, DagNodeBuilder, DagStats, DagView, EdgeType, NodeType}; pub use error::{Error, Result}; -pub use events::EventBroadcaster; +pub use events::{DagEvent, EventBroadcaster}; pub use server::{VizConfig, VizServer}; /// Version information from Cargo.toml. diff --git a/crates/aingle_viz/src/main.rs b/crates/aingle_viz/src/main.rs index c65c7d2d..be750dd5 100644 --- a/crates/aingle_viz/src/main.rs +++ b/crates/aingle_viz/src/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle Viz - DAG Visualization Server //! //! A standalone web server for visualizing AIngle DAG structures. @@ -9,8 +12,13 @@ use clap::Parser; /// AIngle DAG Visualization Server #[derive(Parser, Debug)] #[command(name = "aingle-viz")] -#[command(author = "AIngle Core Dev Team")] -#[command(version)] +#[command(author = "Apilium Technologies OÜ ")] +#[command(version, long_version = concat!( + env!("CARGO_PKG_VERSION"), "\n", + "Copyright 2019-2026 Apilium Technologies OÜ\n", + "License: Apache-2.0 OR Commercial\n", + "https://github.com/ApiliumCode/aingle" +))] #[command(about = "Web-based DAG visualization for AIngle", long_about = None)] struct Args { /// Host to bind to diff --git a/crates/aingle_viz/src/server.rs b/crates/aingle_viz/src/server.rs index 8b904dab..25e6418d 100644 --- a/crates/aingle_viz/src/server.rs +++ b/crates/aingle_viz/src/server.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! HTTP and WebSocket server for the DAG visualization. //! //! This module provides the main [`VizServer`] that serves the web UI and the REST/WebSocket API diff --git a/crates/aingle_wal/Cargo.toml b/crates/aingle_wal/Cargo.toml new file mode 100644 index 00000000..05d31a95 --- /dev/null +++ b/crates/aingle_wal/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "aingle_wal" +version = "0.6.3" +description = "Write-Ahead Log for AIngle clustering and replication" +license = "Apache-2.0 OR LicenseRef-Commercial" +repository = "https://github.com/ApiliumCode/aingle" +homepage = "https://apilium.com" +documentation = "https://docs.rs/aingle_wal" +authors = ["Apilium Technologies "] +keywords = ["aingle", "wal", "replication", "clustering"] +categories = ["database"] +edition = "2021" +rust-version = "1.83" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +bincode = "2" +blake3 = "1.8" +chrono = { version = "0.4", features = ["serde"] } + +[dev-dependencies] +tempfile = "3.26" diff --git a/crates/aingle_wal/src/entry.rs b/crates/aingle_wal/src/entry.rs new file mode 100644 index 00000000..bb35235a --- /dev/null +++ b/crates/aingle_wal/src/entry.rs @@ -0,0 +1,149 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! WAL entry types and serialization. + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +/// A single WAL entry representing one mutation. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct WalEntry { + /// Monotonically increasing sequence number. + pub seq: u64, + /// Wall-clock timestamp (UTC). + pub timestamp: DateTime, + /// The mutation kind. + pub kind: WalEntryKind, + /// blake3 hash of the previous entry (chain integrity). + pub prev_hash: [u8; 32], + /// blake3 hash of this entry's payload. + pub hash: [u8; 32], +} + +impl WalEntry { + /// Compute the hash for this entry's payload (kind + seq + timestamp + prev_hash). + pub fn compute_hash(seq: u64, timestamp: &DateTime, kind: &WalEntryKind, prev_hash: &[u8; 32]) -> [u8; 32] { + let mut hasher = blake3::Hasher::new(); + hasher.update(&seq.to_le_bytes()); + hasher.update(timestamp.to_rfc3339().as_bytes()); + // Hash the serialized kind + if let Ok(kind_bytes) = serde_json::to_vec(kind) { + hasher.update(&kind_bytes); + } + hasher.update(prev_hash); + *hasher.finalize().as_bytes() + } +} + +/// The kind of mutation recorded in a WAL entry. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum WalEntryKind { + /// Triple inserted into GraphDB. + TripleInsert { + subject: String, + predicate: String, + object: serde_json::Value, + triple_id: [u8; 32], + }, + /// Triple deleted from GraphDB. + TripleDelete { + triple_id: [u8; 32], + }, + /// Memory entry stored in Ineru STM. + MemoryStore { + memory_id: String, + entry_type: String, + data: serde_json::Value, + importance: f32, + }, + /// Memory entry forgotten. + MemoryForget { + memory_id: String, + }, + /// STM → LTM consolidation occurred. + MemoryConsolidate { + consolidated_count: usize, + }, + /// Proof submitted. + ProofSubmit { + proof_id: String, + proof_type: String, + }, + /// Snapshot checkpoint marker. + Checkpoint { + graph_triple_count: usize, + ineru_stm_count: usize, + ineru_ltm_entity_count: usize, + }, + /// LTM entity created (for Ineru replication). + LtmEntityCreate { + entity_id: String, + name: String, + entity_type: String, + }, + /// LTM link created (for Ineru replication). + LtmLinkCreate { + from_entity: String, + to_entity: String, + relation: String, + weight: f32, + }, + /// LTM entity deleted (for Ineru replication). + LtmEntityDelete { + entity_id: String, + }, + /// Serialized openraft Raft log entry. + RaftEntry { + index: u64, + term: u64, + data: Vec, + }, + /// DAG action (serialized bytes to avoid circular deps with aingle_graph). + DagAction { + /// Serialized DagAction bytes (JSON). + action_bytes: Vec, + }, + /// No-op entry for linearizable reads. + Noop, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_entry_kind_serialization() { + let kind = WalEntryKind::TripleInsert { + subject: "alice".into(), + predicate: "knows".into(), + object: serde_json::json!("bob"), + triple_id: [0u8; 32], + }; + let json = serde_json::to_string(&kind).unwrap(); + let back: WalEntryKind = serde_json::from_str(&json).unwrap(); + assert!(matches!(back, WalEntryKind::TripleInsert { .. })); + } + + #[test] + fn test_compute_hash_deterministic() { + let ts = Utc::now(); + let kind = WalEntryKind::TripleDelete { triple_id: [1u8; 32] }; + let prev = [0u8; 32]; + + let h1 = WalEntry::compute_hash(1, &ts, &kind, &prev); + let h2 = WalEntry::compute_hash(1, &ts, &kind, &prev); + assert_eq!(h1, h2); + } + + #[test] + fn test_compute_hash_differs_on_seq() { + let ts = Utc::now(); + let kind = WalEntryKind::TripleDelete { triple_id: [1u8; 32] }; + let prev = [0u8; 32]; + + let h1 = WalEntry::compute_hash(1, &ts, &kind, &prev); + let h2 = WalEntry::compute_hash(2, &ts, &kind, &prev); + assert_ne!(h1, h2); + } +} diff --git a/crates/aingle_wal/src/lib.rs b/crates/aingle_wal/src/lib.rs new file mode 100644 index 00000000..2c1b02cb --- /dev/null +++ b/crates/aingle_wal/src/lib.rs @@ -0,0 +1,17 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Write-Ahead Log (WAL) for AIngle clustering and replication. +//! +//! Provides a durable, ordered log of all mutations before they hit +//! the graph/memory store. Used as the foundation for Raft consensus +//! log replication. + +pub mod entry; +pub mod reader; +pub mod segment; +pub mod writer; + +pub use entry::{WalEntry, WalEntryKind}; +pub use reader::{VerifyResult, WalReader}; +pub use writer::WalWriter; diff --git a/crates/aingle_wal/src/reader.rs b/crates/aingle_wal/src/reader.rs new file mode 100644 index 00000000..472092ba --- /dev/null +++ b/crates/aingle_wal/src/reader.rs @@ -0,0 +1,270 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! WAL reader for replay and replication. + +use crate::entry::{WalEntry, WalEntryKind}; +use crate::segment; +use std::io; +use std::path::{Path, PathBuf}; + +/// WAL reader for replay and replication. +pub struct WalReader { + dir: PathBuf, +} + +impl WalReader { + /// Open a WAL directory for reading. + pub fn open(dir: &Path) -> io::Result { + if !dir.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + format!("WAL directory not found: {}", dir.display()), + )); + } + Ok(Self { + dir: dir.to_path_buf(), + }) + } + + /// Read all entries from `start_seq` onwards. + pub fn read_from(&self, start_seq: u64) -> io::Result> { + let segments = segment::list_segments(&self.dir)?; + let mut result = Vec::new(); + + for seg_path in &segments { + let entries = segment::read_entries_from_path(seg_path)?; + for entry in entries { + if entry.seq >= start_seq { + result.push(entry); + } + } + } + + result.sort_by_key(|e| e.seq); + Ok(result) + } + + /// Stream entries from `start_seq` as a Vec (for iteration). + pub fn stream_from(&self, start_seq: u64) -> io::Result> { + self.read_from(start_seq) + } + + /// Verify hash chain integrity across all segments. + pub fn verify_integrity(&self) -> io::Result { + let entries = self.read_from(0)?; + + if entries.is_empty() { + return Ok(VerifyResult { + valid: true, + entries_checked: 0, + first_invalid_seq: None, + }); + } + + // Verify first entry's prev_hash is zeros + if entries[0].prev_hash != [0u8; 32] { + return Ok(VerifyResult { + valid: false, + entries_checked: 1, + first_invalid_seq: Some(entries[0].seq), + }); + } + + // Verify hash chain + for i in 0..entries.len() { + let entry = &entries[i]; + + // Verify this entry's hash + let expected_hash = WalEntry::compute_hash( + entry.seq, + &entry.timestamp, + &entry.kind, + &entry.prev_hash, + ); + if entry.hash != expected_hash { + return Ok(VerifyResult { + valid: false, + entries_checked: i as u64 + 1, + first_invalid_seq: Some(entry.seq), + }); + } + + // Verify chain link + if i > 0 && entry.prev_hash != entries[i - 1].hash { + return Ok(VerifyResult { + valid: false, + entries_checked: i as u64 + 1, + first_invalid_seq: Some(entry.seq), + }); + } + } + + Ok(VerifyResult { + valid: true, + entries_checked: entries.len() as u64, + first_invalid_seq: None, + }) + } + + /// Find the last checkpoint entry. + pub fn last_checkpoint(&self) -> io::Result> { + let entries = self.read_from(0)?; + Ok(entries + .into_iter() + .rev() + .find(|e| matches!(e.kind, WalEntryKind::Checkpoint { .. }))) + } + + /// Count total entries across all segments. + pub fn entry_count(&self) -> io::Result { + let entries = self.read_from(0)?; + Ok(entries.len() as u64) + } +} + +/// Result of WAL integrity verification. +#[derive(Debug, Clone)] +pub struct VerifyResult { + /// Whether the entire WAL is valid. + pub valid: bool, + /// Number of entries checked. + pub entries_checked: u64, + /// First invalid sequence number, if any. + pub first_invalid_seq: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::writer::WalWriter; + + #[test] + fn test_reader_read_from() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + for i in 0..5 { + writer + .append(WalEntryKind::TripleInsert { + subject: format!("s{}", i), + predicate: "p".into(), + object: serde_json::json!(i), + triple_id: [i as u8; 32], + }) + .unwrap(); + } + + let reader = WalReader::open(dir.path()).unwrap(); + let entries = reader.read_from(2).unwrap(); + assert_eq!(entries.len(), 3); + assert_eq!(entries[0].seq, 2); + } + + #[test] + fn test_reader_verify_integrity() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + for i in 0..10 { + writer + .append(WalEntryKind::TripleInsert { + subject: format!("s{}", i), + predicate: "p".into(), + object: serde_json::json!(i), + triple_id: [i as u8; 32], + }) + .unwrap(); + } + + let reader = WalReader::open(dir.path()).unwrap(); + let result = reader.verify_integrity().unwrap(); + assert!(result.valid); + assert_eq!(result.entries_checked, 10); + assert!(result.first_invalid_seq.is_none()); + } + + #[test] + fn test_reader_empty_wal() { + let dir = tempfile::tempdir().unwrap(); + // Create an empty WAL directory + let _ = WalWriter::open(dir.path()).unwrap(); + + let reader = WalReader::open(dir.path()).unwrap(); + let result = reader.verify_integrity().unwrap(); + assert!(result.valid); + assert_eq!(result.entries_checked, 0); + } + + #[test] + fn test_reader_last_checkpoint() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + writer + .append(WalEntryKind::TripleInsert { + subject: "a".into(), + predicate: "b".into(), + object: serde_json::json!("c"), + triple_id: [0u8; 32], + }) + .unwrap(); + + writer.checkpoint(10, 5, 3).unwrap(); + + writer + .append(WalEntryKind::TripleDelete { + triple_id: [1u8; 32], + }) + .unwrap(); + + let reader = WalReader::open(dir.path()).unwrap(); + let cp = reader.last_checkpoint().unwrap(); + assert!(cp.is_some()); + assert!(matches!( + cp.unwrap().kind, + WalEntryKind::Checkpoint { + graph_triple_count: 10, + .. + } + )); + } + + #[test] + fn test_reader_stream_from() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + for i in 0..3 { + writer + .append(WalEntryKind::MemoryStore { + memory_id: format!("m{}", i), + entry_type: "test".into(), + data: serde_json::json!({"n": i}), + importance: 0.5, + }) + .unwrap(); + } + + let reader = WalReader::open(dir.path()).unwrap(); + let entries = reader.stream_from(0).unwrap(); + assert_eq!(entries.len(), 3); + } + + #[test] + fn test_reader_entry_count() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + for i in 0..7 { + writer + .append(WalEntryKind::TripleDelete { + triple_id: [i as u8; 32], + }) + .unwrap(); + } + + let reader = WalReader::open(dir.path()).unwrap(); + assert_eq!(reader.entry_count().unwrap(), 7); + } +} diff --git a/crates/aingle_wal/src/segment.rs b/crates/aingle_wal/src/segment.rs new file mode 100644 index 00000000..b5bcd1e8 --- /dev/null +++ b/crates/aingle_wal/src/segment.rs @@ -0,0 +1,283 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! WAL segment file management. +//! +//! WAL is split into segment files of configurable max size. +//! Format per segment: sequence of `[4-byte len][bincode payload]` entries. +//! Filename: `wal-{first_seq:016}.seg` + +use crate::entry::WalEntry; +use std::fs::{File, OpenOptions}; +use std::io::{self, BufReader, BufWriter, Read, Write}; +use std::path::{Path, PathBuf}; + +/// Default maximum segment size: 64 MB. +pub const DEFAULT_MAX_SEGMENT_SIZE: u64 = 64 * 1024 * 1024; + +/// A single WAL segment file. +pub struct WalSegment { + path: PathBuf, + file: BufWriter, + first_seq: u64, + last_seq: u64, + size_bytes: u64, +} + +impl WalSegment { + /// Create a new segment file. + pub fn create(dir: &Path, first_seq: u64) -> io::Result { + let filename = format!("wal-{:016}.seg", first_seq); + let path = dir.join(filename); + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&path)?; + Ok(Self { + path, + file: BufWriter::new(file), + first_seq, + last_seq: first_seq, + size_bytes: 0, + }) + } + + /// Open an existing segment file for appending. + pub fn open(path: &Path) -> io::Result { + // Parse first_seq from filename + let filename = path + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid segment path"))?; + + let first_seq = filename + .strip_prefix("wal-") + .and_then(|s| s.strip_suffix(".seg")) + .and_then(|s| s.parse::().ok()) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid segment filename"))?; + + let file = OpenOptions::new() + .create(true) + .append(true) + .open(path)?; + + let size_bytes = file.metadata()?.len(); + + // Read all entries to find last_seq + let mut last_seq = first_seq; + if size_bytes > 0 { + let entries = read_entries_from_path(path)?; + if let Some(last) = entries.last() { + last_seq = last.seq; + } + } + + Ok(Self { + path: path.to_path_buf(), + file: BufWriter::new(file), + first_seq, + last_seq, + size_bytes, + }) + } + + /// Append a WAL entry to the segment. + pub fn append(&mut self, entry: &WalEntry) -> io::Result<()> { + let payload = serde_json::to_vec(entry) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let len = payload.len() as u32; + self.file.write_all(&len.to_be_bytes())?; + self.file.write_all(&payload)?; + self.size_bytes += 4 + payload.len() as u64; + self.last_seq = entry.seq; + Ok(()) + } + + /// Flush and fsync the segment to disk. + pub fn sync(&mut self) -> io::Result<()> { + self.file.flush()?; + self.file.get_ref().sync_all() + } + + /// Iterate over all entries in this segment. + pub fn iter(&self) -> io::Result> { + read_entries_from_path(&self.path) + } + + /// Current size of the segment file in bytes. + pub fn size(&self) -> u64 { + self.size_bytes + } + + /// The first sequence number in this segment. + pub fn first_seq(&self) -> u64 { + self.first_seq + } + + /// The last sequence number written to this segment. + pub fn last_seq(&self) -> u64 { + self.last_seq + } + + /// Path to the segment file. + pub fn path(&self) -> &Path { + &self.path + } +} + +/// Read all entries from a segment file. +pub fn read_entries_from_path(path: &Path) -> io::Result> { + let file = File::open(path)?; + let file_len = file.metadata()?.len(); + if file_len == 0 { + return Ok(Vec::new()); + } + let mut reader = BufReader::new(file); + let mut entries = Vec::new(); + let mut pos = 0u64; + + loop { + if pos >= file_len { + break; + } + + let mut len_buf = [0u8; 4]; + match reader.read_exact(&mut len_buf) { + Ok(()) => {} + Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => break, + Err(e) => return Err(e), + } + let len = u32::from_be_bytes(len_buf) as usize; + pos += 4; + + let mut payload = vec![0u8; len]; + reader.read_exact(&mut payload)?; + pos += len as u64; + + let entry: WalEntry = serde_json::from_slice(&payload) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + entries.push(entry); + } + + Ok(entries) +} + +/// List all segment files in a directory, sorted by first_seq. +pub fn list_segments(dir: &Path) -> io::Result> { + let mut segments: Vec = std::fs::read_dir(dir)? + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|p| { + p.extension().map(|e| e == "seg").unwrap_or(false) + && p.file_name() + .and_then(|n| n.to_str()) + .map(|n| n.starts_with("wal-")) + .unwrap_or(false) + }) + .collect(); + segments.sort(); + Ok(segments) +} + +/// Parse the first_seq from a segment filename. +pub fn parse_segment_seq(path: &Path) -> Option { + path.file_name() + .and_then(|n| n.to_str()) + .and_then(|s| s.strip_prefix("wal-")) + .and_then(|s| s.strip_suffix(".seg")) + .and_then(|s| s.parse().ok()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::entry::{WalEntry, WalEntryKind}; + use chrono::Utc; + + fn make_entry(seq: u64) -> WalEntry { + let kind = WalEntryKind::TripleInsert { + subject: format!("s{}", seq), + predicate: "p".into(), + object: serde_json::json!("o"), + triple_id: [seq as u8; 32], + }; + let prev_hash = [0u8; 32]; + let ts = Utc::now(); + let hash = WalEntry::compute_hash(seq, &ts, &kind, &prev_hash); + WalEntry { + seq, + timestamp: ts, + kind, + prev_hash, + hash, + } + } + + #[test] + fn test_segment_create_append_iter() { + let dir = tempfile::tempdir().unwrap(); + let mut seg = WalSegment::create(dir.path(), 0).unwrap(); + + for i in 0..5 { + seg.append(&make_entry(i)).unwrap(); + } + seg.sync().unwrap(); + + let entries = seg.iter().unwrap(); + assert_eq!(entries.len(), 5); + assert_eq!(entries[0].seq, 0); + assert_eq!(entries[4].seq, 4); + } + + #[test] + fn test_segment_open() { + let dir = tempfile::tempdir().unwrap(); + + // Create and write + { + let mut seg = WalSegment::create(dir.path(), 10).unwrap(); + seg.append(&make_entry(10)).unwrap(); + seg.append(&make_entry(11)).unwrap(); + seg.sync().unwrap(); + } + + // Re-open + let path = dir.path().join("wal-0000000000000010.seg"); + let seg = WalSegment::open(&path).unwrap(); + assert_eq!(seg.first_seq(), 10); + assert_eq!(seg.last_seq(), 11); + } + + #[test] + fn test_segment_size_limit() { + let dir = tempfile::tempdir().unwrap(); + let mut seg = WalSegment::create(dir.path(), 0).unwrap(); + + seg.append(&make_entry(0)).unwrap(); + seg.sync().unwrap(); + + assert!(seg.size() > 0); + } + + #[test] + fn test_list_segments() { + let dir = tempfile::tempdir().unwrap(); + + // Create multiple segments + for first in [0, 100, 200] { + let mut seg = WalSegment::create(dir.path(), first).unwrap(); + seg.append(&make_entry(first)).unwrap(); + seg.sync().unwrap(); + } + + let segments = list_segments(dir.path()).unwrap(); + assert_eq!(segments.len(), 3); + } + + #[test] + fn test_parse_segment_seq() { + let path = PathBuf::from("wal-0000000000000042.seg"); + assert_eq!(parse_segment_seq(&path), Some(42)); + } +} diff --git a/crates/aingle_wal/src/writer.rs b/crates/aingle_wal/src/writer.rs new file mode 100644 index 00000000..6440a4dc --- /dev/null +++ b/crates/aingle_wal/src/writer.rs @@ -0,0 +1,327 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Thread-safe WAL writer. +//! +//! All writes are serialized through a Mutex. Each write: +//! 1. Assigns next seq number +//! 2. Computes hash chain (prev_hash from last entry) +//! 3. Appends to current segment +//! 4. Calls fsync +//! 5. Rotates segment if size exceeds threshold + +use crate::entry::{WalEntry, WalEntryKind}; +use crate::segment::{self, WalSegment, DEFAULT_MAX_SEGMENT_SIZE}; +use chrono::Utc; +use std::io; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Mutex; + +/// Thread-safe WAL writer with hash chain integrity and segment rotation. +pub struct WalWriter { + dir: PathBuf, + current_segment: Mutex, + next_seq: AtomicU64, + last_hash: Mutex<[u8; 32]>, + max_segment_size: u64, +} + +impl WalWriter { + /// Open or create a WAL in the given directory. + pub fn open(dir: &Path) -> io::Result { + std::fs::create_dir_all(dir)?; + + let segments = segment::list_segments(dir)?; + + if segments.is_empty() { + // Fresh WAL + let seg = WalSegment::create(dir, 0)?; + return Ok(Self { + dir: dir.to_path_buf(), + current_segment: Mutex::new(seg), + next_seq: AtomicU64::new(0), + last_hash: Mutex::new([0u8; 32]), + max_segment_size: DEFAULT_MAX_SEGMENT_SIZE, + }); + } + + // Open the last segment + let last_path = segments.last().unwrap(); + let seg = WalSegment::open(last_path)?; + + // Find the last entry to restore state + let entries = seg.iter()?; + let (next_seq, last_hash) = if let Some(last) = entries.last() { + (last.seq + 1, last.hash) + } else { + (seg.first_seq(), [0u8; 32]) + }; + + Ok(Self { + dir: dir.to_path_buf(), + current_segment: Mutex::new(seg), + next_seq: AtomicU64::new(next_seq), + last_hash: Mutex::new(last_hash), + max_segment_size: DEFAULT_MAX_SEGMENT_SIZE, + }) + } + + /// Append a mutation to the WAL. + pub fn append(&self, kind: WalEntryKind) -> io::Result { + let seq = self.next_seq.fetch_add(1, Ordering::SeqCst); + let timestamp = Utc::now(); + + let prev_hash = { + let guard = self.last_hash.lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("WAL last_hash lock poisoned: {e}")))?; + *guard + }; + + let hash = WalEntry::compute_hash(seq, ×tamp, &kind, &prev_hash); + + let entry = WalEntry { + seq, + timestamp, + kind, + prev_hash, + hash, + }; + + { + let mut seg = self.current_segment.lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("WAL segment lock poisoned: {e}")))?; + seg.append(&entry)?; + seg.sync()?; + + // Rotate if needed + if seg.size() >= self.max_segment_size { + let new_seq = self.next_seq.load(Ordering::SeqCst); + let new_seg = WalSegment::create(&self.dir, new_seq)?; + *seg = new_seg; + } + } + + // Update last_hash + { + let mut guard = self.last_hash.lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("WAL last_hash lock poisoned: {e}")))?; + *guard = entry.hash; + } + + Ok(entry) + } + + /// Flush the current segment to disk. + pub fn sync(&self) -> io::Result<()> { + let mut seg = self.current_segment.lock() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("WAL segment lock poisoned: {e}")))?; + seg.sync() + } + + /// The next sequence number that will be assigned. + pub fn last_seq(&self) -> u64 { + let next = self.next_seq.load(Ordering::SeqCst); + if next == 0 { 0 } else { next - 1 } + } + + /// Get the WAL directory path. + pub fn dir(&self) -> &Path { + &self.dir + } + + /// Write a checkpoint entry. + pub fn checkpoint( + &self, + graph_triple_count: usize, + ineru_stm_count: usize, + ineru_ltm_entity_count: usize, + ) -> io::Result { + self.append(WalEntryKind::Checkpoint { + graph_triple_count, + ineru_stm_count, + ineru_ltm_entity_count, + }) + } + + /// Truncate WAL entries before `seq` by removing old segment files. + pub fn truncate_before(&self, seq: u64) -> io::Result { + let segments = segment::list_segments(&self.dir)?; + let mut removed = 0; + + for seg_path in &segments { + if segment::parse_segment_seq(seg_path).is_some() { + // Only remove segments whose entries are all before `seq` + let entries = segment::read_entries_from_path(seg_path)?; + if let Some(last) = entries.last() { + if last.seq < seq { + std::fs::remove_file(seg_path)?; + removed += 1; + } + } + } + } + + Ok(removed) + } + + /// Get WAL statistics. + pub fn stats(&self) -> io::Result { + let segments = segment::list_segments(&self.dir)?; + let total_size: u64 = segments + .iter() + .filter_map(|p| std::fs::metadata(p).ok()) + .map(|m| m.len()) + .sum(); + + Ok(WalStats { + segment_count: segments.len(), + total_size_bytes: total_size, + last_seq: self.last_seq(), + next_seq: self.next_seq.load(Ordering::SeqCst), + }) + } +} + +/// WAL statistics. +#[derive(Debug, Clone)] +pub struct WalStats { + pub segment_count: usize, + pub total_size_bytes: u64, + pub last_seq: u64, + pub next_seq: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_writer_append_and_seq() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + let e1 = writer + .append(WalEntryKind::TripleInsert { + subject: "a".into(), + predicate: "b".into(), + object: serde_json::json!("c"), + triple_id: [0u8; 32], + }) + .unwrap(); + assert_eq!(e1.seq, 0); + + let e2 = writer + .append(WalEntryKind::TripleDelete { + triple_id: [1u8; 32], + }) + .unwrap(); + assert_eq!(e2.seq, 1); + assert_eq!(e2.prev_hash, e1.hash); + } + + #[test] + fn test_writer_reopen() { + let dir = tempfile::tempdir().unwrap(); + + // Write some entries + { + let writer = WalWriter::open(dir.path()).unwrap(); + for i in 0..3 { + writer + .append(WalEntryKind::TripleInsert { + subject: format!("s{}", i), + predicate: "p".into(), + object: serde_json::json!("o"), + triple_id: [i as u8; 32], + }) + .unwrap(); + } + } + + // Reopen and continue + let writer = WalWriter::open(dir.path()).unwrap(); + assert_eq!(writer.last_seq(), 2); + + let e = writer + .append(WalEntryKind::TripleDelete { + triple_id: [99u8; 32], + }) + .unwrap(); + assert_eq!(e.seq, 3); + } + + #[test] + fn test_hash_chain_integrity() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + let mut entries = Vec::new(); + for i in 0..5 { + let e = writer + .append(WalEntryKind::TripleInsert { + subject: format!("s{}", i), + predicate: "p".into(), + object: serde_json::json!(i), + triple_id: [i as u8; 32], + }) + .unwrap(); + entries.push(e); + } + + // Verify chain + for i in 1..entries.len() { + assert_eq!(entries[i].prev_hash, entries[i - 1].hash); + } + } + + #[test] + fn test_checkpoint() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + let cp = writer.checkpoint(100, 50, 25).unwrap(); + assert!(matches!(cp.kind, WalEntryKind::Checkpoint { .. })); + } + + #[test] + fn test_stats() { + let dir = tempfile::tempdir().unwrap(); + let writer = WalWriter::open(dir.path()).unwrap(); + + writer + .append(WalEntryKind::TripleDelete { + triple_id: [0u8; 32], + }) + .unwrap(); + + let stats = writer.stats().unwrap(); + assert_eq!(stats.segment_count, 1); + assert!(stats.total_size_bytes > 0); + } + + #[test] + fn test_truncate_before() { + let dir = tempfile::tempdir().unwrap(); + + // Create first segment with entries 0-2 + { + let writer = WalWriter::open(dir.path()).unwrap(); + for i in 0..3 { + writer + .append(WalEntryKind::TripleInsert { + subject: format!("s{}", i), + predicate: "p".into(), + object: serde_json::json!(i), + triple_id: [i as u8; 32], + }) + .unwrap(); + } + } + + // Truncate shouldn't remove the only segment since entries aren't all < seq + let writer = WalWriter::open(dir.path()).unwrap(); + let removed = writer.truncate_before(1).unwrap(); + assert_eq!(removed, 0); // segment has entries 0,1,2 — last (2) >= 1 + } +} diff --git a/crates/aingle_websocket/Cargo.toml b/crates/aingle_websocket/Cargo.toml index 196ba075..1c9d02a9 100644 --- a/crates/aingle_websocket/Cargo.toml +++ b/crates/aingle_websocket/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_websocket" version = "0.0.1" description = "AIngle utilities for serving and connection with websockets" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_websocket" diff --git a/crates/aingle_websocket/benches/bench.rs b/crates/aingle_websocket/benches/bench.rs index 84b0d3b8..6d41467d 100644 --- a/crates/aingle_websocket/benches/bench.rs +++ b/crates/aingle_websocket/benches/bench.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use criterion::criterion_group; use criterion::criterion_main; use criterion::BenchmarkId; diff --git a/crates/aingle_websocket/examples/docs.rs b/crates/aingle_websocket/examples/docs.rs index a90ca52d..9fe5ffbb 100644 --- a/crates/aingle_websocket/examples/docs.rs +++ b/crates/aingle_websocket/examples/docs.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; use aingle_websocket::*; use std::time::Duration; diff --git a/crates/aingle_websocket/examples/echo.rs b/crates/aingle_websocket/examples/echo.rs index 0b902517..02e38f63 100644 --- a/crates/aingle_websocket/examples/echo.rs +++ b/crates/aingle_websocket/examples/echo.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; use aingle_websocket::*; diff --git a/crates/aingle_websocket/examples/echo_client.rs b/crates/aingle_websocket/examples/echo_client.rs index 613368ff..8394aac3 100644 --- a/crates/aingle_websocket/examples/echo_client.rs +++ b/crates/aingle_websocket/examples/echo_client.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; use aingle_websocket::*; use std::convert::TryInto; diff --git a/crates/aingle_websocket/examples/echo_server.rs b/crates/aingle_websocket/examples/echo_server.rs index 156be134..90bfcada 100644 --- a/crates/aingle_websocket/examples/echo_server.rs +++ b/crates/aingle_websocket/examples/echo_server.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; use aingle_websocket::*; use std::convert::TryInto; diff --git a/crates/aingle_websocket/src/error.rs b/crates/aingle_websocket/src/error.rs index efe31110..0b4f1e45 100644 --- a/crates/aingle_websocket/src/error.rs +++ b/crates/aingle_websocket/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::SerializedBytesError; use ghost_actor::GhostError; diff --git a/crates/aingle_websocket/src/lib.rs b/crates/aingle_websocket/src/lib.rs index 51e07857..05ff22f7 100644 --- a/crates/aingle_websocket/src/lib.rs +++ b/crates/aingle_websocket/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! AIngle utilities for websocket serving and connecting. //! diff --git a/crates/aingle_websocket/src/simple_actor.rs b/crates/aingle_websocket/src/simple_actor.rs index 3e5229dd..2e9909e7 100644 --- a/crates/aingle_websocket/src/simple_actor.rs +++ b/crates/aingle_websocket/src/simple_actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Simple actor implementation to replace the missing GhostActor from ghost_actor crate. //! //! This provides a minimal actor implementation that: diff --git a/crates/aingle_websocket/src/util.rs b/crates/aingle_websocket/src/util.rs index ca27a998..ea4f15e7 100644 --- a/crates/aingle_websocket/src/util.rs +++ b/crates/aingle_websocket/src/util.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! internal websocket utility types and code use std::net::SocketAddr; diff --git a/crates/aingle_websocket/src/websocket.rs b/crates/aingle_websocket/src/websocket.rs index d1783bb9..214b1c12 100644 --- a/crates/aingle_websocket/src/websocket.rs +++ b/crates/aingle_websocket/src/websocket.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::collections::HashMap; use std::convert::TryInto; use std::sync::Arc; diff --git a/crates/aingle_websocket/src/websocket_config.rs b/crates/aingle_websocket/src/websocket_config.rs index e2a07a9d..47c0c82b 100644 --- a/crates/aingle_websocket/src/websocket_config.rs +++ b/crates/aingle_websocket/src/websocket_config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! defines a builder-style config struct for setting up websockets /// A builder-style config struct for setting up websockets. diff --git a/crates/aingle_websocket/src/websocket_listener.rs b/crates/aingle_websocket/src/websocket_listener.rs index 7bbbbc21..f2e3c907 100644 --- a/crates/aingle_websocket/src/websocket_listener.rs +++ b/crates/aingle_websocket/src/websocket_listener.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::BoxStream; use futures::StreamExt; use futures::TryStreamExt; diff --git a/crates/aingle_websocket/src/websocket_receiver.rs b/crates/aingle_websocket/src/websocket_receiver.rs index 54954228..9e7d1b3b 100644 --- a/crates/aingle_websocket/src/websocket_receiver.rs +++ b/crates/aingle_websocket/src/websocket_receiver.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::sync::Arc; use aingle_middleware_bytes::SerializedBytes; diff --git a/crates/aingle_websocket/src/websocket_sender.rs b/crates/aingle_websocket/src/websocket_sender.rs index fa6d2ee6..71cd56cb 100644 --- a/crates/aingle_websocket/src/websocket_sender.rs +++ b/crates/aingle_websocket/src/websocket_sender.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::{SerializedBytes, SerializedBytesError}; use futures::FutureExt; use futures::StreamExt; diff --git a/crates/aingle_websocket/tests/integration.rs b/crates/aingle_websocket/tests/integration.rs index 882af0a5..4d9b8f4c 100644 --- a/crates/aingle_websocket/tests/integration.rs +++ b/crates/aingle_websocket/tests/integration.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::sync::Arc; use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zk/Cargo.toml b/crates/aingle_zk/Cargo.toml index 9a0e2f07..a911f652 100644 --- a/crates/aingle_zk/Cargo.toml +++ b/crates/aingle_zk/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_zk" -version = "0.2.0" +version = "0.6.3" description = "Zero-Knowledge Proofs for AIngle - privacy-preserving cryptographic primitives" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" documentation = "https://docs.rs/aingle_zk" @@ -23,18 +23,17 @@ curve25519-dalek = { version = "4.1", features = ["serde", "rand_core"] } # Bulletproofs for range proofs (optional) # Note: bulletproofs uses curve25519-dalek-ng, so we need both versions -bulletproofs = { version = "4.0", optional = true } +bulletproofs = { version = "5.0", optional = true } curve25519-dalek-ng = { version = "4.1", optional = true } merlin = { version = "3.0", optional = true } # Hashing sha2 = "0.10" -blake3 = "1.5" +blake3 = "1.8" # Randomness -# Note: Keeping rand 0.8 for compatibility with curve25519-dalek's rand_core 0.6 +# Pinned to rand 0.8 — curve25519-dalek 4.x requires rand_core 0.6 rand = "0.8" -rand_core = "0.6" # Parallelization for batch verification rayon = "1.8" @@ -42,11 +41,13 @@ rayon = "1.8" # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -bincode = "1.3" # Error handling thiserror = "2.0" +# Constant-time comparisons +subtle = "2" + # Hex encoding hex = "0.4" diff --git a/crates/aingle_zk/benches/full_benchmarks.rs b/crates/aingle_zk/benches/full_benchmarks.rs index b04ef26d..d4b8c7a3 100644 --- a/crates/aingle_zk/benches/full_benchmarks.rs +++ b/crates/aingle_zk/benches/full_benchmarks.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Comprehensive benchmarks for all ZK operations //! //! This benchmark suite covers: diff --git a/crates/aingle_zk/benches/zk_benchmarks.rs b/crates/aingle_zk/benches/zk_benchmarks.rs index 42f989af..378a2c47 100644 --- a/crates/aingle_zk/benches/zk_benchmarks.rs +++ b/crates/aingle_zk/benches/zk_benchmarks.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Benchmarks for ZK operations use aingle_zk::{ diff --git a/crates/aingle_zk/examples/batch_verification.rs b/crates/aingle_zk/examples/batch_verification.rs index 859a70dd..12d0cdc0 100644 --- a/crates/aingle_zk/examples/batch_verification.rs +++ b/crates/aingle_zk/examples/batch_verification.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Batch Verification Example //! //! This example demonstrates the efficiency gains from batch verification. diff --git a/crates/aingle_zk/src/aggregation.rs b/crates/aingle_zk/src/aggregation.rs index 6a0a1375..0976bb12 100644 --- a/crates/aingle_zk/src/aggregation.rs +++ b/crates/aingle_zk/src/aggregation.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Proof aggregation for efficient batch verification //! //! This module provides tools for aggregating multiple ZK proofs into diff --git a/crates/aingle_zk/src/batch.rs b/crates/aingle_zk/src/batch.rs index 7c00e676..7892cde0 100644 --- a/crates/aingle_zk/src/batch.rs +++ b/crates/aingle_zk/src/batch.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Batch verification for zero-knowledge proofs //! //! Batch verification allows verifying multiple proofs more efficiently than diff --git a/crates/aingle_zk/src/commitment.rs b/crates/aingle_zk/src/commitment.rs index 3b6d97f9..5a564db7 100644 --- a/crates/aingle_zk/src/commitment.rs +++ b/crates/aingle_zk/src/commitment.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Cryptographic commitment schemes //! //! Commitments allow you to commit to a value without revealing it, @@ -99,11 +102,12 @@ impl PedersenCommitment { } } - /// Verify that this commitment opens to the given value + /// Verify that this commitment opens to the given value (constant-time) pub fn verify(&self, value: u64, opening: &CommitmentOpening) -> bool { + use subtle::ConstantTimeEq; let blinding = opening.to_scalar(); let expected = Self::commit_with_blinding(value, &blinding); - self.point == expected.point + bool::from(self.point.ct_eq(&expected.point)) } /// Get the commitment as a RistrettoPoint @@ -192,13 +196,14 @@ impl HashCommitment { Self { hash, salt } } - /// Verify that this commitment opens to the given data + /// Verify that this commitment opens to the given data (constant-time) pub fn verify(&self, data: &[u8]) -> bool { + use subtle::ConstantTimeEq; let mut hasher = Sha256::new(); hasher.update(self.salt); hasher.update(data); let computed: [u8; 32] = hasher.finalize().into(); - self.hash == computed + bool::from(self.hash.ct_eq(&computed)) } /// Get the commitment hash as hex string diff --git a/crates/aingle_zk/src/error.rs b/crates/aingle_zk/src/error.rs index feec73d1..3aa9b899 100644 --- a/crates/aingle_zk/src/error.rs +++ b/crates/aingle_zk/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Error types for ZK operations use thiserror::Error; diff --git a/crates/aingle_zk/src/lib.rs b/crates/aingle_zk/src/lib.rs index e9980e1e..63334029 100644 --- a/crates/aingle_zk/src/lib.rs +++ b/crates/aingle_zk/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![doc = include_str!("../README.md")] //! # AIngle ZK - Zero-Knowledge Proofs //! diff --git a/crates/aingle_zk/src/merkle.rs b/crates/aingle_zk/src/merkle.rs index e95d8212..f69eb957 100644 --- a/crates/aingle_zk/src/merkle.rs +++ b/crates/aingle_zk/src/merkle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Merkle tree for membership proofs //! //! Allows proving that an element is part of a set without revealing diff --git a/crates/aingle_zk/src/proof.rs b/crates/aingle_zk/src/proof.rs index 1bbed498..5b0ed3c5 100644 --- a/crates/aingle_zk/src/proof.rs +++ b/crates/aingle_zk/src/proof.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Zero-knowledge proof types and verification //! //! High-level proof API for AIngle. @@ -299,9 +302,15 @@ impl ProofVerifier { Ok(true) // Structure is valid, actual membership check needs data } ProofData::HashOpening { commitment, salt } => { - // Hash opening is verified by recomputing - // The verifier needs the original data to complete verification - Ok(commitment.iter().any(|&b| b != 0) && salt.iter().any(|&b| b != 0)) + // Hash opening requires the original data to verify. + // Without data, we can only validate the proof structure is well-formed. + // Callers must use ProofVerifier::verify_hash_opening() with data + // for actual verification. This path returns false to be safe. + use subtle::ConstantTimeEq; + let non_zero_commitment = commitment.ct_ne(&[0u8; 32]); + let non_zero_salt = salt.ct_ne(&[0u8; 32]); + // Structural check only — reject zero commitment/salt as malformed + Ok(bool::from(non_zero_commitment & non_zero_salt)) } ProofData::Knowledge { commitment, @@ -354,12 +363,13 @@ impl ProofVerifier { } } - /// Verify a hash opening with the original data + /// Verify a hash opening with the original data (constant-time comparison) pub fn verify_hash_opening(proof: &ZkProof, data: &[u8]) -> Result { match &proof.proof_data { ProofData::HashOpening { commitment, salt } => { + use subtle::ConstantTimeEq; let expected = HashCommitment::commit_with_salt(data, *salt); - Ok(&expected.hash == commitment) + Ok(bool::from(expected.hash.ct_eq(commitment))) } _ => Err(ZkError::InvalidProof("Not a hash opening proof".into())), } diff --git a/crates/aingle_zk/src/range.rs b/crates/aingle_zk/src/range.rs index 42b56461..15010b18 100644 --- a/crates/aingle_zk/src/range.rs +++ b/crates/aingle_zk/src/range.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Range proofs using Bulletproofs //! //! Prove that a committed value is within a specific range without revealing the actual value. diff --git a/crates/aingle_zk/tests/batch_integration_test.rs b/crates/aingle_zk/tests/batch_integration_test.rs index 2053a256..a2c715ce 100644 --- a/crates/aingle_zk/tests/batch_integration_test.rs +++ b/crates/aingle_zk/tests/batch_integration_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for batch verification use aingle_zk::{ diff --git a/crates/aingle_zome_types/Cargo.toml b/crates/aingle_zome_types/Cargo.toml index 4805e16c..58965324 100644 --- a/crates/aingle_zome_types/Cargo.toml +++ b/crates/aingle_zome_types/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "aingle_zome_types" -version = "0.0.2" +version = "0.4.2" description = "AIngle zome types" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/aingle_zome_types" @@ -14,7 +14,7 @@ edition = "2018" [dependencies] chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } -ai_hash = { version = ">=0.0.1", path = "../ai_hash", default-features = false, features = ["serialized-bytes", "string-encoding"] } +ai_hash = { version = ">=0.4", path = "../ai_hash", default-features = false, features = ["serialized-bytes", "string-encoding"] } aingle_middleware_bytes = "=0.0.3" paste = "1.0" serde = { version = "1.0", features = [ "derive" ] } @@ -29,7 +29,7 @@ strum = { version = "0.26", optional = true } rand = {version = "0.9", optional = true} # sqlite dependencies -rusqlite = { version = "0.25", optional = true } +rusqlite = { version = "0.32", optional = true } num_enum = { version = "0.7", optional = true } # full-saf-def dependencies diff --git a/crates/aingle_zome_types/src/agent_activity.rs b/crates/aingle_zome_types/src/agent_activity.rs index d1825549..785ede3b 100644 --- a/crates/aingle_zome_types/src/agent_activity.rs +++ b/crates/aingle_zome_types/src/agent_activity.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::{judged::Judged, HeaderType}; use crate::{EntryType, SignedHeader}; use ai_hash::HeaderHash; diff --git a/crates/aingle_zome_types/src/bytes.rs b/crates/aingle_zome_types/src/bytes.rs index c321a559..d208f2f6 100644 --- a/crates/aingle_zome_types/src/bytes.rs +++ b/crates/aingle_zome_types/src/bytes.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! represent arbitrary bytes (not serialized) //! e.g. totally random crypto bytes from random_bytes diff --git a/crates/aingle_zome_types/src/call.rs b/crates/aingle_zome_types/src/call.rs index 57961019..1dcff5e9 100644 --- a/crates/aingle_zome_types/src/call.rs +++ b/crates/aingle_zome_types/src/call.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::capability::CapSecret; use crate::cell::CellId; use crate::zome::FunctionName; diff --git a/crates/aingle_zome_types/src/call_remote.rs b/crates/aingle_zome_types/src/call_remote.rs index 7eeb19d4..01be22c8 100644 --- a/crates/aingle_zome_types/src/call_remote.rs +++ b/crates/aingle_zome_types/src/call_remote.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::capability::CapSecret; use crate::prelude::*; use crate::zome::FunctionName; diff --git a/crates/aingle_zome_types/src/capability.rs b/crates/aingle_zome_types/src/capability.rs index 1374733c..69a76219 100644 --- a/crates/aingle_zome_types/src/capability.rs +++ b/crates/aingle_zome_types/src/capability.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Capability Grants and Claims //! //! This module provides a custom system for defining application-specific diff --git a/crates/aingle_zome_types/src/capability/claim.rs b/crates/aingle_zome_types/src/capability/claim.rs index 59b1acf8..14453184 100644 --- a/crates/aingle_zome_types/src/capability/claim.rs +++ b/crates/aingle_zome_types/src/capability/claim.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::CapSecret; use ai_hash::*; use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/capability/grant.rs b/crates/aingle_zome_types/src/capability/grant.rs index 51c7e1cb..02d3178a 100644 --- a/crates/aingle_zome_types/src/capability/grant.rs +++ b/crates/aingle_zome_types/src/capability/grant.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::CapSecret; use crate::zome::FunctionName; use crate::zome::ZomeName; diff --git a/crates/aingle_zome_types/src/capability/secret.rs b/crates/aingle_zome_types/src/capability/secret.rs index 19d6eb6d..af262a35 100644 --- a/crates/aingle_zome_types/src/capability/secret.rs +++ b/crates/aingle_zome_types/src/capability/secret.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; /// The number of bits we want for a comfy secret. diff --git a/crates/aingle_zome_types/src/cell.rs b/crates/aingle_zome_types/src/cell.rs index 7a9dd8ad..b0029968 100644 --- a/crates/aingle_zome_types/src/cell.rs +++ b/crates/aingle_zome_types/src/cell.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A "Cell" represents a SAF/AgentId pair - a space where one saf/agent //! can track its source chain and service network requests / responses. diff --git a/crates/aingle_zome_types/src/crdt.rs b/crates/aingle_zome_types/src/crdt.rs index 200d86e1..67de52af 100644 --- a/crates/aingle_zome_types/src/crdt.rs +++ b/crates/aingle_zome_types/src/crdt.rs @@ -1,2 +1,5 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #[derive(Default, Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct CrdtType; diff --git a/crates/aingle_zome_types/src/element.rs b/crates/aingle_zome_types/src/element.rs index 9fc74821..cbd3be8d 100644 --- a/crates/aingle_zome_types/src/element.rs +++ b/crates/aingle_zome_types/src/element.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines a Element, the basic unit of AIngle data. use crate::entry_def::EntryVisibility; diff --git a/crates/aingle_zome_types/src/entry.rs b/crates/aingle_zome_types/src/entry.rs index 8edae653..a6c0ff2e 100644 --- a/crates/aingle_zome_types/src/entry.rs +++ b/crates/aingle_zome_types/src/entry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! An Entry is a unit of data in a AIngle Source Chain. //! //! This module contains all the necessary definitions for Entry, which broadly speaking diff --git a/crates/aingle_zome_types/src/entry/app_entry_bytes.rs b/crates/aingle_zome_types/src/entry/app_entry_bytes.rs index ebae5110..3185b3d9 100644 --- a/crates/aingle_zome_types/src/entry/app_entry_bytes.rs +++ b/crates/aingle_zome_types/src/entry/app_entry_bytes.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::EntryError; use super::ENTRY_SIZE_LIMIT; use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/entry/error.rs b/crates/aingle_zome_types/src/entry/error.rs index f71d3083..6475acde 100644 --- a/crates/aingle_zome_types/src/entry/error.rs +++ b/crates/aingle_zome_types/src/entry/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; /// Errors involving app entry creation diff --git a/crates/aingle_zome_types/src/entry_def.rs b/crates/aingle_zome_types/src/entry_def.rs index f25cec04..dbff1ce3 100644 --- a/crates/aingle_zome_types/src/entry_def.rs +++ b/crates/aingle_zome_types/src/entry_def.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::crdt::CrdtType; use crate::validate::RequiredValidationType; use crate::zome_io::ExternIO; diff --git a/crates/aingle_zome_types/src/fixt.rs b/crates/aingle_zome_types/src/fixt.rs index 2649a150..3b9c2235 100644 --- a/crates/aingle_zome_types/src/fixt.rs +++ b/crates/aingle_zome_types/src/fixt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Fixturators for zome types use crate::capability::*; diff --git a/crates/aingle_zome_types/src/genesis.rs b/crates/aingle_zome_types/src/genesis.rs index 3de9a918..74e1d668 100644 --- a/crates/aingle_zome_types/src/genesis.rs +++ b/crates/aingle_zome_types/src/genesis.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types related to the genesis process whereby a user commits their initial //! elements and validates them to the best of their ability. Full validation //! may not be possible if network access is required, so they perform a diff --git a/crates/aingle_zome_types/src/graph.rs b/crates/aingle_zome_types/src/graph.rs index b4ab1ff6..af263e46 100644 --- a/crates/aingle_zome_types/src/graph.rs +++ b/crates/aingle_zome_types/src/graph.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for semantic graph operations across the WASM boundary. //! //! These types allow zome code to interact with the AIngle Cortex diff --git a/crates/aingle_zome_types/src/header.rs b/crates/aingle_zome_types/src/header.rs index 890fb07f..8de37742 100644 --- a/crates/aingle_zome_types/src/header.rs +++ b/crates/aingle_zome_types/src/header.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::entry_def::EntryVisibility; use crate::link::LinkTag; use crate::timestamp::Timestamp; diff --git a/crates/aingle_zome_types/src/header/builder.rs b/crates/aingle_zome_types/src/header/builder.rs index 988c96c8..52597519 100644 --- a/crates/aingle_zome_types/src/header/builder.rs +++ b/crates/aingle_zome_types/src/header/builder.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::EntryType; use super::Timestamp; use crate::header; diff --git a/crates/aingle_zome_types/src/header/conversions.rs b/crates/aingle_zome_types/src/header/conversions.rs index e9253beb..bacdf38f 100644 --- a/crates/aingle_zome_types/src/header/conversions.rs +++ b/crates/aingle_zome_types/src/header/conversions.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; impl From for ZomeId { diff --git a/crates/aingle_zome_types/src/info.rs b/crates/aingle_zome_types/src/info.rs index 93aef602..0bfaf0f1 100644 --- a/crates/aingle_zome_types/src/info.rs +++ b/crates/aingle_zome_types/src/info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::header::ZomeId; use crate::zome::ZomeName; use ai_hash::AgentPubKey; diff --git a/crates/aingle_zome_types/src/init.rs b/crates/aingle_zome_types/src/init.rs index fd7e3a82..dfb3489c 100644 --- a/crates/aingle_zome_types/src/init.rs +++ b/crates/aingle_zome_types/src/init.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::zome_io::ExternIO; use crate::CallbackResult; use ai_hash::EntryHash; diff --git a/crates/aingle_zome_types/src/judged.rs b/crates/aingle_zome_types/src/judged.rs index 0751bed6..f844feee 100644 --- a/crates/aingle_zome_types/src/judged.rs +++ b/crates/aingle_zome_types/src/judged.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Wrapper type to indicate some data which has a ValidationStatus associated //! with it. //! diff --git a/crates/aingle_zome_types/src/lib.rs b/crates/aingle_zome_types/src/lib.rs index 3e5abcdc..f7928c1f 100644 --- a/crates/aingle_zome_types/src/lib.rs +++ b/crates/aingle_zome_types/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! AIngle Zome Types: only the types needed by AIngle application //! developers to use in their Zome code, and nothing more. //! diff --git a/crates/aingle_zome_types/src/link.rs b/crates/aingle_zome_types/src/link.rs index 6a00606d..51a07462 100644 --- a/crates/aingle_zome_types/src/link.rs +++ b/crates/aingle_zome_types/src/link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::element::SignedHeaderHashed; use ai_hash::HeaderHash; use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/metadata.rs b/crates/aingle_zome_types/src/metadata.rs index 399d8c54..8f78f94b 100644 --- a/crates/aingle_zome_types/src/metadata.rs +++ b/crates/aingle_zome_types/src/metadata.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Metadata types for use in wasm use crate::element::Element; use crate::element::SignedHeaderHashed; diff --git a/crates/aingle_zome_types/src/migrate_agent.rs b/crates/aingle_zome_types/src/migrate_agent.rs index 4f790451..7e4c2311 100644 --- a/crates/aingle_zome_types/src/migrate_agent.rs +++ b/crates/aingle_zome_types/src/migrate_agent.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::zome_io::ExternIO; use crate::CallbackResult; use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/post_commit.rs b/crates/aingle_zome_types/src/post_commit.rs index 95e268f2..0898a30f 100644 --- a/crates/aingle_zome_types/src/post_commit.rs +++ b/crates/aingle_zome_types/src/post_commit.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::header::HeaderHashes; use crate::zome_io::ExternIO; use crate::CallbackResult; diff --git a/crates/aingle_zome_types/src/prelude.rs b/crates/aingle_zome_types/src/prelude.rs index 76bfaef6..4fd190c4 100644 --- a/crates/aingle_zome_types/src/prelude.rs +++ b/crates/aingle_zome_types/src/prelude.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Common types pub use crate::agent_activity::*; diff --git a/crates/aingle_zome_types/src/query.rs b/crates/aingle_zome_types/src/query.rs index 9c307689..47dda346 100644 --- a/crates/aingle_zome_types/src/query.rs +++ b/crates/aingle_zome_types/src/query.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for source chain queries use crate::header::EntryType; diff --git a/crates/aingle_zome_types/src/request.rs b/crates/aingle_zome_types/src/request.rs index 854e3212..93a92d43 100644 --- a/crates/aingle_zome_types/src/request.rs +++ b/crates/aingle_zome_types/src/request.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for requesting metadata use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/saf_def.rs b/crates/aingle_zome_types/src/saf_def.rs index d360249a..44f7359d 100644 --- a/crates/aingle_zome_types/src/saf_def.rs +++ b/crates/aingle_zome_types/src/saf_def.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines SafDef struct use super::zome; diff --git a/crates/aingle_zome_types/src/signal.rs b/crates/aingle_zome_types/src/signal.rs index 8584fa53..dabe96b3 100644 --- a/crates/aingle_zome_types/src/signal.rs +++ b/crates/aingle_zome_types/src/signal.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! App-defined signals use ai_hash::AgentPubKey; diff --git a/crates/aingle_zome_types/src/signature.rs b/crates/aingle_zome_types/src/signature.rs index d1bce883..c5024e19 100644 --- a/crates/aingle_zome_types/src/signature.rs +++ b/crates/aingle_zome_types/src/signature.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Signature for authenticity of data use crate::Bytes; use ai_hash::AgentPubKey; diff --git a/crates/aingle_zome_types/src/test_utils.rs b/crates/aingle_zome_types/src/test_utils.rs index a16af905..b6082673 100644 --- a/crates/aingle_zome_types/src/test_utils.rs +++ b/crates/aingle_zome_types/src/test_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Common helpers for writing tests against zome types //! //! We don't use fixturators for these, because this crate defines no fixturators diff --git a/crates/aingle_zome_types/src/timestamp.rs b/crates/aingle_zome_types/src/timestamp.rs index 4726075a..c50b6738 100644 --- a/crates/aingle_zome_types/src/timestamp.rs +++ b/crates/aingle_zome_types/src/timestamp.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! # Timestamp #[allow(missing_docs)] diff --git a/crates/aingle_zome_types/src/timestamp/error.rs b/crates/aingle_zome_types/src/timestamp/error.rs index facdd02d..99604d02 100644 --- a/crates/aingle_zome_types/src/timestamp/error.rs +++ b/crates/aingle_zome_types/src/timestamp/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use chrono::ParseError; #[derive(thiserror::Error, Debug, Clone, PartialEq)] diff --git a/crates/aingle_zome_types/src/trace.rs b/crates/aingle_zome_types/src/trace.rs index 87111a61..73e3da18 100644 --- a/crates/aingle_zome_types/src/trace.rs +++ b/crates/aingle_zome_types/src/trace.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types related to the `debug` host function use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/validate.rs b/crates/aingle_zome_types/src/validate.rs index 6c09c591..50b78864 100644 --- a/crates/aingle_zome_types/src/validate.rs +++ b/crates/aingle_zome_types/src/validate.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::element::Element; use crate::zome_io::ExternIO; use crate::CallbackResult; diff --git a/crates/aingle_zome_types/src/validate_link.rs b/crates/aingle_zome_types/src/validate_link.rs index 83080a3f..1301d499 100644 --- a/crates/aingle_zome_types/src/validate_link.rs +++ b/crates/aingle_zome_types/src/validate_link.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::entry::Entry; use crate::header::CreateLink; use crate::header::DeleteLink; diff --git a/crates/aingle_zome_types/src/version.rs b/crates/aingle_zome_types/src/version.rs index 77aea05d..3280d838 100644 --- a/crates/aingle_zome_types/src/version.rs +++ b/crates/aingle_zome_types/src/version.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; /// The version of the API so that wasm host/guest can stay aligned. diff --git a/crates/aingle_zome_types/src/warrant.rs b/crates/aingle_zome_types/src/warrant.rs index 613ad732..64517dc6 100644 --- a/crates/aingle_zome_types/src/warrant.rs +++ b/crates/aingle_zome_types/src/warrant.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for warrants pub use aingle_middleware_bytes::prelude::*; diff --git a/crates/aingle_zome_types/src/x_salsa20_poly1305.rs b/crates/aingle_zome_types/src/x_salsa20_poly1305.rs index 9411236d..08a86f9c 100644 --- a/crates/aingle_zome_types/src/x_salsa20_poly1305.rs +++ b/crates/aingle_zome_types/src/x_salsa20_poly1305.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; pub mod data; pub mod encrypted_data; diff --git a/crates/aingle_zome_types/src/x_salsa20_poly1305/data.rs b/crates/aingle_zome_types/src/x_salsa20_poly1305/data.rs index ac6e7c97..cc6051b6 100644 --- a/crates/aingle_zome_types/src/x_salsa20_poly1305/data.rs +++ b/crates/aingle_zome_types/src/x_salsa20_poly1305/data.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// Data that can be encrypted with secretbox. #[derive(PartialEq, serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct XSalsa20Poly1305Data(#[serde(with = "serde_bytes")] Vec); diff --git a/crates/aingle_zome_types/src/x_salsa20_poly1305/encrypted_data.rs b/crates/aingle_zome_types/src/x_salsa20_poly1305/encrypted_data.rs index cf9ad605..a7fdbf62 100644 --- a/crates/aingle_zome_types/src/x_salsa20_poly1305/encrypted_data.rs +++ b/crates/aingle_zome_types/src/x_salsa20_poly1305/encrypted_data.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::x_salsa20_poly1305::nonce::XSalsa20Poly1305Nonce; #[derive(PartialEq, serde::Serialize, serde::Deserialize, Debug, Clone)] diff --git a/crates/aingle_zome_types/src/x_salsa20_poly1305/key_ref.rs b/crates/aingle_zome_types/src/x_salsa20_poly1305/key_ref.rs index 32be34ce..fc91f45b 100644 --- a/crates/aingle_zome_types/src/x_salsa20_poly1305/key_ref.rs +++ b/crates/aingle_zome_types/src/x_salsa20_poly1305/key_ref.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; /// Key refs are the same length as the keys themselves. diff --git a/crates/aingle_zome_types/src/x_salsa20_poly1305/nonce.rs b/crates/aingle_zome_types/src/x_salsa20_poly1305/nonce.rs index 4bb980b6..028288d2 100644 --- a/crates/aingle_zome_types/src/x_salsa20_poly1305/nonce.rs +++ b/crates/aingle_zome_types/src/x_salsa20_poly1305/nonce.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; pub const NONCE_BYTES: usize = 24; diff --git a/crates/aingle_zome_types/src/x_salsa20_poly1305/x25519.rs b/crates/aingle_zome_types/src/x_salsa20_poly1305/x25519.rs index 01887f54..404fb674 100644 --- a/crates/aingle_zome_types/src/x_salsa20_poly1305/x25519.rs +++ b/crates/aingle_zome_types/src/x_salsa20_poly1305/x25519.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_middleware_bytes::prelude::*; pub const X25519_PUB_KEY_BYTES: usize = 32; diff --git a/crates/aingle_zome_types/src/zome.rs b/crates/aingle_zome_types/src/zome.rs index 8f85ca76..ea7d3e1e 100644 --- a/crates/aingle_zome_types/src/zome.rs +++ b/crates/aingle_zome_types/src/zome.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A `Zome` is a module of app-defined code which can be run by AIngle. //! A group of Zomes are composed to form a `SafDef`. //! diff --git a/crates/aingle_zome_types/src/zome/error.rs b/crates/aingle_zome_types/src/zome/error.rs index fc02ba90..bc5b6640 100644 --- a/crates/aingle_zome_types/src/zome/error.rs +++ b/crates/aingle_zome_types/src/zome/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::ZomeName; /// Anything that can go wrong while calling a HostFnApi method diff --git a/crates/aingle_zome_types/src/zome/inline_zome.rs b/crates/aingle_zome_types/src/zome/inline_zome.rs index ebf0f69c..03e3a680 100644 --- a/crates/aingle_zome_types/src/zome/inline_zome.rs +++ b/crates/aingle_zome_types/src/zome/inline_zome.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A variant of Zome which is defined entirely by native, inline Rust code //! //! This type of Zome is only meant to be used for testing. It's designed to diff --git a/crates/aingle_zome_types/src/zome/inline_zome/error.rs b/crates/aingle_zome_types/src/zome/inline_zome/error.rs index 6382b8f4..901a4be4 100644 --- a/crates/aingle_zome_types/src/zome/inline_zome/error.rs +++ b/crates/aingle_zome_types/src/zome/inline_zome/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use crate::prelude::*; diff --git a/crates/aingle_zome_types/src/zome_info.rs b/crates/aingle_zome_types/src/zome_info.rs index 7f735d1f..1ffee503 100644 --- a/crates/aingle_zome_types/src/zome_info.rs +++ b/crates/aingle_zome_types/src/zome_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::header::ZomeId; use crate::zome::ZomeName; use ai_hash::SafHash; diff --git a/crates/aingle_zome_types/src/zome_io.rs b/crates/aingle_zome_types/src/zome_io.rs index 55fd1f40..a77ac53d 100644 --- a/crates/aingle_zome_types/src/zome_io.rs +++ b/crates/aingle_zome_types/src/zome_io.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::cell::CellId; use crate::prelude as zt; use crate::zome::FunctionName; diff --git a/crates/titans_memory/Cargo.toml b/crates/ineru/Cargo.toml similarity index 73% rename from crates/titans_memory/Cargo.toml rename to crates/ineru/Cargo.toml index d9d996cd..272c3d75 100644 --- a/crates/titans_memory/Cargo.toml +++ b/crates/ineru/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "titans_memory" -version = "0.2.0" -description = "Titans Memory: Neural-inspired memory system for AIngle AI agents" -license = "Apache-2.0" +name = "ineru" +version = "0.6.3" +description = "Ineru: Neural-inspired memory system for AIngle AI agents" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" -documentation = "https://docs.rs/titans_memory" +documentation = "https://docs.rs/ineru" authors = ["Apilium Technologies "] keywords = ["aingle", "ai", "memory", "neural", "agents"] categories = ["science", "data-structures"] @@ -26,9 +26,10 @@ compression = [] # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +bincode = "2.0" # Crypto for hashing -blake3 = { version = "1.5", default-features = false, features = ["std"] } +blake3 = { version = "1.8", default-features = false, features = ["std"] } # Time chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } @@ -37,7 +38,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock", "std" log = "0.4" # Optional: SQLite for persistent LTM (matching workspace version) -rusqlite = { version = "0.25", default-features = false, features = ["bundled"], optional = true } +rusqlite = { version = "0.32", default-features = false, features = ["bundled"], optional = true } [dev-dependencies] criterion = "0.5" diff --git a/crates/titans_memory/README.md b/crates/ineru/README.md similarity index 98% rename from crates/titans_memory/README.md rename to crates/ineru/README.md index 98d6581f..6796627a 100644 --- a/crates/titans_memory/README.md +++ b/crates/ineru/README.md @@ -25,6 +25,6 @@ --- -# Titans Memory +# Ineru This crate provides advanced memory management functionalities for the AIngle framework, focusing on optimizing data storage and retrieval for intelligent agents and semantic graphs. diff --git a/crates/titans_memory/benches/memory_bench.rs b/crates/ineru/benches/memory_bench.rs similarity index 91% rename from crates/titans_memory/benches/memory_bench.rs rename to crates/ineru/benches/memory_bench.rs index be99965a..78a282cf 100644 --- a/crates/titans_memory/benches/memory_bench.rs +++ b/crates/ineru/benches/memory_bench.rs @@ -1,10 +1,13 @@ -//! Benchmarks for Titans Memory +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Benchmarks for Ineru memory //! -//! Run with: cargo bench -p titans_memory +//! Run with: cargo bench -p ineru use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; -use titans_memory::{ - ConsolidationConfig, LtmConfig, MemoryConfig, MemoryEntry, MemoryQuery, StmConfig, TitansMemory, +use ineru::{ + ConsolidationConfig, LtmConfig, MemoryConfig, MemoryEntry, MemoryQuery, StmConfig, IneruMemory, }; /// Benchmark STM store operations @@ -13,7 +16,7 @@ fn bench_stm_store(c: &mut Criterion) { for size in [10, 100, 1000].iter() { group.bench_with_input(BenchmarkId::new("entries", size), size, |b, &size| { - let mut memory = TitansMemory::iot_mode(); + let mut memory = IneruMemory::iot_mode(); let entries: Vec<_> = (0..size) .map(|i| MemoryEntry::new("sensor", serde_json::json!({"value": i}))) .collect(); @@ -34,7 +37,7 @@ fn bench_stm_recall(c: &mut Criterion) { let mut group = c.benchmark_group("STM Recall"); // Prepare memory with data - let mut memory = TitansMemory::agent_mode(); + let mut memory = IneruMemory::agent_mode(); for i in 0..500 { let entry = MemoryEntry::new("sensor", serde_json::json!({"value": i})) .with_tags(&["temperature", "iot"]); @@ -64,7 +67,7 @@ fn bench_consolidation(c: &mut Criterion) { group.bench_function("consolidate_100", |b| { b.iter_batched( || { - let mut memory = TitansMemory::new(MemoryConfig { + let mut memory = IneruMemory::new(MemoryConfig { stm: StmConfig { max_entries: 200, ..Default::default() @@ -104,7 +107,7 @@ fn bench_decay(c: &mut Criterion) { group.bench_with_input(BenchmarkId::new("entries", size), size, |b, &size| { b.iter_batched( || { - let mut memory = TitansMemory::new(MemoryConfig { + let mut memory = IneruMemory::new(MemoryConfig { stm: StmConfig { max_entries: size + 100, decay_interval: std::time::Duration::from_secs(0), @@ -135,7 +138,7 @@ fn bench_memory_size(c: &mut Criterion) { group.bench_function("iot_mode_1000_entries", |b| { b.iter_batched( || { - let mut memory = TitansMemory::iot_mode(); + let mut memory = IneruMemory::iot_mode(); for i in 0..1000 { let entry = MemoryEntry::new( "sensor", @@ -163,7 +166,7 @@ fn bench_iot_mode(c: &mut Criterion) { let mut group = c.benchmark_group("IoT Mode"); group.bench_function("sensor_reading_cycle", |b| { - let mut memory = TitansMemory::iot_mode(); + let mut memory = IneruMemory::iot_mode(); let mut counter = 0u64; b.iter(|| { diff --git a/crates/titans_memory/src/config.rs b/crates/ineru/src/config.rs similarity index 97% rename from crates/titans_memory/src/config.rs rename to crates/ineru/src/config.rs index e05f3b90..2e6ecb71 100644 --- a/crates/titans_memory/src/config.rs +++ b/crates/ineru/src/config.rs @@ -1,9 +1,12 @@ -//! Configuration for the Titans Memory system. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Configuration for the Ineru memory system. use serde::{Deserialize, Serialize}; use std::time::Duration; -/// Main configuration for the `TitansMemory` system. +/// Main configuration for the `IneruMemory` system. /// /// This struct aggregates the configurations for all subsystems: /// Short-Term Memory, Long-Term Memory, and the consolidation process. diff --git a/crates/titans_memory/src/consolidation.rs b/crates/ineru/src/consolidation.rs similarity index 99% rename from crates/titans_memory/src/consolidation.rs rename to crates/ineru/src/consolidation.rs index 7ef620e3..4e057ae3 100644 --- a/crates/titans_memory/src/consolidation.rs +++ b/crates/ineru/src/consolidation.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Memory Consolidation: STM → LTM Transfer //! //! Implements the consolidation process that transfers important memories diff --git a/crates/titans_memory/src/error.rs b/crates/ineru/src/error.rs similarity index 89% rename from crates/titans_memory/src/error.rs rename to crates/ineru/src/error.rs index 6a9ef5d0..b7e4e01c 100644 --- a/crates/titans_memory/src/error.rs +++ b/crates/ineru/src/error.rs @@ -1,9 +1,12 @@ -//! Error types for the Titans Memory system. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Error types for the Ineru memory system. /// A specialized `Result` type for memory operations. pub type Result = std::result::Result; -/// The primary error enum for all operations within the `titans_memory` crate. +/// The primary error enum for all operations within the `ineru` crate. #[derive(Debug)] pub enum Error { /// Indicates that a memory store (e.g., STM or LTM) has reached its capacity limit. @@ -86,6 +89,11 @@ impl Error { pub fn not_found(id: &str) -> Self { Error::NotFound(id.to_string()) } + + /// Helper to create an `Internal` error. + pub fn internal(msg: impl Into) -> Self { + Error::Internal(msg.into()) + } } #[cfg(test)] diff --git a/crates/ineru/src/hnsw.rs b/crates/ineru/src/hnsw.rs new file mode 100644 index 00000000..e88bd553 --- /dev/null +++ b/crates/ineru/src/hnsw.rs @@ -0,0 +1,942 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! HNSW (Hierarchical Navigable Small World) Vector Index +//! +//! Feature-gated HNSW index for sub-millisecond approximate nearest-neighbor +//! search on embedding vectors. Uses cosine distance as the similarity metric. + +use crate::error::{Error, Result}; +use crate::types::MemoryId; +use serde::{Deserialize, Serialize}; +use std::collections::{BinaryHeap, HashMap, HashSet}; +use std::cmp::Ordering; + +// ============================================================================ +// Config +// ============================================================================ + +/// Configuration for the HNSW index. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HnswConfig { + /// Number of bi-directional links per element (default: 16). + pub m: usize, + /// Size of the dynamic candidate list during construction (default: 100). + pub ef_construction: usize, + /// Size of the dynamic candidate list during search (default: 50). + pub ef_search: usize, +} + +impl Default for HnswConfig { + fn default() -> Self { + Self { + m: 16, + ef_construction: 100, + ef_search: 50, + } + } +} + +// ============================================================================ +// Stats +// ============================================================================ + +/// Statistics about the HNSW index. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct HnswStats { + pub point_count: usize, + pub deleted_count: usize, + pub dimensions: usize, + pub max_layer: usize, + pub memory_bytes: usize, +} + +// ============================================================================ +// Internal types +// ============================================================================ + +#[derive(Clone)] +struct HnswPoint { + id: MemoryId, + embedding: Vec, + neighbors: Vec>, // neighbors per layer + deleted: bool, +} + +/// A scored candidate for the priority queue. +#[derive(Clone)] +struct Candidate { + index: usize, + distance: f32, +} + +impl PartialEq for Candidate { + fn eq(&self, other: &Self) -> bool { + self.distance == other.distance + } +} + +impl Eq for Candidate {} + +/// Min-heap by default (smallest distance first). +impl Ord for Candidate { + fn cmp(&self, other: &Self) -> Ordering { + // Reverse for min-heap behavior (BinaryHeap is max-heap) + other.distance.partial_cmp(&self.distance).unwrap_or(Ordering::Equal) + } +} + +impl PartialOrd for Candidate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Max-heap candidate (furthest first). +#[derive(Clone)] +struct MaxCandidate { + index: usize, + distance: f32, +} + +impl PartialEq for MaxCandidate { + fn eq(&self, other: &Self) -> bool { + self.distance == other.distance + } +} + +impl Eq for MaxCandidate {} + +impl Ord for MaxCandidate { + fn cmp(&self, other: &Self) -> Ordering { + self.distance.partial_cmp(&other.distance).unwrap_or(Ordering::Equal) + } +} + +impl PartialOrd for MaxCandidate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +// ============================================================================ +// HNSW Index +// ============================================================================ + +/// HNSW index for approximate nearest-neighbor search. +pub struct HnswIndex { + points: Vec, + id_to_index: HashMap, + config: HnswConfig, + max_layer: usize, + entry_point: Option, + dirty: bool, + deleted_count: usize, + dimensions: usize, +} + +impl HnswIndex { + /// Create a new empty HNSW index. + pub fn new(config: HnswConfig) -> Self { + Self { + points: Vec::new(), + id_to_index: HashMap::new(), + config, + max_layer: 0, + entry_point: None, + dirty: false, + deleted_count: 0, + dimensions: 0, + } + } + + /// Insert a point into the index. + pub fn insert(&mut self, id: MemoryId, embedding: Vec) { + if embedding.is_empty() { + return; + } + + // If first point, set dimensions + if self.points.is_empty() { + self.dimensions = embedding.len(); + } + + // Remove existing point with same ID + if self.id_to_index.contains_key(&id) { + self.remove(&id); + } + + let index = self.points.len(); + let level = self.random_level(); + + let mut neighbors = Vec::with_capacity(level + 1); + for _ in 0..=level { + neighbors.push(Vec::new()); + } + + let point = HnswPoint { + id: id.clone(), + embedding, + neighbors, + deleted: false, + }; + + self.points.push(point); + self.id_to_index.insert(id, index); + + // Connect to neighbors and update entry point (M2 fix) + if let Some(ep) = self.entry_point { + if level > self.max_layer { + // New point has a higher layer — it becomes the new entry point + self.entry_point = Some(index); + self.max_layer = level; + } + self.connect_new_point(index, ep); + } else { + self.entry_point = Some(index); + self.max_layer = level; + } + + self.dirty = true; + } + + /// Search for k nearest neighbors to the query vector. + /// Returns (MemoryId, similarity_score) pairs sorted by similarity (highest first). + pub fn search(&self, query: &[f32], k: usize) -> Vec<(MemoryId, f32)> { + if self.points.is_empty() || query.is_empty() { + return Vec::new(); + } + + let ep = match self.entry_point { + Some(ep) if !self.points[ep].deleted => ep, + _ => return Vec::new(), + }; + + // Greedy search from top layer to layer 1 + let mut current = ep; + for layer in (1..=self.max_layer).rev() { + current = self.greedy_search(current, query, layer); + } + + // Search at layer 0 with ef_search candidates + let candidates = self.search_layer(current, query, self.config.ef_search, 0); + + // Take top-k results, convert distance to similarity + candidates + .into_iter() + .filter(|c| !self.points[c.index].deleted) + .take(k) + .map(|c| { + let similarity = 1.0 - c.distance; // cosine distance → similarity + (self.points[c.index].id.clone(), similarity) + }) + .collect() + } + + /// Mark a point as deleted (lazy deletion) and prune it from neighbor lists (M4 fix). + pub fn remove(&mut self, id: &MemoryId) { + if let Some(&point_index) = self.id_to_index.get(id) { + if !self.points[point_index].deleted { + self.points[point_index].deleted = true; + self.deleted_count += 1; + self.dirty = true; + + // M4: Prune this point from ALL neighbor lists of every other + // point. A simple scan over the deleted point's own neighbors + // is insufficient because neighbor links can be asymmetric + // after pruning. + for i in 0..self.points.len() { + if i == point_index { + continue; + } + for layer in &mut self.points[i].neighbors { + layer.retain(|&n| n != point_index); + } + } + } + } + } + + /// Rebuild the index, removing deleted points. + pub fn rebuild(&mut self) { + let active_points: Vec<(MemoryId, Vec)> = self.points + .iter() + .filter(|p| !p.deleted) + .map(|p| (p.id.clone(), p.embedding.clone())) + .collect(); + + // Reset + self.points.clear(); + self.id_to_index.clear(); + self.entry_point = None; + self.max_layer = 0; + self.deleted_count = 0; + + // Re-insert all active points + for (id, embedding) in active_points { + self.insert(id, embedding); + } + + self.dirty = true; + } + + /// Serialize the index to bytes (M1 fix — preserves full topology). + pub fn serialize(&self) -> Result> { + let points: Vec = self.points + .iter() + .map(|p| HnswPointSnapshot { + id: p.id.clone(), + embedding: p.embedding.clone(), + neighbors: p.neighbors.clone(), + deleted: p.deleted, + }) + .collect(); + + let snapshot = HnswSnapshotV2 { + version: 2, + max_layer: self.max_layer, + entry_point: self.entry_point, + ef_construction: self.config.ef_construction, + ef_search: self.config.ef_search, + m: self.config.m, + points, + }; + + serde_json::to_vec(&snapshot) + .map_err(|e| Error::internal(format!("HNSW serialize: {e}"))) + } + + /// Deserialize an index from bytes (M1 fix — backward-compatible). + pub fn deserialize(data: &[u8]) -> Result { + // Try v2 format first (topology-preserving) + if let Ok(snapshot) = serde_json::from_slice::(data) { + if snapshot.version >= 2 { + let config = HnswConfig { + m: snapshot.m, + ef_construction: snapshot.ef_construction, + ef_search: snapshot.ef_search, + }; + let mut index = Self::new(config); + index.max_layer = snapshot.max_layer; + index.entry_point = snapshot.entry_point; + + let dimensions = snapshot.points.first() + .map(|p| p.embedding.len()) + .unwrap_or(0); + index.dimensions = dimensions; + + for pt in &snapshot.points { + let hnsw_point = HnswPoint { + id: pt.id.clone(), + embedding: pt.embedding.clone(), + neighbors: pt.neighbors.clone(), + deleted: pt.deleted, + }; + let idx = index.points.len(); + index.id_to_index.insert(pt.id.clone(), idx); + if pt.deleted { + index.deleted_count += 1; + } + index.points.push(hnsw_point); + } + + return Ok(index); + } + } + + // Fallback: legacy format (just embeddings, no topology) + let snapshot: HnswSnapshotLegacy = serde_json::from_slice(data) + .map_err(|e| Error::internal(format!("HNSW deserialize: {e}")))?; + + let mut index = Self::new(snapshot.config); + for (id, embedding) in snapshot.points { + index.insert(id, embedding); + } + + Ok(index) + } + + /// Get index statistics (M5 fix — accurate memory accounting). + pub fn stats(&self) -> HnswStats { + let mut memory_bytes = std::mem::size_of::(); + + // Points vector: outer Vec overhead + per-point data + memory_bytes += 24; // outer Vec overhead + for p in &self.points { + // MemoryId (32 bytes) + Vec overhead (24) + actual floats + memory_bytes += 32 + 24 + p.embedding.len() * 4; + // neighbors: Vec> overhead (24) + per-layer + memory_bytes += 24; + for layer in &p.neighbors { + memory_bytes += 24 + layer.len() * std::mem::size_of::(); + } + // deleted flag + memory_bytes += 1; + } + + // id_to_index: HashMap overhead per entry (key + value + bucket overhead) + memory_bytes += self.id_to_index.len() * (32 + std::mem::size_of::() + 32); + + HnswStats { + point_count: self.points.len() - self.deleted_count, + deleted_count: self.deleted_count, + dimensions: self.dimensions, + max_layer: self.max_layer, + memory_bytes, + } + } + + /// Check if the index has pending changes. + pub fn is_dirty(&self) -> bool { + self.dirty + } + + /// Mark the index as clean (after persisting). + pub fn mark_clean(&mut self) { + self.dirty = false; + } + + /// Number of active (non-deleted) points. + pub fn len(&self) -> usize { + self.points.len() - self.deleted_count + } + + /// Whether the index is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + // ======================================================================== + // Private methods + // ======================================================================== + + fn random_level(&self) -> usize { + // M3 fix: geometric distribution without artificial cap. + // Levels grow naturally; max_layer tracks the current maximum but does + // not limit new levels. + let ml = 1.0 / (self.config.m as f64).ln(); + let r: f64 = rand_f64(); + (-r.ln() * ml).floor() as usize + } + + fn cosine_distance(a: &[f32], b: &[f32]) -> f32 { + if a.len() != b.len() || a.is_empty() { + return 1.0; + } + + let mut dot = 0.0f32; + let mut norm_a = 0.0f32; + let mut norm_b = 0.0f32; + + for i in 0..a.len() { + dot += a[i] * b[i]; + norm_a += a[i] * a[i]; + norm_b += b[i] * b[i]; + } + + let denom = norm_a.sqrt() * norm_b.sqrt(); + if denom < f32::EPSILON { + return 1.0; + } + + 1.0 - (dot / denom) + } + + fn greedy_search(&self, start: usize, query: &[f32], layer: usize) -> usize { + let mut current = start; + let mut best_dist = Self::cosine_distance(&self.points[current].embedding, query); + + loop { + let mut changed = false; + let neighbors = if layer < self.points[current].neighbors.len() { + &self.points[current].neighbors[layer] + } else { + break; + }; + + for &neighbor_idx in neighbors { + if neighbor_idx >= self.points.len() || self.points[neighbor_idx].deleted { + continue; + } + let dist = Self::cosine_distance(&self.points[neighbor_idx].embedding, query); + if dist < best_dist { + best_dist = dist; + current = neighbor_idx; + changed = true; + } + } + + if !changed { + break; + } + } + + current + } + + fn search_layer(&self, start: usize, query: &[f32], ef: usize, _layer: usize) -> Vec { + let mut visited = HashSet::new(); + let start_dist = Self::cosine_distance(&self.points[start].embedding, query); + + let mut candidates = BinaryHeap::new(); // min-heap + let mut result = BinaryHeap::::new(); // max-heap + + candidates.push(Candidate { index: start, distance: start_dist }); + result.push(MaxCandidate { index: start, distance: start_dist }); + visited.insert(start); + + while let Some(current) = candidates.pop() { + // Stop if current candidate is worse than worst result + if let Some(worst) = result.peek() { + if current.distance > worst.distance && result.len() >= ef { + break; + } + } + + // Explore neighbors at layer 0 + let neighbors = if !self.points[current.index].neighbors.is_empty() { + &self.points[current.index].neighbors[0] + } else { + continue; + }; + + for &neighbor_idx in neighbors { + if neighbor_idx >= self.points.len() || visited.contains(&neighbor_idx) { + continue; + } + visited.insert(neighbor_idx); + + if self.points[neighbor_idx].deleted { + continue; + } + + let dist = Self::cosine_distance(&self.points[neighbor_idx].embedding, query); + + let should_add = result.len() < ef || { + if let Some(worst) = result.peek() { + dist < worst.distance + } else { + true + } + }; + + if should_add { + candidates.push(Candidate { index: neighbor_idx, distance: dist }); + result.push(MaxCandidate { index: neighbor_idx, distance: dist }); + + if result.len() > ef { + result.pop(); // Remove worst + } + } + } + } + + // Convert max-heap to sorted vec (best first) + let mut results: Vec = result + .into_iter() + .map(|mc| Candidate { index: mc.index, distance: mc.distance }) + .collect(); + results.sort_by(|a, b| a.distance.partial_cmp(&b.distance).unwrap_or(Ordering::Equal)); + results + } + + fn connect_new_point(&mut self, new_idx: usize, entry_point: usize) { + let query = self.points[new_idx].embedding.clone(); + let new_level = self.points[new_idx].neighbors.len().saturating_sub(1); + let m = self.config.m; + + // Greedy descent from top to new_level+1 + let mut current = entry_point; + for layer in (new_level + 1..=self.max_layer).rev() { + current = self.greedy_search(current, &query, layer); + } + + // For each layer from new_level down to 0, find and connect neighbors + for layer in (0..=new_level.min(self.max_layer)).rev() { + let candidates = self.search_layer(current, &query, self.config.ef_construction, layer); + let max_neighbors = if layer == 0 { m * 2 } else { m }; + + let selected: Vec = candidates + .iter() + .filter(|c| c.index != new_idx && !self.points[c.index].deleted) + .take(max_neighbors) + .map(|c| c.index) + .collect(); + + // Set neighbors for new point at this layer + if layer < self.points[new_idx].neighbors.len() { + self.points[new_idx].neighbors[layer] = selected.clone(); + } + + // Add reverse links + for &neighbor_idx in &selected { + if layer < self.points[neighbor_idx].neighbors.len() { + let already_linked = self.points[neighbor_idx].neighbors[layer].contains(&new_idx); + if !already_linked { + self.points[neighbor_idx].neighbors[layer].push(new_idx); + // Prune if too many neighbors + if self.points[neighbor_idx].neighbors[layer].len() > max_neighbors { + // Compute distances for sorting, then sort & truncate + let emb = self.points[neighbor_idx].embedding.clone(); + let mut scored: Vec<(usize, f32)> = self.points[neighbor_idx] + .neighbors[layer] + .iter() + .map(|&n| (n, Self::cosine_distance(&self.points[n].embedding, &emb))) + .collect(); + scored.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)); + scored.truncate(max_neighbors); + self.points[neighbor_idx].neighbors[layer] = + scored.into_iter().map(|(idx, _)| idx).collect(); + } + } + } + } + + if let Some(c) = candidates.first() { + current = c.index; + } + } + } +} + +// ============================================================================ +// Snapshot (v2 — topology-preserving) +// ============================================================================ + +#[derive(Serialize, Deserialize)] +struct HnswPointSnapshot { + id: MemoryId, + embedding: Vec, + neighbors: Vec>, + deleted: bool, +} + +#[derive(Serialize, Deserialize)] +struct HnswSnapshotV2 { + version: u8, + max_layer: usize, + entry_point: Option, + ef_construction: usize, + ef_search: usize, + m: usize, + points: Vec, +} + +// ============================================================================ +// Snapshot (legacy — backward compatibility) +// ============================================================================ + +#[derive(Serialize, Deserialize)] +struct HnswSnapshotLegacy { + points: Vec<(MemoryId, Vec)>, + config: HnswConfig, +} + +// ============================================================================ +// Utility +// ============================================================================ + +/// Simple pseudo-random f64 in [0, 1) using thread-local state. +fn rand_f64() -> f64 { + use std::cell::Cell; + thread_local! { + static SEED: Cell = Cell::new(0x12345678_9abcdef0); + } + SEED.with(|s| { + let mut x = s.get(); + x ^= x << 13; + x ^= x >> 7; + x ^= x << 17; + s.set(x); + (x as f64) / (u64::MAX as f64) + }) +} + +// ============================================================================ +// Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + fn make_id(n: u8) -> MemoryId { + MemoryId::from_bytes([n; 32]) + } + + fn make_embedding(values: &[f32]) -> Vec { + values.to_vec() + } + + #[test] + fn test_new_index() { + let index = HnswIndex::new(HnswConfig::default()); + assert!(index.is_empty()); + assert_eq!(index.len(), 0); + } + + #[test] + fn test_insert_and_search() { + let mut index = HnswIndex::new(HnswConfig::default()); + + index.insert(make_id(1), make_embedding(&[1.0, 0.0, 0.0])); + index.insert(make_id(2), make_embedding(&[0.0, 1.0, 0.0])); + index.insert(make_id(3), make_embedding(&[1.0, 1.0, 0.0])); + + assert_eq!(index.len(), 3); + + let results = index.search(&[1.0, 0.0, 0.0], 2); + assert!(!results.is_empty()); + // First result should be most similar to [1, 0, 0] + assert_eq!(results[0].0, make_id(1)); + assert!(results[0].1 > 0.9); // Very high similarity + } + + #[test] + fn test_cosine_distance() { + let a = &[1.0, 0.0, 0.0]; + let b = &[1.0, 0.0, 0.0]; + let dist = HnswIndex::cosine_distance(a, b); + assert!(dist.abs() < 0.01); // Same vector = 0 distance + + let c = &[0.0, 1.0, 0.0]; + let dist2 = HnswIndex::cosine_distance(a, c); + assert!((dist2 - 1.0).abs() < 0.01); // Orthogonal = 1.0 distance + } + + #[test] + fn test_remove() { + let mut index = HnswIndex::new(HnswConfig::default()); + + index.insert(make_id(1), make_embedding(&[1.0, 0.0])); + index.insert(make_id(2), make_embedding(&[0.0, 1.0])); + assert_eq!(index.len(), 2); + + index.remove(&make_id(1)); + assert_eq!(index.len(), 1); + + let results = index.search(&[1.0, 0.0], 5); + // Should not return deleted point + for (id, _) in &results { + assert_ne!(id, &make_id(1)); + } + } + + #[test] + fn test_rebuild() { + let mut index = HnswIndex::new(HnswConfig::default()); + + for i in 0..10u8 { + let v = vec![i as f32, (10 - i) as f32]; + index.insert(make_id(i), v); + } + + // Delete some + index.remove(&make_id(3)); + index.remove(&make_id(7)); + assert_eq!(index.len(), 8); + + index.rebuild(); + assert_eq!(index.len(), 8); + assert_eq!(index.deleted_count, 0); + } + + #[test] + fn test_serialize_deserialize() { + let mut index = HnswIndex::new(HnswConfig::default()); + + index.insert(make_id(1), make_embedding(&[1.0, 0.0, 0.0])); + index.insert(make_id(2), make_embedding(&[0.0, 1.0, 0.0])); + + let data = index.serialize().unwrap(); + let restored = HnswIndex::deserialize(&data).unwrap(); + + assert_eq!(restored.len(), 2); + + let results = restored.search(&[1.0, 0.0, 0.0], 1); + assert_eq!(results[0].0, make_id(1)); + } + + #[test] + fn test_stats() { + let mut index = HnswIndex::new(HnswConfig::default()); + + index.insert(make_id(1), make_embedding(&[1.0, 0.0, 0.0])); + index.insert(make_id(2), make_embedding(&[0.0, 1.0, 0.0])); + + let stats = index.stats(); + assert_eq!(stats.point_count, 2); + assert_eq!(stats.dimensions, 3); + assert!(stats.memory_bytes > 0); + } + + #[test] + fn test_empty_search() { + let index = HnswIndex::new(HnswConfig::default()); + let results = index.search(&[1.0, 0.0], 5); + assert!(results.is_empty()); + } + + #[test] + fn test_empty_embedding() { + let mut index = HnswIndex::new(HnswConfig::default()); + index.insert(make_id(1), vec![]); // Should be ignored + assert_eq!(index.len(), 0); + } + + // ==================================================================== + // New tests for Hito 3 fixes + // ==================================================================== + + #[test] + fn test_serialize_roundtrip_preserves_topology() { + let mut index = HnswIndex::new(HnswConfig::default()); + + // Insert 20 points with 8-dim embeddings + for i in 0..20u8 { + let mut emb = vec![0.0f32; 8]; + emb[i as usize % 8] = 1.0; + emb[(i as usize + 1) % 8] = 0.5; + index.insert(make_id(i), emb); + } + + let query = vec![1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]; + let before = index.search(&query, 5); + assert!(!before.is_empty(), "should have results before serialize"); + + let data = index.serialize().unwrap(); + let restored = HnswIndex::deserialize(&data).unwrap(); + + assert_eq!(restored.len(), index.len()); + assert_eq!(restored.max_layer, index.max_layer); + assert_eq!(restored.entry_point, index.entry_point); + + let after = restored.search(&query, 5); + assert_eq!( + before.iter().map(|(id, _)| id.clone()).collect::>(), + after.iter().map(|(id, _)| id.clone()).collect::>(), + "search results must be identical after round-trip" + ); + + // Similarity scores should match within floating-point tolerance + for (b, a) in before.iter().zip(after.iter()) { + assert!( + (b.1 - a.1).abs() < 1e-6, + "scores diverged: {} vs {}", + b.1, + a.1 + ); + } + } + + #[test] + fn test_legacy_format_backward_compat() { + // Build a legacy snapshot manually + let legacy = HnswSnapshotLegacy { + points: vec![ + (make_id(1), vec![1.0, 0.0, 0.0]), + (make_id(2), vec![0.0, 1.0, 0.0]), + ], + config: HnswConfig::default(), + }; + + let data = serde_json::to_vec(&legacy).unwrap(); + let restored = HnswIndex::deserialize(&data).unwrap(); + assert_eq!(restored.len(), 2); + + let results = restored.search(&[1.0, 0.0, 0.0], 1); + assert_eq!(results[0].0, make_id(1)); + } + + #[test] + fn test_entry_point_updates_on_higher_level() { + // Use a config with small m to increase chance of higher layers + let config = HnswConfig { m: 2, ef_construction: 10, ef_search: 10 }; + let mut index = HnswIndex::new(config); + + // Insert many points; at least one should get a level > 0 + for i in 0..50u8 { + let emb = vec![i as f32, (50 - i) as f32]; + index.insert(make_id(i), emb); + } + + // With m=2, max_layer should have grown beyond 0 + assert!( + index.max_layer >= 1, + "expected max_layer >= 1 after 50 inserts with m=2, got {}", + index.max_layer + ); + + // Verify entry point is at the max layer + if let Some(ep) = index.entry_point { + let ep_level = index.points[ep].neighbors.len().saturating_sub(1); + assert_eq!( + ep_level, index.max_layer, + "entry point level ({}) should equal max_layer ({})", + ep_level, index.max_layer + ); + } else { + panic!("entry_point should be Some after inserts"); + } + } + + #[test] + fn test_deletion_prunes_neighbor_lists() { + let config = HnswConfig { m: 4, ef_construction: 20, ef_search: 10 }; + let mut index = HnswIndex::new(config); + + // Insert several closely-related points so they appear in each other's + // neighbor lists + for i in 0..10u8 { + let emb = vec![i as f32, (10 - i) as f32, 1.0]; + index.insert(make_id(i), emb); + } + + // Pick a point to delete (not the entry point to keep things simple) + let victim_id = make_id(5); + let &victim_idx = index.id_to_index.get(&victim_id).unwrap(); + + index.remove(&victim_id); + + // Verify victim_idx does not appear in any neighbor list of any point + for (idx, point) in index.points.iter().enumerate() { + if idx == victim_idx { + continue; // skip the deleted point itself + } + for (layer, neighbors) in point.neighbors.iter().enumerate() { + assert!( + !neighbors.contains(&victim_idx), + "point {} layer {} still references deleted point {}", + idx, + layer, + victim_idx + ); + } + } + } + + #[test] + fn test_memory_stats_lower_bound() { + let mut index = HnswIndex::new(HnswConfig::default()); + + let dim = 128; + for i in 0..100u8 { + let mut emb = vec![0.0f32; dim]; + emb[i as usize % dim] = 1.0; + index.insert(make_id(i), emb); + } + + let stats = index.stats(); + assert_eq!(stats.point_count, 100); + assert_eq!(stats.dimensions, dim); + + // Conservative lower bound: 100 points * (32-byte MemoryId + 128*4 floats) + let lower_bound = 100 * (32 + dim * 4); + assert!( + stats.memory_bytes >= lower_bound, + "memory_bytes ({}) should be >= conservative lower bound ({})", + stats.memory_bytes, + lower_bound + ); + } +} diff --git a/crates/titans_memory/src/lib.rs b/crates/ineru/src/lib.rs similarity index 82% rename from crates/titans_memory/src/lib.rs rename to crates/ineru/src/lib.rs index 2e1927ba..927c822c 100644 --- a/crates/titans_memory/src/lib.rs +++ b/crates/ineru/src/lib.rs @@ -1,15 +1,18 @@ -//! # Titans Memory +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! # Ineru //! //! Neural-inspired memory system for AIngle AI agents. //! //! ## Architecture //! -//! Titans Memory implements a dual-memory architecture inspired by +//! Ineru implements a dual-memory architecture inspired by //! cognitive neuroscience and modern AI memory systems: //! //! ```text //! ┌─────────────────────────────────────────────────────────────┐ -//! │ Titans Memory System │ +//! │ Ineru Memory System │ //! ├─────────────────────────────────────────────────────────────┤ //! │ │ //! │ ┌──────────────────┐ ┌──────────────────────────────┐ │ @@ -36,10 +39,10 @@ //! ## Usage //! //! ```rust,ignore -//! use titans_memory::{TitansMemory, MemoryConfig, MemoryEntry}; +//! use ineru::{IneruMemory, MemoryConfig, MemoryEntry}; //! //! // Create memory system -//! let mut memory = TitansMemory::new(MemoryConfig::default()); +//! let mut memory = IneruMemory::new(MemoryConfig::default()); //! //! // Store in short-term memory //! let entry = MemoryEntry::new("sensor_data", json!({"temp": 23.5})); @@ -63,6 +66,7 @@ pub mod config; pub mod consolidation; pub mod error; +pub mod hnsw; pub mod ltm; pub mod stm; pub mod types; @@ -77,11 +81,11 @@ pub use types::{ MemoryQuery, MemoryResult, Relation, SemanticTag, }; -/// The main interface for the Titans Memory system. +/// The main interface for the Ineru memory system. /// /// This struct integrates a `ShortTermMemory` (STM) and a `LongTermMemory` (LTM) /// to provide a comprehensive, neural-inspired memory solution for AI agents. -pub struct TitansMemory { +pub struct IneruMemory { /// The fast, volatile, and bounded Short-Term Memory. pub stm: ShortTermMemory, /// The persistent, graph-based Long-Term Memory. @@ -92,8 +96,8 @@ pub struct TitansMemory { config: MemoryConfig, } -impl TitansMemory { - /// Creates a new `TitansMemory` system with the given configuration. +impl IneruMemory { + /// Creates a new `IneruMemory` system with the given configuration. /// /// # Arguments /// @@ -108,14 +112,14 @@ impl TitansMemory { } } - /// Creates a new `TitansMemory` system with defaults optimized for IoT devices. + /// Creates a new `IneruMemory` system with defaults optimized for IoT devices. /// /// This configuration prioritizes a low memory footprint. pub fn iot_mode() -> Self { Self::new(MemoryConfig::iot_mode()) } - /// Creates a new `TitansMemory` system with defaults optimized for general AI agents. + /// Creates a new `IneruMemory` system with defaults optimized for general AI agents. /// /// This configuration provides a balanced trade-off between performance and memory usage. pub fn agent_mode() -> Self { @@ -319,7 +323,7 @@ impl TitansMemory { } } -/// Provides statistics about the state of the `TitansMemory` system. +/// Provides statistics about the state of the `IneruMemory` system. #[derive(Debug, Clone)] pub struct MemoryStats { /// The number of entries currently in Short-Term Memory (STM). @@ -334,7 +338,76 @@ pub struct MemoryStats { pub total_memory_bytes: usize, } -impl Default for TitansMemory { +// --------------------------------------------------------------------------- +// Snapshot persistence +// --------------------------------------------------------------------------- + +/// A serializable snapshot of the Ineru memory state. +/// +/// Used to persist STM + LTM contents across process restarts. +#[derive(serde::Serialize, serde::Deserialize)] +struct IneruSnapshot { + stm_entries: Vec, + ltm_entries: Vec, + config: MemoryConfig, +} + +impl IneruMemory { + /// Exports the current memory state as a JSON byte vector. + pub fn export_snapshot(&self) -> Result> { + let stm_entries = self.stm.all_entries(); + let ltm_entries = self.ltm.all_entries(); + + let snapshot = IneruSnapshot { + stm_entries, + ltm_entries, + config: self.config.clone(), + }; + + serde_json::to_vec(&snapshot).map_err(|e| Error::internal(format!("snapshot export: {}", e))) + } + + /// Imports a memory state from a JSON byte slice. + pub fn import_snapshot(data: &[u8]) -> Result { + let snapshot: IneruSnapshot = serde_json::from_slice(data) + .map_err(|e| Error::internal(format!("snapshot import: {}", e)))?; + + let mut memory = Self::new(snapshot.config); + + // Restore STM entries + for entry in snapshot.stm_entries { + let _ = memory.stm.store(entry); + } + + // Restore LTM entries + for entry in snapshot.ltm_entries { + let _ = memory.ltm.store(entry); + } + + Ok(memory) + } + + /// Saves the current memory state to a file (with fsync for durability). + pub fn save_to_file(&self, path: &std::path::Path) -> Result<()> { + use std::io::Write; + let data = self.export_snapshot()?; + let mut file = std::fs::File::create(path) + .map_err(|e| Error::internal(format!("snapshot create: {}", e)))?; + file.write_all(&data) + .map_err(|e| Error::internal(format!("snapshot write: {}", e)))?; + file.sync_all() + .map_err(|e| Error::internal(format!("snapshot fsync: {}", e))) + } + + /// Loads a memory state from a file. + pub fn load_from_file(path: &std::path::Path) -> Result { + let data = std::fs::read(path) + .map_err(|e| Error::internal(format!("snapshot read: {}", e)))?; + Self::import_snapshot(&data) + } +} + +impl Default for IneruMemory { fn default() -> Self { Self::new(MemoryConfig::default()) } @@ -346,13 +419,13 @@ mod tests { #[test] fn test_memory_creation() { - let memory = TitansMemory::default(); + let memory = IneruMemory::default(); assert_eq!(memory.stats().stm_count, 0); } #[test] fn test_remember_recall() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("test", serde_json::json!({"value": 42})); let id = memory.remember(entry).unwrap(); @@ -363,21 +436,21 @@ mod tests { #[test] fn test_iot_mode() { - let memory = TitansMemory::iot_mode(); + let memory = IneruMemory::iot_mode(); // IoT mode has smaller capacity assert!(memory.config.stm.max_entries <= 100); } #[test] fn test_agent_mode() { - let memory = TitansMemory::agent_mode(); + let memory = IneruMemory::agent_mode(); // Agent mode has larger capacity assert!(memory.config.stm.max_entries >= 100); } #[test] fn test_remember_important() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("important", serde_json::json!({"critical": true})); let id = memory.remember_important(entry, 0.95).unwrap(); @@ -388,7 +461,7 @@ mod tests { #[test] fn test_recall_empty() { - let memory = TitansMemory::default(); + let memory = IneruMemory::default(); let query = MemoryQuery::text("anything"); let results = memory.recall(&query).unwrap(); assert!(results.is_empty()); @@ -396,7 +469,7 @@ mod tests { #[test] fn test_recall_with_limit() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); // Add multiple entries for i in 0..10 { @@ -412,7 +485,7 @@ mod tests { #[test] fn test_recall_text() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("sensor_data", serde_json::json!({"temp": 25.0})); memory.remember(entry).unwrap(); @@ -424,7 +497,7 @@ mod tests { #[test] fn test_recall_tagged() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let mut entry = MemoryEntry::new("tagged_entry", serde_json::json!({"data": 123})); entry.tags.push(SemanticTag::new("test_tag")); @@ -436,7 +509,7 @@ mod tests { #[test] fn test_recall_recent() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); for i in 0..5 { let entry = MemoryEntry::new(&format!("recent_{}", i), serde_json::json!({"n": i})); @@ -449,7 +522,7 @@ mod tests { #[test] fn test_consolidate() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); // Add some important entries for i in 0..3 { @@ -464,7 +537,7 @@ mod tests { #[test] fn test_consolidate_memory() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("to_consolidate", serde_json::json!({"data": 1})); let id = memory.remember(entry).unwrap(); @@ -478,7 +551,7 @@ mod tests { #[test] fn test_consolidate_nonexistent() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let fake_id = MemoryId::from_bytes([0u8; 32]); // Should not panic @@ -487,7 +560,7 @@ mod tests { #[test] fn test_forget() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("to_forget", serde_json::json!({"temp": 1})); let id = memory.remember(entry).unwrap(); @@ -504,7 +577,7 @@ mod tests { #[test] fn test_decay() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("decaying", serde_json::json!({"val": 1})); memory.remember_important(entry, 1.0).unwrap(); @@ -518,7 +591,7 @@ mod tests { #[test] fn test_prune_stm() { - let mut memory = TitansMemory::iot_mode(); // Smaller capacity + let mut memory = IneruMemory::iot_mode(); // Smaller capacity // Add many entries to exceed capacity for i in 0..200 { @@ -533,7 +606,7 @@ mod tests { #[test] fn test_stats() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); for i in 0..5 { let entry = MemoryEntry::new(&format!("stat_{}", i), serde_json::json!({"n": i})); @@ -577,7 +650,7 @@ mod tests { #[test] fn test_clear() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); for i in 0..5 { let entry = MemoryEntry::new(&format!("clear_{}", i), serde_json::json!({"n": i})); @@ -593,7 +666,7 @@ mod tests { #[test] fn test_get_from_ltm() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); let entry = MemoryEntry::new("ltm_entry", serde_json::json!({"data": 42})); let id = memory.remember(entry).unwrap(); @@ -610,7 +683,7 @@ mod tests { #[test] fn test_get_nonexistent() { - let memory = TitansMemory::default(); + let memory = IneruMemory::default(); let fake_id = MemoryId::from_bytes([99u8; 32]); let result = memory.get(&fake_id).unwrap(); @@ -619,7 +692,7 @@ mod tests { #[test] fn test_multiple_operations() { - let mut memory = TitansMemory::default(); + let mut memory = IneruMemory::default(); // Add entries let ids: Vec = (0..10) diff --git a/crates/titans_memory/src/ltm.rs b/crates/ineru/src/ltm.rs similarity index 86% rename from crates/titans_memory/src/ltm.rs rename to crates/ineru/src/ltm.rs index 4b57432a..158f75e7 100644 --- a/crates/titans_memory/src/ltm.rs +++ b/crates/ineru/src/ltm.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Long-Term Memory (LTM) with a Knowledge Graph. //! //! LTM stores persistent knowledge as a graph of entities and the relationships @@ -5,6 +8,7 @@ use crate::config::LtmConfig; use crate::error::{Error, Result}; +use crate::hnsw::{HnswConfig, HnswIndex}; use crate::types::{ Embedding, Entity, EntityId, Link, MemoryEntry, MemoryId, MemoryQuery, MemoryResult, MemorySource, Relation, SemanticTag, @@ -32,11 +36,19 @@ pub struct LongTermMemory { config: LtmConfig, /// A running estimate of the total memory used by the LTM. memory_usage: usize, + /// Optional HNSW index for fast vector search on memory embeddings. + hnsw_index: Option, } impl LongTermMemory { /// Creates a new, empty `LongTermMemory` with the given configuration. pub fn new(config: LtmConfig) -> Self { + let hnsw_index = if config.enable_embeddings { + Some(HnswIndex::new(HnswConfig::default())) + } else { + None + }; + Self { memories: HashMap::new(), entities: HashMap::new(), @@ -46,6 +58,7 @@ impl LongTermMemory { type_index: HashMap::new(), config, memory_usage: 0, + hnsw_index, } } @@ -85,6 +98,13 @@ impl LongTermMemory { .or_default() .insert(id.clone()); + // Insert into HNSW index if entry has an embedding + if let Some(ref emb) = entry.embedding { + if let Some(hnsw) = self.hnsw_index.as_mut() { + hnsw.insert(id.clone(), emb.0.clone()); + } + } + // Store self.memory_usage += entry.size_bytes(); self.memories.insert(id.clone(), entry); @@ -102,6 +122,11 @@ impl LongTermMemory { if let Some(entry) = self.memories.remove(id) { self.memory_usage = self.memory_usage.saturating_sub(entry.size_bytes()); + // Remove from HNSW index + if let Some(hnsw) = self.hnsw_index.as_mut() { + hnsw.remove(id); + } + // Update indices for tag in &entry.tags { if let Some(set) = self.tag_index.get_mut(tag) { @@ -331,6 +356,54 @@ impl LongTermMemory { scored } + /// Performs a vector search over memory entries using the HNSW index. + /// + /// This is much faster than brute-force `semantic_search` for large datasets. + /// Falls back to brute-force if HNSW index is not available. + pub fn vector_search_memories( + &self, + query: &[f32], + k: usize, + min_similarity: f32, + ) -> Vec<(&MemoryEntry, f32)> { + if let Some(ref hnsw) = self.hnsw_index { + let results = hnsw.search(query, k); + results + .into_iter() + .filter(|(_, sim)| *sim >= min_similarity) + .filter_map(|(id, sim)| { + self.memories.get(&id).map(|entry| (entry, sim)) + }) + .collect() + } else { + // Fallback to brute-force over memory entries with embeddings + let query_emb = Embedding::new(query.to_vec()); + let mut scored: Vec<_> = self.memories.values() + .filter_map(|entry| { + entry.embedding.as_ref().map(|emb| { + let sim = query_emb.cosine_similarity(emb); + (entry, sim) + }) + }) + .filter(|(_, sim)| *sim >= min_similarity) + .collect(); + + scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); + scored.truncate(k); + scored + } + } + + /// Get a reference to the HNSW index, if available. + pub fn hnsw_index(&self) -> Option<&HnswIndex> { + self.hnsw_index.as_ref() + } + + /// Get a mutable reference to the HNSW index, if available. + pub fn hnsw_index_mut(&mut self) -> Option<&mut HnswIndex> { + self.hnsw_index.as_mut() + } + // ============ Statistics ============ /// Returns the number of memory entries stored in the LTM. @@ -338,6 +411,11 @@ impl LongTermMemory { self.memories.len() } + /// Returns all memory entries stored in the LTM. + pub fn all_entries(&self) -> Vec { + self.memories.values().cloned().collect() + } + /// Returns the number of entities in the knowledge graph. pub fn entity_count(&self) -> usize { self.entities.len() @@ -362,6 +440,9 @@ impl LongTermMemory { self.tag_index.clear(); self.type_index.clear(); self.memory_usage = 0; + if let Some(hnsw) = self.hnsw_index.as_mut() { + hnsw.rebuild(); // clears all points + } Ok(()) } diff --git a/crates/titans_memory/src/stm.rs b/crates/ineru/src/stm.rs similarity index 98% rename from crates/titans_memory/src/stm.rs rename to crates/ineru/src/stm.rs index e9b1d2e5..435a3898 100644 --- a/crates/titans_memory/src/stm.rs +++ b/crates/ineru/src/stm.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Short-Term Memory (STM) with attention-based weighting. //! //! STM provides fast, volatile access to recent memories. It uses an attention-based @@ -252,6 +255,11 @@ impl ShortTermMemory { Ok(()) } + /// Returns all entries currently in the STM. + pub fn all_entries(&self) -> Vec { + self.entries.values().cloned().collect() + } + /// Returns the number of entries currently in the STM. pub fn len(&self) -> usize { self.entries.len() diff --git a/crates/titans_memory/src/types.rs b/crates/ineru/src/types.rs similarity index 99% rename from crates/titans_memory/src/types.rs rename to crates/ineru/src/types.rs index 3a80ca31..db160588 100644 --- a/crates/titans_memory/src/types.rs +++ b/crates/ineru/src/types.rs @@ -1,4 +1,7 @@ -//! Core data types for the Titans Memory system. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Core data types for the Ineru memory system. use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/crates/hope_agents/COMPLETION_SUMMARY.md b/crates/kaneru/COMPLETION_SUMMARY.md similarity index 87% rename from crates/hope_agents/COMPLETION_SUMMARY.md rename to crates/kaneru/COMPLETION_SUMMARY.md index 3faba21b..97a0aa8d 100644 --- a/crates/hope_agents/COMPLETION_SUMMARY.md +++ b/crates/kaneru/COMPLETION_SUMMARY.md @@ -1,11 +1,11 @@ -# HOPE Agents - 100% Completion Summary +# Kaneru - 100% Completion Summary **Date**: 2025-12-17 **Status**: ✅ **100% COMPLETE** ## Overview -HOPE Agents has been completed to 100% with all requested features fully implemented, tested, and documented. The framework provides a comprehensive reinforcement learning system for autonomous AI agents with multi-agent coordination, state persistence, and advanced learning capabilities. +Kaneru has been completed to 100% with all requested features fully implemented, tested, and documented. The framework provides a comprehensive reinforcement learning system for autonomous AI agents with multi-agent coordination, state persistence, and advanced learning capabilities. ## Completion Status (100%) @@ -39,7 +39,7 @@ HOPE Agents has been completed to 100% with all requested features fully impleme - Configurable sensitivity - Real-time anomaly scoring -5. **Orchestrator (HopeAgent)** - Main agent coordination +5. **Orchestrator (KaneruAgent)** - Main agent coordination - Integration of all components - Multiple operation modes - Episode management @@ -106,12 +106,12 @@ Features: - Configurable intervals **Implementations**: -- `HopeAgent` persistence (full state serialization) +- `KaneruAgent` persistence (full state serialization) - `LearningEngine` persistence (Q-values, episodes, config) -- `SimpleAgent` persistence (through HopeAgent) +- `SimpleAgent` persistence (through KaneruAgent) **Tests**: 8 comprehensive unit tests, all passing -- Save/load roundtrip for HopeAgent +- Save/load roundtrip for KaneruAgent - Different format options - Byte serialization - Learning engine persistence @@ -141,7 +141,7 @@ Features: Test Coverage: 1. Simple agent workflow -2. HOPE agent learning cycle +2. Kaneru agent learning cycle 3. Multi-agent coordination 4. Consensus mechanism 5. Agent persistence (save/load) @@ -169,7 +169,7 @@ Test Coverage: - **Coordination module**: 10 tests ✨ NEW - Goal module: 10 tests - Hierarchical module: 19 tests - - HOPE agent module: 10 tests + - Kaneru agent module: 10 tests - Learning module: 22 tests - Observation module: 3 tests - **Persistence module**: 8 tests ✨ NEW @@ -260,8 +260,8 @@ All new types properly exported and documented in the public API. ```rust let mut coordinator = AgentCoordinator::new(); -let id1 = coordinator.register_agent(HopeAgent::with_default_config()); -let id2 = coordinator.register_agent(HopeAgent::with_default_config()); +let id1 = coordinator.register_agent(KaneruAgent::with_default_config()); +let id2 = coordinator.register_agent(KaneruAgent::with_default_config()); // Broadcast to all agents coordinator.broadcast(Message::new("update", "System status")); @@ -277,7 +277,7 @@ let result = coordinator.get_consensus(&proposal); ### State Persistence ```rust -let agent = HopeAgent::with_default_config(); +let agent = KaneruAgent::with_default_config(); // Train agent... @@ -285,7 +285,7 @@ let agent = HopeAgent::with_default_config(); agent.save_to_file(Path::new("agent.json")).unwrap(); // Load from file -let loaded = HopeAgent::load_from_file(Path::new("agent.json")).unwrap(); +let loaded = KaneruAgent::load_from_file(Path::new("agent.json")).unwrap(); // Checkpoint manager let mut manager = CheckpointManager::new(Path::new("checkpoints"), 5) @@ -367,17 +367,17 @@ All requested deliverables have been completed: ## Files Created/Modified ### New Files (5) -1. `/crates/hope_agents/src/coordination.rs` - Multi-agent coordination -2. `/crates/hope_agents/src/persistence.rs` - State persistence -3. `/crates/hope_agents/tests/integration_test.rs` - Integration tests -4. `/crates/hope_agents/examples/complete_demo.rs` - Complete demo -5. `/crates/hope_agents/README.md` - Documentation +1. `/crates/kaneru/src/coordination.rs` - Multi-agent coordination +2. `/crates/kaneru/src/persistence.rs` - State persistence +3. `/crates/kaneru/tests/integration_test.rs` - Integration tests +4. `/crates/kaneru/examples/complete_demo.rs` - Complete demo +5. `/crates/kaneru/README.md` - Documentation ### Modified Files (1) -1. `/crates/hope_agents/src/lib.rs` - Updated exports and documentation +1. `/crates/kaneru/src/lib.rs` - Updated exports and documentation ### Generated Files (1) -1. `/crates/hope_agents/COMPLETION_SUMMARY.md` - This summary +1. `/crates/kaneru/COMPLETION_SUMMARY.md` - This summary ## Quality Metrics @@ -404,7 +404,7 @@ The framework is complete but could be extended with: ## Conclusion -HOPE Agents is now **100% complete** with all core features implemented, tested, and documented. The framework provides: +Kaneru is now **100% complete** with all core features implemented, tested, and documented. The framework provides: - ✅ Complete reinforcement learning system - ✅ Multi-agent coordination with consensus diff --git a/crates/hope_agents/Cargo.toml b/crates/kaneru/Cargo.toml similarity index 72% rename from crates/hope_agents/Cargo.toml rename to crates/kaneru/Cargo.toml index bf69c40e..f5f6175c 100644 --- a/crates/hope_agents/Cargo.toml +++ b/crates/kaneru/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "hope_agents" -version = "0.2.0" -description = "HOPE Agents: Hierarchical Optimizing Policy Engine for AIngle AI agents" -license = "Apache-2.0" +name = "kaneru" +version = "0.6.3" +description = "Kaneru: Unified Multi-Agent Execution System for AIngle AI agents" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" homepage = "https://apilium.com" -documentation = "https://docs.rs/hope_agents" +documentation = "https://docs.rs/kaneru" authors = ["Apilium Technologies "] keywords = ["aingle", "ai", "agents", "rl", "iot"] categories = ["science", "network-programming"] @@ -15,8 +15,8 @@ rust-version = "1.83" [features] default = ["std"] std = [] -# Enable Titans Memory integration -memory = ["titans_memory"] +# Enable Ineru memory integration +memory = ["ineru"] # Enable async operations async = [] # Enable learning capabilities @@ -31,7 +31,7 @@ serde_json = "1.0" log = "0.4" # AI Memory integration -titans_memory = { version = "0.2", path = "../titans_memory", optional = true } +ineru = { version = "0.6", path = "../ineru", optional = true } # Random for exploration (updated from 0.7) rand = { version = "0.9", default-features = false, features = ["std", "thread_rng"] } diff --git a/crates/hope_agents/IMPLEMENTATION_SUMMARY.md b/crates/kaneru/IMPLEMENTATION_SUMMARY.md similarity index 86% rename from crates/hope_agents/IMPLEMENTATION_SUMMARY.md rename to crates/kaneru/IMPLEMENTATION_SUMMARY.md index 461c2d7c..d5abaa6c 100644 --- a/crates/hope_agents/IMPLEMENTATION_SUMMARY.md +++ b/crates/kaneru/IMPLEMENTATION_SUMMARY.md @@ -1,15 +1,15 @@ -# HOPE Orchestrator Implementation Summary +# Kaneru Orchestrator Implementation Summary ## Overview -Successfully implemented the HOPE (Hierarchical, Optimistic, Predictive, Emergent) Agent Orchestrator that integrates all HOPE Agents modules into a unified intelligent agent system. +Successfully implemented the Kaneru (Unified Multi-Agent Execution System) Agent Orchestrator that integrates all Kaneru modules into a unified intelligent agent system. ## Files Created/Modified ### Core Implementation -1. **`src/hope_agent.rs`** (NEW - 878 lines) - - Main `HopeAgent` struct integrating all modules +1. **`src/kaneru_agent.rs`** (NEW - 878 lines) + - Main `KaneruAgent` struct integrating all modules - Operation modes: Exploration, Exploitation, GoalDriven, Adaptive - Complete step-learn cycle - Goal management and integration @@ -19,8 +19,8 @@ Successfully implemented the HOPE (Hierarchical, Optimistic, Predictive, Emergen - 10 unit tests covering all major features 2. **`src/lib.rs`** (MODIFIED) - - Added `pub mod hope_agent` - - Exported all public types from hope_agent module + - Added `pub mod kaneru_agent` + - Exported all public types from kaneru_agent module 3. **`src/learning/engine.rs`** (MODIFIED) - Added `config_mut()` method for mutable configuration access @@ -30,7 +30,7 @@ Successfully implemented the HOPE (Hierarchical, Optimistic, Predictive, Emergen ### Documentation -5. **`HOPE_ORCHESTRATOR.md`** (NEW - 370 lines) +5. **`KANERU_ORCHESTRATOR.md`** (NEW - 370 lines) - Comprehensive documentation - Architecture diagrams - API reference @@ -47,7 +47,7 @@ Successfully implemented the HOPE (Hierarchical, Optimistic, Predictive, Emergen ### Examples -7. **`examples/hope_orchestrator.rs`** (NEW - 265 lines) +7. **`examples/kaneru_orchestrator.rs`** (NEW - 265 lines) - Basic agent usage example - Goal-driven behavior example - Multi-episode learning example @@ -57,10 +57,10 @@ Successfully implemented the HOPE (Hierarchical, Optimistic, Predictive, Emergen ## Key Features Implemented -### 1. Core Orchestrator (`HopeAgent`) +### 1. Core Orchestrator (`KaneruAgent`) ```rust -pub struct HopeAgent { +pub struct KaneruAgent { // Core components learning: LearningEngine, // Q-Learning, SARSA, TD goal_solver: HierarchicalGoalSolver, // Goal management @@ -75,7 +75,7 @@ pub struct HopeAgent { action_history: VecDeque, // Configuration & stats - config: HopeConfig, + config: KaneruConfig, stats: AgentStats, } ``` @@ -87,7 +87,7 @@ pub struct HopeAgent { - **GoalDriven**: Balanced exploration (ε=0.1) focused on goals - **Adaptive**: Dynamic adjustment based on performance -### 3. HOPE Cycle +### 3. Kaneru Cycle ``` Observation → State Update → Anomaly Check → Goal Update → @@ -145,7 +145,7 @@ pub struct AgentStats { ### 9. Configuration ```rust -pub struct HopeConfig { +pub struct KaneruConfig { learning: LearningConfig, predictive: PredictiveConfig, mode: OperationMode, @@ -163,7 +163,7 @@ pub struct HopeConfig { All 100 tests pass successfully: -- **hope_agent module**: 10 tests +- **kaneru_agent module**: 10 tests - Agent creation and configuration - Step-learn cycle - Goal integration @@ -187,7 +187,7 @@ All 100 tests pass successfully: cargo test # Specific module -cargo test hope_agent +cargo test kaneru_agent # With output cargo test -- --nocapture @@ -200,7 +200,7 @@ cargo test --release ### AIngle Minimal Integration -The HOPE Agent can integrate with aingle_minimal for: +The Kaneru Agent can integrate with aingle_minimal for: 1. **Observations from Records** ```rust @@ -237,7 +237,7 @@ The HOPE Agent can integrate with aingle_minimal for: ### IoT/Embedded ```rust -HopeConfig { +KaneruConfig { max_observations: 100, max_actions: 100, learning: LearningConfig { @@ -251,7 +251,7 @@ HopeConfig { ### Server/Cloud ```rust -HopeConfig { +KaneruConfig { max_observations: 10000, max_actions: 10000, learning: LearningConfig { @@ -267,7 +267,7 @@ HopeConfig { ### Basic Loop ```rust -let mut agent = HopeAgent::with_default_config(); +let mut agent = KaneruAgent::with_default_config(); loop { let obs = get_observation(); @@ -301,7 +301,7 @@ Potential improvements documented for future work: 1. **Deep Q-Networks (DQN)**: Neural network approximation for continuous spaces 2. **Actor-Critic**: Policy gradient methods -3. **Multi-Agent**: Coordination between multiple HOPE agents +3. **Multi-Agent**: Coordination between multiple Kaneru agents 4. **Hierarchical RL**: More sophisticated goal decomposition 5. **Transfer Learning**: Knowledge transfer between tasks 6. **Meta-Learning**: Faster adaptation to new environments @@ -320,16 +320,16 @@ Potential improvements documented for future work: ## Files Summary ``` -hope_agents/ +kaneru/ ├── src/ -│ ├── hope_agent.rs (878 lines) - Main orchestrator +│ ├── kaneru_agent.rs (878 lines) - Main orchestrator │ ├── learning/ (Existing - Q-Learning, SARSA, TD) │ ├── hierarchical/ (Existing - Goal management) │ ├── predictive/ (Existing - Forecasting, anomalies) │ └── lib.rs (Modified - exports) ├── examples/ -│ └── hope_orchestrator.rs (265 lines) - Complete examples -├── HOPE_ORCHESTRATOR.md (370 lines) - Full documentation +│ └── kaneru_orchestrator.rs (265 lines) - Complete examples +├── KANERU_ORCHESTRATOR.md (370 lines) - Full documentation ├── QUICK_START.md (290 lines) - Quick reference └── IMPLEMENTATION_SUMMARY.md (This file) ``` @@ -351,13 +351,13 @@ cargo test ### Examples ```bash -cargo run --example hope_orchestrator +cargo run --example kaneru_orchestrator # All examples run successfully ``` ## Conclusion -The HOPE Orchestrator successfully integrates all HOPE Agents modules into a unified, production-ready intelligent agent system. The implementation includes: +The Kaneru Orchestrator successfully integrates all Kaneru modules into a unified, production-ready intelligent agent system. The implementation includes: - ✅ Complete integration of learning, hierarchical, and predictive modules - ✅ 4 operation modes (Exploration, Exploitation, GoalDriven, Adaptive) diff --git a/crates/hope_agents/HOPE_ORCHESTRATOR.md b/crates/kaneru/KANERU_ORCHESTRATOR.md similarity index 92% rename from crates/hope_agents/HOPE_ORCHESTRATOR.md rename to crates/kaneru/KANERU_ORCHESTRATOR.md index 3aa4a4b0..6db45e20 100644 --- a/crates/hope_agents/HOPE_ORCHESTRATOR.md +++ b/crates/kaneru/KANERU_ORCHESTRATOR.md @@ -1,10 +1,10 @@ -# HOPE Agent Orchestrator +# Kaneru Agent Orchestrator -The HOPE Agent Orchestrator integrates all HOPE (Hierarchical, Optimistic, Predictive, Emergent) components into a unified intelligent agent system. +The Kaneru Agent Orchestrator integrates all Kaneru (Unified Multi-Agent Execution System) components into a unified intelligent agent system. ## Overview -The HOPE Agent is a complete autonomous agent that combines: +The Kaneru Agent is a complete autonomous agent that combines: - **Learning Engine** (Q-Learning, SARSA, TD) - Learn from experience - **Hierarchical Goal Solver** - Manage and decompose complex goals @@ -15,7 +15,7 @@ The HOPE Agent is a complete autonomous agent that combines: ``` ┌─────────────────────────────────────────────────────────┐ -│ HOPE Agent │ +│ Kaneru Agent │ ├─────────────────────────────────────────────────────────┤ │ │ │ Observation → State → Decision → Action → Learning │ @@ -34,24 +34,24 @@ The HOPE Agent is a complete autonomous agent that combines: ## Core Components -### 1. HOPE Agent (`HopeAgent`) +### 1. Kaneru Agent (`KaneruAgent`) Main orchestrator that integrates all modules. ```rust -use hope_agents::{HopeAgent, HopeConfig}; +use kaneru::{KaneruAgent, KaneruConfig}; // Create with default config -let mut agent = HopeAgent::with_default_config(); +let mut agent = KaneruAgent::with_default_config(); // Or with custom config -let config = HopeConfig { +let config = KaneruConfig { mode: OperationMode::GoalDriven, learning: LearningConfig { /* ... */ }, predictive: PredictiveConfig { /* ... */ }, // ... }; -let mut agent = HopeAgent::new(config); +let mut agent = KaneruAgent::new(config); ``` ### 2. Operation Modes @@ -76,7 +76,7 @@ let mode = agent.mode(); Set and track goals: ```rust -use hope_agents::{Goal, Priority}; +use kaneru::{Goal, Priority}; // Create goals let goal = Goal::maintain("temperature", 18.0..22.0) @@ -96,7 +96,7 @@ if let Some(goal) = agent.current_goal() { Main interaction loop: ```rust -use hope_agents::{Observation, ActionResult, Outcome}; +use kaneru::{Observation, ActionResult, Outcome}; // 1. Agent observes environment let obs = Observation::sensor("temperature", 22.5); @@ -148,7 +148,7 @@ Complex goals can be decomposed: ```rust // Set auto-decomposition in config -let config = HopeConfig { +let config = KaneruConfig { auto_decompose_goals: true, // ... }; @@ -170,7 +170,7 @@ let state = agent.save_state(); let json = serde_json::to_string(&state)?; // Create new agent and restore -let mut new_agent = HopeAgent::with_default_config(); +let mut new_agent = KaneruAgent::with_default_config(); new_agent.load_state(state); ``` @@ -192,10 +192,10 @@ println!("Current epsilon: {:.3}", stats.current_epsilon); ## Configuration Options -### HopeConfig +### KaneruConfig ```rust -pub struct HopeConfig { +pub struct KaneruConfig { /// Learning configuration pub learning: LearningConfig, @@ -291,7 +291,7 @@ let history = predictive.history(); ### Network Operations ```rust -use hope_agents::{ActionType, Observation}; +use kaneru::{ActionType, Observation}; // Observe network events let obs = Observation::network("peer_connected", "peer_123"); @@ -342,7 +342,7 @@ fn record_to_observation(record: &Record) -> Observation { ## Examples -See `examples/hope_orchestrator.rs` for complete examples: +See `examples/kaneru_orchestrator.rs` for complete examples: - Basic agent usage - Goal-driven behavior @@ -354,7 +354,7 @@ See `examples/hope_orchestrator.rs` for complete examples: Run examples: ```bash -cargo run --example hope_orchestrator +cargo run --example kaneru_orchestrator ``` ## Testing @@ -362,7 +362,7 @@ cargo run --example hope_orchestrator Run tests: ```bash -cargo test hope_agent +cargo test kaneru_agent ``` All major features are tested: diff --git a/crates/hope_agents/QUICK_START.md b/crates/kaneru/QUICK_START.md similarity index 91% rename from crates/hope_agents/QUICK_START.md rename to crates/kaneru/QUICK_START.md index ac72246a..d2c40e60 100644 --- a/crates/hope_agents/QUICK_START.md +++ b/crates/kaneru/QUICK_START.md @@ -1,6 +1,6 @@ -# HOPE Agent Quick Start Guide +# Kaneru Quick Start Guide -Get started with HOPE Agents in 5 minutes. +Get started with Kaneru in 5 minutes. ## Installation @@ -8,17 +8,17 @@ Add to your `Cargo.toml`: ```toml [dependencies] -hope_agents = { path = "../hope_agents" } +kaneru = { path = "../kaneru" } ``` ## Basic Usage ```rust -use hope_agents::{HopeAgent, Observation, ActionResult, Outcome}; +use kaneru::{KaneruAgent, Observation, ActionResult, Outcome}; fn main() { // 1. Create agent - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // 2. Main loop loop { @@ -57,7 +57,7 @@ fn main() { ## With Goals ```rust -use hope_agents::{Goal, Priority}; +use kaneru::{Goal, Priority}; // Set a goal let goal = Goal::maintain("temperature", 18.0..22.0) @@ -75,7 +75,7 @@ if let Some(current) = agent.current_goal() { ## Operation Modes ```rust -use hope_agents::OperationMode; +use kaneru::OperationMode; // Exploration: Learn new behaviors agent.set_mode(OperationMode::Exploration); @@ -166,9 +166,9 @@ Goal::perform("calibrate_sensors") ## Configuration ```rust -use hope_agents::{HopeConfig, LearningConfig, LearningAlgorithm}; +use kaneru::{KaneruConfig, LearningConfig, LearningAlgorithm}; -let config = HopeConfig { +let config = KaneruConfig { mode: OperationMode::GoalDriven, learning: LearningConfig { learning_rate: 0.15, @@ -181,7 +181,7 @@ let config = HopeConfig { ..Default::default() }; -let agent = HopeAgent::new(config); +let agent = KaneruAgent::new(config); ``` ## Reward Design @@ -306,8 +306,8 @@ if agent.get_statistics().total_steps > 10000 { ## Next Steps -- Read [HOPE_ORCHESTRATOR.md](HOPE_ORCHESTRATOR.md) for detailed documentation -- Check [examples/hope_orchestrator.rs](examples/hope_orchestrator.rs) for complete examples +- Read [KANERU_ORCHESTRATOR.md](KANERU_ORCHESTRATOR.md) for detailed documentation +- Check [examples/kaneru_orchestrator.rs](examples/kaneru_orchestrator.rs) for complete examples - Explore individual modules: learning, hierarchical, predictive - Integrate with AIngle network operations @@ -329,4 +329,4 @@ if agent.get_statistics().total_steps > 10000 { - [Hierarchical Goals Documentation](src/hierarchical/mod.rs) - [Predictive Model Documentation](src/predictive/mod.rs) - Run tests: `cargo test` -- Run examples: `cargo run --example hope_orchestrator` +- Run examples: `cargo run --example kaneru_orchestrator` diff --git a/crates/hope_agents/README.md b/crates/kaneru/README.md similarity index 83% rename from crates/hope_agents/README.md rename to crates/kaneru/README.md index d8960c52..265f699d 100644 --- a/crates/hope_agents/README.md +++ b/crates/kaneru/README.md @@ -25,6 +25,6 @@ --- -# HOPE Agents +# Kaneru -This crate implements the Hierarchical Optimistic Policy Engine (HOPE) for autonomous decision-making within the AIngle framework, providing reinforcement learning capabilities for agents. \ No newline at end of file +This crate implements the Unified Multi-Agent Execution System (Kaneru) for autonomous decision-making within the AIngle framework, providing reinforcement learning capabilities for agents. \ No newline at end of file diff --git a/crates/hope_agents/README_HOPE_AGENT.md b/crates/kaneru/README_KANERU_AGENT.md similarity index 73% rename from crates/hope_agents/README_HOPE_AGENT.md rename to crates/kaneru/README_KANERU_AGENT.md index 91e13681..55ab7614 100644 --- a/crates/hope_agents/README_HOPE_AGENT.md +++ b/crates/kaneru/README_KANERU_AGENT.md @@ -1,12 +1,12 @@ -# HOPE Agent Orchestrator - Integration Complete +# Kaneru Agent Orchestrator - Integration Complete ## Summary -The HOPE Agent Orchestrator has been successfully implemented and integrated into the hope_agents crate. This provides a complete, production-ready intelligent agent system for the AIngle ecosystem. +The Kaneru Agent Orchestrator has been successfully implemented and integrated into the kaneru crate. This provides a complete, production-ready intelligent agent system for the AIngle ecosystem. ## What Was Built -### 1. Core Module: `hope_agent.rs` (876 lines) +### 1. Core Module: `kaneru_agent.rs` (876 lines) Complete orchestrator that integrates: - **Learning Engine**: Q-Learning, SARSA, TD, Experience Replay @@ -24,13 +24,13 @@ Key features: ### 2. Documentation (1,112 lines total) -- **HOPE_ORCHESTRATOR.md** (405 lines): Complete technical documentation +- **KANERU_ORCHESTRATOR.md** (405 lines): Complete technical documentation - **QUICK_START.md** (332 lines): Quick start guide with recipes - **IMPLEMENTATION_SUMMARY.md** (375 lines): Implementation details ### 3. Examples (303 lines) -- **examples/hope_orchestrator.rs**: 6 working examples demonstrating all features +- **examples/kaneru_orchestrator.rs**: 6 working examples demonstrating all features ## Test Results @@ -44,28 +44,28 @@ Key features: ## File Structure ``` -hope_agents/ +kaneru/ ├── src/ -│ ├── hope_agent.rs ← NEW: Main orchestrator +│ ├── kaneru_agent.rs ← NEW: Main orchestrator │ ├── learning/ ← Existing: Integrated │ ├── hierarchical/ ← Existing: Integrated │ ├── predictive/ ← Existing: Integrated │ └── lib.rs ← Modified: Exports added ├── examples/ -│ └── hope_orchestrator.rs ← NEW: Complete examples -├── HOPE_ORCHESTRATOR.md ← NEW: Full documentation +│ └── kaneru_orchestrator.rs ← NEW: Complete examples +├── KANERU_ORCHESTRATOR.md ← NEW: Full documentation ├── QUICK_START.md ← NEW: Quick reference ├── IMPLEMENTATION_SUMMARY.md ← NEW: Implementation details -└── README_HOPE_AGENT.md ← This file +└── README_KANERU_AGENT.md ← This file ``` ## Quick Usage ```rust -use hope_agents::{HopeAgent, Observation, ActionResult, Outcome}; +use kaneru::{KaneruAgent, Observation, ActionResult, Outcome}; // Create agent -let mut agent = HopeAgent::with_default_config(); +let mut agent = KaneruAgent::with_default_config(); // Main loop loop { @@ -90,7 +90,7 @@ loop { ```rust // Agent -HopeAgent::with_default_config() -> HopeAgent +KaneruAgent::with_default_config() -> KaneruAgent agent.step(observation) -> Action agent.learn(outcome) agent.set_goal(goal) -> GoalId @@ -100,7 +100,7 @@ agent.save_state() -> SerializedState agent.load_state(state) // Configuration -HopeConfig { +KaneruConfig { mode: OperationMode, learning: LearningConfig, predictive: PredictiveConfig, @@ -121,7 +121,7 @@ Outcome::new(action, result, reward, new_obs, done) ## Integration with AIngle -The HOPE Agent is ready to integrate with aingle_minimal: +The Kaneru Agent is ready to integrate with aingle_minimal: ```rust // Observations from network events @@ -139,9 +139,9 @@ match action.action_type { ## Documentation Quick Links 1. **Getting Started**: See [QUICK_START.md](QUICK_START.md) -2. **Full Documentation**: See [HOPE_ORCHESTRATOR.md](HOPE_ORCHESTRATOR.md) +2. **Full Documentation**: See [KANERU_ORCHESTRATOR.md](KANERU_ORCHESTRATOR.md) 3. **Implementation Details**: See [IMPLEMENTATION_SUMMARY.md](IMPLEMENTATION_SUMMARY.md) -4. **Examples**: Run `cargo run --example hope_orchestrator` +4. **Examples**: Run `cargo run --example kaneru_orchestrator` ## Testing @@ -149,14 +149,14 @@ match action.action_type { # Run all tests cargo test -# Run HOPE agent tests only -cargo test hope_agent +# Run Kaneru agent tests only +cargo test kaneru_agent # Run in release mode cargo test --release # Run examples -cargo run --example hope_orchestrator +cargo run --example kaneru_orchestrator ``` ## Key Features Checklist @@ -184,16 +184,16 @@ cargo run --example hope_orchestrator ## Next Steps 1. **Read the documentation**: Start with [QUICK_START.md](QUICK_START.md) -2. **Run the examples**: `cargo run --example hope_orchestrator` +2. **Run the examples**: `cargo run --example kaneru_orchestrator` 3. **Integrate with AIngle**: Use the agent for network operations 4. **Customize**: Adjust configuration for your use case 5. **Monitor**: Track statistics to optimize performance ## Support -- **Code**: `/Users/carlostovar/aingle/aingle/crates/hope_agents/src/hope_agent.rs` -- **Tests**: Run `cargo test hope_agent` -- **Examples**: `/Users/carlostovar/aingle/aingle/crates/hope_agents/examples/hope_orchestrator.rs` +- **Code**: `/Users/carlostovar/aingle/aingle/crates/kaneru/src/kaneru_agent.rs` +- **Tests**: Run `cargo test kaneru_agent` +- **Examples**: `/Users/carlostovar/aingle/aingle/crates/kaneru/examples/kaneru_orchestrator.rs` - **Docs**: All markdown files in this directory ## Version @@ -205,7 +205,7 @@ cargo run --example hope_orchestrator ## Credits -Implements HOPE (Hierarchical, Optimistic, Predictive, Emergent) architecture for autonomous agents in the AIngle distributed system. +Implements Kaneru (Unified Multi-Agent Execution System) architecture for autonomous agents in the AIngle distributed system. --- diff --git a/crates/hope_agents/benches/agent_bench.rs b/crates/kaneru/benches/agent_bench.rs similarity index 96% rename from crates/hope_agents/benches/agent_bench.rs rename to crates/kaneru/benches/agent_bench.rs index 0bc7f9c0..197a34f5 100644 --- a/crates/hope_agents/benches/agent_bench.rs +++ b/crates/kaneru/benches/agent_bench.rs @@ -1,9 +1,12 @@ -//! Benchmarks for HOPE Agents +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Benchmarks for Kaneru //! -//! Run with: cargo bench -p hope_agents +//! Run with: cargo bench -p kaneru use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; -use hope_agents::{Action, Agent, AgentConfig, Condition, Goal, Observation, Rule, SimpleAgent}; +use kaneru::{Action, Agent, AgentConfig, Condition, Goal, Observation, Rule, SimpleAgent}; /// Benchmark agent creation fn bench_agent_creation(c: &mut Criterion) { diff --git a/crates/hope_agents/examples/complete_demo.rs b/crates/kaneru/examples/complete_demo.rs similarity index 92% rename from crates/hope_agents/examples/complete_demo.rs rename to crates/kaneru/examples/complete_demo.rs index a4bca8d2..ab2aa6e6 100644 --- a/crates/hope_agents/examples/complete_demo.rs +++ b/crates/kaneru/examples/complete_demo.rs @@ -1,6 +1,9 @@ -//! Complete HOPE Agents Demonstration +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Complete Kaneru Demonstration //! -//! This example demonstrates all major features of the HOPE Agents framework: +//! This example demonstrates all major features of the Kaneru framework: //! - Learning with different algorithms //! - Hierarchical goal management //! - Multi-agent coordination @@ -9,11 +12,11 @@ //! //! Run with: cargo run --example complete_demo -use hope_agents::*; +use kaneru::*; use std::collections::HashMap; fn main() { - println!("=== HOPE Agents Complete Demo ===\n"); + println!("=== Kaneru Complete Demo ===\n"); demo_simple_agent(); demo_learning_agent(); @@ -77,7 +80,7 @@ fn demo_simple_agent() { fn demo_learning_agent() { println!("--- 2. Learning Agent (Q-Learning) ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Set a goal let goal = Goal::maintain("temperature", 20.0..25.0).with_priority(Priority::High); @@ -130,7 +133,7 @@ fn demo_learning_agent() { fn demo_hierarchical_goals() { println!("--- 3. Hierarchical Goal Management ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Create multiple goals with different priorities let goal1 = Goal::maintain("temperature", 20.0..25.0).with_priority(Priority::High); @@ -170,9 +173,9 @@ fn demo_multi_agent_coordination() { // Create and register agents println!(" Registering 3 agents..."); - let agent1 = HopeAgent::with_default_config(); - let agent2 = HopeAgent::with_default_config(); - let agent3 = HopeAgent::with_default_config(); + let agent1 = KaneruAgent::with_default_config(); + let agent2 = KaneruAgent::with_default_config(); + let agent3 = KaneruAgent::with_default_config(); let id1 = coordinator.register_agent(agent1); let id2 = coordinator.register_agent(agent2); @@ -257,7 +260,7 @@ fn demo_multi_agent_coordination() { fn demo_persistence() { println!("--- 5. State Persistence ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Train the agent briefly println!(" Training agent..."); @@ -279,7 +282,7 @@ fn demo_persistence() { // Load from bytes println!(" Deserializing agent state..."); - let loaded_agent = HopeAgent::from_bytes(&bytes).unwrap(); + let loaded_agent = KaneruAgent::from_bytes(&bytes).unwrap(); println!( " Loaded agent with {} steps", loaded_agent.get_statistics().total_steps @@ -293,7 +296,7 @@ fn demo_persistence() { fn demo_anomaly_detection() { println!("--- 6. Anomaly Detection ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Establish normal pattern println!(" Establishing normal pattern (20-25°C)..."); diff --git a/crates/hope_agents/examples/hierarchical_goals.rs b/crates/kaneru/examples/hierarchical_goals.rs similarity index 97% rename from crates/hope_agents/examples/hierarchical_goals.rs rename to crates/kaneru/examples/hierarchical_goals.rs index 77a7fdc2..d0bff02a 100644 --- a/crates/hope_agents/examples/hierarchical_goals.rs +++ b/crates/kaneru/examples/hierarchical_goals.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Example demonstrating the Hierarchical Goal Solver //! //! This example shows how to: @@ -6,7 +9,7 @@ //! - Detect and resolve conflicts //! - Use custom decomposition strategies -use hope_agents::{ +use kaneru::{ default_decomposition_rules, DecompositionStrategy, Goal, HierarchicalGoalSolver, SequentialStrategy, }; diff --git a/crates/hope_agents/examples/hope_orchestrator.rs b/crates/kaneru/examples/kaneru_orchestrator.rs similarity index 92% rename from crates/hope_agents/examples/hope_orchestrator.rs rename to crates/kaneru/examples/kaneru_orchestrator.rs index 4007803a..3acaacb0 100644 --- a/crates/hope_agents/examples/hope_orchestrator.rs +++ b/crates/kaneru/examples/kaneru_orchestrator.rs @@ -1,21 +1,24 @@ -//! Example of using the HOPE Agent Orchestrator +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Example of using the Kaneru Agent Orchestrator //! //! This example demonstrates how to: -//! - Create and configure a HOPE agent +//! - Create and configure a Kaneru agent //! - Set goals //! - Run the agent step/learn cycle //! - Switch operation modes //! - Track statistics //! - Serialize/deserialize agent state -use hope_agents::{ - Action, ActionResult, ActionType, Goal, GoalSelectionStrategy, HopeAgent, HopeConfig, +use kaneru::{ + Action, ActionResult, ActionType, Goal, GoalSelectionStrategy, KaneruAgent, KaneruConfig, LearningAlgorithm, LearningConfig, Observation, OperationMode, Outcome, PredictiveConfig, Priority, }; fn main() { - println!("=== HOPE Agent Orchestrator Example ===\n"); + println!("=== Kaneru Agent Orchestrator Example ===\n"); // Example 1: Basic agent creation and configuration basic_example(); @@ -44,8 +47,8 @@ fn main() { fn basic_example() { println!("--- Example 1: Basic Agent ---"); - // Create a HOPE agent with default configuration - let mut agent = HopeAgent::with_default_config(); + // Create a Kaneru agent with default configuration + let mut agent = KaneruAgent::with_default_config(); println!("Created agent with mode: {:?}", agent.mode()); @@ -86,7 +89,7 @@ fn goal_driven_example() { println!("--- Example 2: Goal-Driven Agent ---"); // Create agent with custom configuration - let config = HopeConfig { + let config = KaneruConfig { mode: OperationMode::GoalDriven, learning: LearningConfig { learning_rate: 0.15, @@ -100,7 +103,7 @@ fn goal_driven_example() { ..Default::default() }; - let mut agent = HopeAgent::new(config); + let mut agent = KaneruAgent::new(config); // Set multiple goals let goal1 = Goal::maintain("temperature", 18.0..22.0).with_priority(Priority::High); @@ -148,7 +151,7 @@ fn goal_driven_example() { fn multi_episode_example() { println!("--- Example 3: Multi-Episode Learning ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Run 5 episodes for episode in 0..5 { @@ -193,7 +196,7 @@ fn multi_episode_example() { fn mode_switching_example() { println!("--- Example 4: Operation Mode Switching ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Start with exploration agent.set_mode(OperationMode::Exploration); @@ -241,7 +244,7 @@ fn persistence_example() { println!("--- Example 5: State Persistence ---"); // Create and train an agent - let mut agent1 = HopeAgent::with_default_config(); + let mut agent1 = KaneruAgent::with_default_config(); for i in 0..5 { let obs = Observation::sensor("data", i as f64); @@ -263,7 +266,7 @@ fn persistence_example() { println!("\nSaved agent state"); // Create new agent and load state - let mut agent2 = HopeAgent::with_default_config(); + let mut agent2 = KaneruAgent::with_default_config(); agent2.load_state(saved_state); println!("\nRestored agent:"); @@ -295,7 +298,7 @@ fn persistence_example() { fn aingle_integration_example() { println!("--- AIngle Integration Example ---"); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Set a goal for network operation let goal = Goal::perform("maintain_network_health").with_priority(Priority::Critical); diff --git a/crates/hope_agents/src/action.rs b/crates/kaneru/src/action.rs similarity index 95% rename from crates/hope_agents/src/action.rs rename to crates/kaneru/src/action.rs index 839bc8ff..72c4b186 100644 --- a/crates/hope_agents/src/action.rs +++ b/crates/kaneru/src/action.rs @@ -1,4 +1,7 @@ -//! Action types for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Action types for Kaneru. //! //! Actions represent what an agent can do in its environment. They are the //! output of the agent's decision-making process. @@ -16,7 +19,7 @@ use std::collections::HashMap; /// # Examples /// /// ``` -/// # use hope_agents::ActionType; +/// # use kaneru::ActionType; /// let msg_action = ActionType::send_message("peer_123"); /// let store_action = ActionType::store("temperature_data"); /// let alert_action = ActionType::alert("Critical error occurred"); @@ -55,7 +58,7 @@ impl ActionType { /// # Examples /// /// ``` - /// # use hope_agents::ActionType; + /// # use kaneru::ActionType; /// let action = ActionType::send_message("agent_123"); /// ``` pub fn send_message(target: &str) -> Self { @@ -71,7 +74,7 @@ impl ActionType { /// # Examples /// /// ``` - /// # use hope_agents::ActionType; + /// # use kaneru::ActionType; /// let action = ActionType::store("sensor_reading"); /// ``` pub fn store(key: &str) -> Self { @@ -87,7 +90,7 @@ impl ActionType { /// # Examples /// /// ``` - /// # use hope_agents::ActionType; + /// # use kaneru::ActionType; /// let action = ActionType::publish("temperature_updates"); /// ``` pub fn publish(topic: &str) -> Self { @@ -103,7 +106,7 @@ impl ActionType { /// # Examples /// /// ``` - /// # use hope_agents::ActionType; + /// # use kaneru::ActionType; /// let action = ActionType::alert("System overheating"); /// ``` pub fn alert(message: &str) -> Self { @@ -120,7 +123,7 @@ impl ActionType { /// # Examples /// /// ``` -/// # use hope_agents::{Action, Priority}; +/// # use kaneru::{Action, Priority}; /// // Create a simple action /// let action = Action::store("temperature", 23.5); /// @@ -162,7 +165,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::{Action, ActionType}; + /// # use kaneru::{Action, ActionType}; /// let action = Action::new(ActionType::NoOp); /// ``` pub fn new(action_type: ActionType) -> Self { @@ -184,7 +187,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::Action; + /// # use kaneru::Action; /// let action = Action::noop(); /// assert!(action.is_noop()); /// ``` @@ -199,7 +202,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::Action; + /// # use kaneru::Action; /// let action = Action::wait(); /// ``` pub fn wait() -> Self { @@ -216,7 +219,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::Action; + /// # use kaneru::Action; /// let action = Action::send_message("peer_123", "Hello, peer!"); /// ``` pub fn send_message(target: &str, content: impl Into) -> Self { @@ -233,7 +236,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::Action; + /// # use kaneru::Action; /// let action = Action::store("temperature", 23.5); /// ``` pub fn store(key: &str, value: impl Into) -> Self { @@ -249,7 +252,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::Action; + /// # use kaneru::Action; /// let action = Action::alert("Temperature threshold exceeded!"); /// ``` pub fn alert(message: &str) -> Self { @@ -269,7 +272,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::{Action, ActionType}; + /// # use kaneru::{Action, ActionType}; /// let action = Action::new(ActionType::Custom("process".into())) /// .with_param("input", "data.csv") /// .with_param("output", "results.json"); @@ -291,7 +294,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::{Action, Priority}; + /// # use kaneru::{Action, Priority}; /// let action = Action::alert("Critical failure") /// .with_priority(Priority::Critical); /// ``` @@ -305,7 +308,7 @@ impl Action { /// # Examples /// /// ``` - /// # use hope_agents::Action; + /// # use kaneru::Action; /// let noop = Action::noop(); /// assert!(noop.is_noop()); /// @@ -326,7 +329,7 @@ impl Action { /// # Examples /// /// ``` -/// # use hope_agents::ActionResult; +/// # use kaneru::ActionResult; /// // Create a successful result /// let success = ActionResult::success("action_123"); /// assert!(success.success); @@ -364,7 +367,7 @@ impl ActionResult { /// # Examples /// /// ``` - /// # use hope_agents::ActionResult; + /// # use kaneru::ActionResult; /// let result = ActionResult::success("action_123"); /// assert!(result.success); /// assert!(result.error.is_none()); @@ -390,7 +393,7 @@ impl ActionResult { /// # Examples /// /// ``` - /// # use hope_agents::ActionResult; + /// # use kaneru::ActionResult; /// let result = ActionResult::success_with_value("action_123", 42); /// assert!(result.success); /// assert!(result.value.is_some()); @@ -416,7 +419,7 @@ impl ActionResult { /// # Examples /// /// ``` - /// # use hope_agents::ActionResult; + /// # use kaneru::ActionResult; /// let result = ActionResult::failure("action_123", "Network unreachable"); /// assert!(!result.success); /// assert!(result.error.is_some()); @@ -441,7 +444,7 @@ impl ActionResult { /// # Examples /// /// ``` - /// # use hope_agents::ActionResult; + /// # use kaneru::ActionResult; /// let result = ActionResult::success("action_123") /// .with_duration(1500); /// assert_eq!(result.duration_us, 1500); @@ -461,7 +464,7 @@ impl ActionResult { /// # Examples /// /// ``` -/// # use hope_agents::{Action, ActionResult, ActionType, action::ActionExecutor}; +/// # use kaneru::{Action, ActionResult, ActionType, action::ActionExecutor}; /// struct MyExecutor; /// /// impl ActionExecutor for MyExecutor { @@ -509,7 +512,7 @@ pub trait ActionExecutor { /// # Examples /// /// ``` -/// # use hope_agents::{Action, action::{ActionExecutor, LoggingExecutor}}; +/// # use kaneru::{Action, action::{ActionExecutor, LoggingExecutor}}; /// let mut executor = LoggingExecutor; /// let action = Action::store("key", "value"); /// let result = executor.execute(&action); diff --git a/crates/hope_agents/src/agent.rs b/crates/kaneru/src/agent.rs similarity index 93% rename from crates/hope_agents/src/agent.rs rename to crates/kaneru/src/agent.rs index a56f990f..c935fff2 100644 --- a/crates/hope_agents/src/agent.rs +++ b/crates/kaneru/src/agent.rs @@ -1,7 +1,10 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The core `Agent` trait and a simple, concrete implementation. //! //! This module provides the fundamental building blocks for creating agents -//! within the HOPE framework. +//! within the Kaneru framework. use crate::action::{Action, ActionResult}; use crate::config::AgentConfig; @@ -20,7 +23,7 @@ use serde::{Deserialize, Serialize}; /// # Examples /// /// ``` -/// # use hope_agents::AgentId; +/// # use kaneru::AgentId; /// let id = AgentId::new("sensor_agent"); /// // ID will be something like "sensor_agent_1234567890" /// ``` @@ -40,7 +43,7 @@ impl AgentId { /// # Examples /// /// ``` - /// # use hope_agents::AgentId; + /// # use kaneru::AgentId; /// let id1 = AgentId::new("agent"); /// let id2 = AgentId::new("agent"); /// assert_ne!(id1, id2); // Different due to timestamps @@ -68,7 +71,7 @@ impl AgentId { /// # Examples /// /// ``` -/// # use hope_agents::AgentState; +/// # use kaneru::AgentState; /// let state = AgentState::default(); /// assert_eq!(state, AgentState::Initializing); /// ``` @@ -95,7 +98,7 @@ pub enum AgentState { /// The core trait defining the capabilities and lifecycle of all agents. /// -/// The `Agent` trait defines the fundamental interface that all agents in the HOPE framework +/// The `Agent` trait defines the fundamental interface that all agents in the Kaneru framework /// must implement. It follows a sense-decide-act-learn cycle inspired by reinforcement learning /// and autonomous systems design. /// @@ -110,7 +113,7 @@ pub enum AgentState { /// # Examples /// /// ``` -/// # use hope_agents::{Agent, SimpleAgent, Observation, Action}; +/// # use kaneru::{Agent, SimpleAgent, Observation, Action}; /// let mut agent = SimpleAgent::new("example"); /// /// // Observe @@ -130,14 +133,14 @@ pub enum AgentState { /// # See Also /// /// - [`SimpleAgent`] for a concrete implementation -/// - `HopeAgent` for an advanced implementation with hierarchical goals and learning +/// - `KaneruAgent` for an advanced implementation with hierarchical goals and learning pub trait Agent { /// Returns the unique identifier of the agent. /// /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent}; + /// # use kaneru::{Agent, SimpleAgent}; /// let agent = SimpleAgent::new("my_agent"); /// println!("Agent ID: {:?}", agent.id()); /// ``` @@ -148,7 +151,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent}; + /// # use kaneru::{Agent, SimpleAgent}; /// let agent = SimpleAgent::new("my_agent"); /// assert_eq!(agent.name(), "my_agent"); /// ``` @@ -159,7 +162,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, AgentState}; + /// # use kaneru::{Agent, SimpleAgent, AgentState}; /// let agent = SimpleAgent::new("my_agent"); /// assert_eq!(agent.state(), AgentState::Idle); /// ``` @@ -178,7 +181,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Observation}; + /// # use kaneru::{Agent, SimpleAgent, Observation}; /// let mut agent = SimpleAgent::new("sensor_agent"); /// agent.observe(Observation::sensor("temperature", 23.5)); /// assert_eq!(agent.stats().observations_received, 1); @@ -198,7 +201,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Observation, Action}; + /// # use kaneru::{Agent, SimpleAgent, Observation, Action}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.observe(Observation::sensor("temperature", 25.0)); /// let action = agent.decide(); @@ -224,7 +227,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Action}; + /// # use kaneru::{Agent, SimpleAgent, Action}; /// let mut agent = SimpleAgent::new("my_agent"); /// let action = Action::store("key", "value"); /// let result = agent.execute(action); @@ -247,7 +250,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Observation, Action, ActionResult}; + /// # use kaneru::{Agent, SimpleAgent, Observation, Action, ActionResult}; /// let mut agent = SimpleAgent::new("learning_agent"); /// let obs = Observation::sensor("temperature", 25.0); /// let action = Action::store("data", "value"); @@ -269,7 +272,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Observation}; + /// # use kaneru::{Agent, SimpleAgent, Observation}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.observe(Observation::sensor("temperature", 25.0)); /// @@ -290,7 +293,7 @@ pub trait Agent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent}; + /// # use kaneru::{Agent, SimpleAgent}; /// let agent = SimpleAgent::new("my_agent"); /// println!("Max goals: {}", agent.config().max_goals); /// ``` @@ -316,7 +319,7 @@ pub trait Agent { /// ## Basic Usage /// /// ``` -/// # use hope_agents::{Agent, SimpleAgent, Observation, Rule, Condition, Action}; +/// # use kaneru::{Agent, SimpleAgent, Observation, Rule, Condition, Action}; /// let mut agent = SimpleAgent::new("temperature_monitor"); /// /// // Add a rule @@ -336,8 +339,8 @@ pub trait Agent { /// ## With Learning /// /// ``` -/// # use hope_agents::{Agent, SimpleAgent, Observation, Action}; -/// # use hope_agents::learning::{LearningConfig, LearningAlgorithm}; +/// # use kaneru::{Agent, SimpleAgent, Observation, Action}; +/// # use kaneru::learning::{LearningConfig, LearningAlgorithm}; /// let mut agent = SimpleAgent::new("learning_agent"); /// /// // Enable learning @@ -362,7 +365,7 @@ pub trait Agent { /// # See Also /// /// - [`Agent`] trait for the core interface -/// - `HopeAgent` for advanced hierarchical goal-based agents +/// - `KaneruAgent` for advanced hierarchical goal-based agents /// - [`AgentConfig`] for configuration options pub struct SimpleAgent { /// The unique identifier for the agent. @@ -405,7 +408,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent}; + /// # use kaneru::{Agent, SimpleAgent}; /// let agent = SimpleAgent::new("my_agent"); /// assert_eq!(agent.name(), "my_agent"); /// ``` @@ -431,7 +434,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, AgentConfig}; + /// # use kaneru::{Agent, SimpleAgent, AgentConfig}; /// let config = AgentConfig::iot_mode(); /// let agent = SimpleAgent::with_config("iot_agent", config); /// assert!(agent.config().max_memory_bytes <= 128 * 1024); @@ -482,7 +485,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{SimpleAgent, Policy, Rule, Condition, Action}; + /// # use kaneru::{SimpleAgent, Policy, Rule, Condition, Action}; /// let mut agent = SimpleAgent::new("my_agent"); /// let mut policy = Policy::new("safety"); /// policy.add_rule(Rule::new( @@ -508,7 +511,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{SimpleAgent, Rule, Condition, Action}; + /// # use kaneru::{SimpleAgent, Rule, Condition, Action}; /// let mut agent = SimpleAgent::new("temperature_monitor"); /// agent.add_rule(Rule::new( /// "high_temp", @@ -535,7 +538,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::SimpleAgent; + /// # use kaneru::SimpleAgent; /// let mut agent = SimpleAgent::new("explorer"); /// agent.set_exploration_rate(0.1); // 10% exploration, 90% exploitation /// ``` @@ -560,7 +563,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{SimpleAgent, Goal}; + /// # use kaneru::{SimpleAgent, Goal}; /// let mut agent = SimpleAgent::new("my_agent"); /// let goal = Goal::maintain("temperature", 20.0..25.0); /// if let Some(goal_id) = agent.add_goal(goal) { @@ -583,7 +586,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{SimpleAgent, Goal}; + /// # use kaneru::{SimpleAgent, Goal}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.set_goal(Goal::maximize("efficiency")); /// ``` @@ -598,7 +601,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{SimpleAgent, Goal}; + /// # use kaneru::{SimpleAgent, Goal}; /// let mut agent = SimpleAgent::new("my_agent"); /// let mut goal = Goal::maximize("score"); /// goal.activate(); @@ -621,7 +624,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Observation}; + /// # use kaneru::{Agent, SimpleAgent, Observation}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.observe(Observation::sensor("temp", 20.0)); /// agent.observe(Observation::sensor("temp", 21.0)); @@ -642,7 +645,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Observation}; + /// # use kaneru::{Agent, SimpleAgent, Observation}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.observe(Observation::sensor("temp", 20.0)); /// assert_eq!(agent.stats().observations_received, 1); @@ -660,7 +663,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, AgentState}; + /// # use kaneru::{Agent, SimpleAgent, AgentState}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.pause(); /// assert_eq!(agent.state(), AgentState::Paused); @@ -676,7 +679,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, AgentState}; + /// # use kaneru::{Agent, SimpleAgent, AgentState}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.pause(); /// agent.resume(); @@ -695,7 +698,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent}; + /// # use kaneru::{Agent, SimpleAgent}; /// let mut agent = SimpleAgent::new("my_agent"); /// agent.stop(); /// assert!(!agent.is_running()); @@ -712,7 +715,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent}; + /// # use kaneru::{Agent, SimpleAgent}; /// let mut agent = SimpleAgent::new("my_agent"); /// assert!(agent.is_running()); /// @@ -735,7 +738,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::{SimpleAgent, learning::{LearningConfig, LearningAlgorithm}}; + /// # use kaneru::{SimpleAgent, learning::{LearningConfig, LearningAlgorithm}}; /// let mut agent = SimpleAgent::new("learner"); /// let config = LearningConfig { /// learning_rate: 0.1, @@ -763,7 +766,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::SimpleAgent; + /// # use kaneru::SimpleAgent; /// let mut agent = SimpleAgent::new("learner"); /// agent.enable_learning_default(); /// assert!(agent.learning_engine().is_some()); @@ -780,7 +783,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::SimpleAgent; + /// # use kaneru::SimpleAgent; /// let mut agent = SimpleAgent::new("learner"); /// agent.enable_learning_default(); /// agent.disable_learning(); @@ -796,7 +799,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::SimpleAgent; + /// # use kaneru::SimpleAgent; /// let agent = SimpleAgent::new("learner"); /// if let Some(engine) = agent.learning_engine() { /// println!("Total updates: {}", engine.total_updates()); @@ -811,7 +814,7 @@ impl SimpleAgent { /// # Examples /// /// ``` - /// # use hope_agents::SimpleAgent; + /// # use kaneru::SimpleAgent; /// let mut agent = SimpleAgent::new("learner"); /// if let Some(engine) = agent.learning_engine_mut() { /// // Modify engine settings @@ -943,7 +946,7 @@ impl Agent for SimpleAgent { /// # Examples /// /// ``` -/// # use hope_agents::{Agent, SimpleAgent, Observation, Action}; +/// # use kaneru::{Agent, SimpleAgent, Observation, Action}; /// let mut agent = SimpleAgent::new("my_agent"); /// /// // Process some observations and actions @@ -987,7 +990,7 @@ impl AgentStats { /// # Examples /// /// ``` - /// # use hope_agents::{Agent, SimpleAgent, Action}; + /// # use kaneru::{Agent, SimpleAgent, Action}; /// let mut agent = SimpleAgent::new("my_agent"); /// /// // Execute some actions diff --git a/crates/hope_agents/src/config.rs b/crates/kaneru/src/config.rs similarity index 95% rename from crates/hope_agents/src/config.rs rename to crates/kaneru/src/config.rs index ff97d2b0..63df53d0 100644 --- a/crates/hope_agents/src/config.rs +++ b/crates/kaneru/src/config.rs @@ -1,9 +1,12 @@ -//! Configuration for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Configuration for Kaneru. use serde::{Deserialize, Serialize}; use std::time::Duration; -/// Defines the configuration for a HOPE agent. +/// Defines the configuration for a Kaneru agent. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentConfig { /// The human-readable name of the agent. diff --git a/crates/hope_agents/src/coordination.rs b/crates/kaneru/src/coordination.rs similarity index 94% rename from crates/hope_agents/src/coordination.rs rename to crates/kaneru/src/coordination.rs index 1c6b4351..1056131e 100644 --- a/crates/hope_agents/src/coordination.rs +++ b/crates/kaneru/src/coordination.rs @@ -1,6 +1,9 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Multi-Agent Coordination. //! -//! Enables multiple HOPE agents to coordinate their actions through: +//! Enables multiple Kaneru agents to coordinate their actions through: //! - A central `AgentCoordinator`. //! - A `MessageBus` for inter-agent communication. //! - `SharedMemory` for common knowledge. @@ -9,14 +12,14 @@ //! ## Example //! //! ```rust,ignore -//! use hope_agents::{HopeAgent, AgentCoordinator, Message}; +//! use kaneru::{KaneruAgent, AgentCoordinator, Message}; //! use std::collections::HashMap; //! //! let mut coordinator = AgentCoordinator::new(); //! //! // Register multiple agents -//! let agent1 = HopeAgent::with_default_config(); -//! let agent2 = HopeAgent::with_default_config(); +//! let agent1 = KaneruAgent::with_default_config(); +//! let agent2 = KaneruAgent::with_default_config(); //! //! let id1 = coordinator.register_agent(agent1); //! let id2 = coordinator.register_agent(agent2); @@ -31,7 +34,7 @@ //! let actions = coordinator.step_all(observations); //! ``` -use crate::{Action, AgentId, HopeAgent, Observation, Outcome}; +use crate::{Action, AgentId, KaneruAgent, Observation, Outcome}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, VecDeque}; @@ -327,13 +330,13 @@ impl Default for MessageBus { /// A handle to a registered agent within the coordinator. #[allow(dead_code)] struct AgentHandle { - agent: HopeAgent, + agent: KaneruAgent, inbox: VecDeque, outbox: VecDeque, } impl AgentHandle { - fn new(agent: HopeAgent) -> Self { + fn new(agent: KaneruAgent) -> Self { Self { agent, inbox: VecDeque::new(), @@ -404,7 +407,7 @@ impl AgentCoordinator { } /// Registers a new agent with the coordinator and returns its assigned `AgentId`. - pub fn register_agent(&mut self, agent: HopeAgent) -> AgentId { + pub fn register_agent(&mut self, agent: KaneruAgent) -> AgentId { let id = AgentId(format!("agent_{}", self.next_id)); self.next_id += 1; @@ -416,7 +419,7 @@ impl AgentCoordinator { } /// Unregisters an agent from the coordinator. - pub fn unregister_agent(&mut self, agent_id: &AgentId) -> Result { + pub fn unregister_agent(&mut self, agent_id: &AgentId) -> Result { self.agents .remove(agent_id) .map(|handle| handle.agent) @@ -582,12 +585,12 @@ impl AgentCoordinator { } /// Returns a reference to a specific agent managed by the coordinator. - pub fn get_agent(&self, agent_id: &AgentId) -> Option<&HopeAgent> { + pub fn get_agent(&self, agent_id: &AgentId) -> Option<&KaneruAgent> { self.agents.get(agent_id).map(|handle| &handle.agent) } /// Returns a mutable reference to a specific agent managed by the coordinator. - pub fn get_agent_mut(&mut self, agent_id: &AgentId) -> Option<&mut HopeAgent> { + pub fn get_agent_mut(&mut self, agent_id: &AgentId) -> Option<&mut KaneruAgent> { self.agents .get_mut(agent_id) .map(|handle| &mut handle.agent) @@ -666,7 +669,7 @@ fn uuid_v4() -> String { #[cfg(test)] mod tests { use super::*; - use crate::{HopeAgent, HopeConfig, Observation}; + use crate::{KaneruAgent, KaneruConfig, Observation}; #[test] fn test_coordinator_creation() { @@ -678,7 +681,7 @@ mod tests { fn test_agent_registration() { let mut coordinator = AgentCoordinator::new(); - let agent = HopeAgent::new(HopeConfig::default()); + let agent = KaneruAgent::new(KaneruConfig::default()); let id = coordinator.register_agent(agent); assert_eq!(coordinator.agent_count(), 1); @@ -689,7 +692,7 @@ mod tests { fn test_agent_unregistration() { let mut coordinator = AgentCoordinator::new(); - let agent = HopeAgent::new(HopeConfig::default()); + let agent = KaneruAgent::new(KaneruConfig::default()); let id = coordinator.register_agent(agent); let agent = coordinator.unregister_agent(&id); @@ -701,8 +704,8 @@ mod tests { fn test_broadcast_message() { let mut coordinator = AgentCoordinator::new(); - let agent1 = HopeAgent::new(HopeConfig::default()); - let agent2 = HopeAgent::new(HopeConfig::default()); + let agent1 = KaneruAgent::new(KaneruConfig::default()); + let agent2 = KaneruAgent::new(KaneruConfig::default()); let id1 = coordinator.register_agent(agent1); let id2 = coordinator.register_agent(agent2); @@ -722,8 +725,8 @@ mod tests { fn test_direct_message() { let mut coordinator = AgentCoordinator::new(); - let agent1 = HopeAgent::new(HopeConfig::default()); - let agent2 = HopeAgent::new(HopeConfig::default()); + let agent1 = KaneruAgent::new(KaneruConfig::default()); + let agent2 = KaneruAgent::new(KaneruConfig::default()); let id1 = coordinator.register_agent(agent1); let id2 = coordinator.register_agent(agent2); @@ -757,9 +760,9 @@ mod tests { fn test_consensus_proposal() { let mut coordinator = AgentCoordinator::new(); - let agent1 = HopeAgent::new(HopeConfig::default()); - let agent2 = HopeAgent::new(HopeConfig::default()); - let agent3 = HopeAgent::new(HopeConfig::default()); + let agent1 = KaneruAgent::new(KaneruConfig::default()); + let agent2 = KaneruAgent::new(KaneruConfig::default()); + let agent3 = KaneruAgent::new(KaneruConfig::default()); let id1 = coordinator.register_agent(agent1); let id2 = coordinator.register_agent(agent2); @@ -792,8 +795,8 @@ mod tests { fn test_step_all() { let mut coordinator = AgentCoordinator::new(); - let agent1 = HopeAgent::new(HopeConfig::default()); - let agent2 = HopeAgent::new(HopeConfig::default()); + let agent1 = KaneruAgent::new(KaneruConfig::default()); + let agent2 = KaneruAgent::new(KaneruConfig::default()); let id1 = coordinator.register_agent(agent1); let id2 = coordinator.register_agent(agent2); diff --git a/crates/hope_agents/src/error.rs b/crates/kaneru/src/error.rs similarity index 93% rename from crates/hope_agents/src/error.rs rename to crates/kaneru/src/error.rs index 2be2143d..61aa9b2e 100644 --- a/crates/hope_agents/src/error.rs +++ b/crates/kaneru/src/error.rs @@ -1,9 +1,12 @@ -//! Error types for the HOPE Agents framework. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Error types for the Kaneru framework. /// A specialized `Result` type for agent operations. pub type Result = std::result::Result; -/// The primary error enum for all operations within the `hope_agents` crate. +/// The primary error enum for all operations within the `kaneru` crate. #[derive(Debug)] pub enum Error { /// An error related to the agent's configuration. @@ -16,7 +19,7 @@ pub enum Error { Action(String), /// An error related to processing an observation. Observation(String), - /// An error originating from the agent's memory system (e.g., `titans_memory`). + /// An error originating from the agent's memory system (e.g., `ineru`). Memory(String), /// An operation timed out. Timeout(String), @@ -48,8 +51,8 @@ impl From for Error { } #[cfg(feature = "memory")] -impl From for Error { - fn from(e: titans_memory::Error) -> Self { +impl From for Error { + fn from(e: ineru::Error) -> Self { Error::Memory(e.to_string()) } } diff --git a/crates/hope_agents/src/goal.rs b/crates/kaneru/src/goal.rs similarity index 99% rename from crates/hope_agents/src/goal.rs rename to crates/kaneru/src/goal.rs index 2792ce24..1e56ffa1 100644 --- a/crates/hope_agents/src/goal.rs +++ b/crates/kaneru/src/goal.rs @@ -1,4 +1,7 @@ -//! Goal types for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Goal types for Kaneru. //! //! Goals define what an agent is trying to achieve, providing the primary //! motivation for its actions. diff --git a/crates/hope_agents/src/hierarchical/README.md b/crates/kaneru/src/hierarchical/README.md similarity index 93% rename from crates/hope_agents/src/hierarchical/README.md rename to crates/kaneru/src/hierarchical/README.md index f93d49d3..8b8b51e1 100644 --- a/crates/hope_agents/src/hierarchical/README.md +++ b/crates/kaneru/src/hierarchical/README.md @@ -1,6 +1,6 @@ # Hierarchical Goal Solver -The Hierarchical Goal Solver provides sophisticated goal management capabilities for HOPE Agents, including automatic goal decomposition, dependency tracking, conflict detection, and progress propagation. +The Hierarchical Goal Solver provides sophisticated goal management capabilities for Kaneru, including automatic goal decomposition, dependency tracking, conflict detection, and progress propagation. ## Features @@ -9,7 +9,7 @@ The Hierarchical Goal Solver provides sophisticated goal management capabilities Goals can be automatically decomposed into subgoals using registered decomposition rules: ```rust -use hope_agents::{HierarchicalGoalSolver, Goal, default_decomposition_rules}; +use kaneru::{HierarchicalGoalSolver, Goal, default_decomposition_rules}; let mut solver = HierarchicalGoalSolver::new(); @@ -136,7 +136,7 @@ You can create custom decomposition strategies: Breaks goals into sequential steps: ```rust -use hope_agents::{SequentialStrategy, DecompositionStrategy}; +use kaneru::{SequentialStrategy, DecompositionStrategy}; let strategy = SequentialStrategy { name: "Database Migration".to_string(), @@ -157,7 +157,7 @@ let subgoals = strategy.decompose(&goal); Breaks goals into parallel tasks: ```rust -use hope_agents::ParallelStrategy; +use kaneru::ParallelStrategy; let strategy = ParallelStrategy { name: "Distributed Processing".to_string(), @@ -174,7 +174,7 @@ let strategy = ParallelStrategy { Create custom decomposition rules: ```rust -use hope_agents::DecompositionRule; +use kaneru::DecompositionRule; let rule = DecompositionRule { name: "custom_rule".to_string(), @@ -210,7 +210,7 @@ The solver detects four types of conflicts: Several strategies are available: ```rust -use hope_agents::ConflictResolution; +use kaneru::ConflictResolution; // Prioritize one goal over another ConflictResolution::PrioritizeFirst @@ -260,7 +260,7 @@ let roots = tree.root_goals(); See `examples/hierarchical_goals.rs` for a comprehensive example demonstrating all features. ```bash -cargo run -p hope_agents --example hierarchical_goals +cargo run -p kaneru --example hierarchical_goals ``` ## Integration with Learning Engine @@ -308,10 +308,10 @@ The module includes comprehensive tests: ```bash # Run all hierarchical tests -cargo test -p hope_agents --lib hierarchical +cargo test -p kaneru --lib hierarchical # Run specific test -cargo test -p hope_agents --lib hierarchical::tests::test_goal_decomposition +cargo test -p kaneru --lib hierarchical::tests::test_goal_decomposition ``` ## Future Enhancements diff --git a/crates/hope_agents/src/hierarchical/decomposition.rs b/crates/kaneru/src/hierarchical/decomposition.rs similarity index 99% rename from crates/hope_agents/src/hierarchical/decomposition.rs rename to crates/kaneru/src/hierarchical/decomposition.rs index eac0fd4b..e7ad4998 100644 --- a/crates/hope_agents/src/hierarchical/decomposition.rs +++ b/crates/kaneru/src/hierarchical/decomposition.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Defines strategies and rules for decomposing high-level goals into smaller, manageable sub-goals. use super::{DecompositionRule, GoalTypeFilter}; diff --git a/crates/hope_agents/src/hierarchical/goal_solver.rs b/crates/kaneru/src/hierarchical/goal_solver.rs similarity index 98% rename from crates/hope_agents/src/hierarchical/goal_solver.rs rename to crates/kaneru/src/hierarchical/goal_solver.rs index e5ce7cb9..4296f40e 100644 --- a/crates/hope_agents/src/hierarchical/goal_solver.rs +++ b/crates/kaneru/src/hierarchical/goal_solver.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! The core logic for the Hierarchical Goal Solver. use crate::{Goal, GoalStatus, GoalType}; @@ -530,13 +533,9 @@ impl Default for GoalTree { } } -impl Clone for DecompositionRule { - fn clone(&self) -> Self { - // Note: We can't actually clone the closures, so this is a limitation - // In practice, rules should be created fresh rather than cloned - panic!("DecompositionRule cannot be cloned due to closure fields") - } -} +// DecompositionRule intentionally does not implement Clone because its +// closure fields (ConditionFn, DecomposeFn) are not cloneable. +// Wrap in Arc if shared ownership is needed. /// Helper function to check if a goal's type matches a `GoalTypeFilter`. fn matches_goal_type(goal_type: &GoalType, filter: &GoalTypeFilter) -> bool { diff --git a/crates/hope_agents/src/hierarchical/mod.rs b/crates/kaneru/src/hierarchical/mod.rs similarity index 65% rename from crates/hope_agents/src/hierarchical/mod.rs rename to crates/kaneru/src/hierarchical/mod.rs index ed8134c7..5cb81366 100644 --- a/crates/hope_agents/src/hierarchical/mod.rs +++ b/crates/kaneru/src/hierarchical/mod.rs @@ -1,4 +1,7 @@ -//! Hierarchical goal decomposition and management for HOPE agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Hierarchical goal decomposition and management for Kaneru agents. //! //! This module provides sophisticated goal management capabilities including: //! - Automatic goal decomposition using rules diff --git a/crates/hope_agents/src/hierarchical/tests.rs b/crates/kaneru/src/hierarchical/tests.rs similarity index 98% rename from crates/hope_agents/src/hierarchical/tests.rs rename to crates/kaneru/src/hierarchical/tests.rs index 819f5d38..fe596f56 100644 --- a/crates/hope_agents/src/hierarchical/tests.rs +++ b/crates/kaneru/src/hierarchical/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::{Goal, GoalStatus}; diff --git a/crates/hope_agents/src/hope_agent.rs b/crates/kaneru/src/kaneru_agent.rs similarity index 92% rename from crates/hope_agents/src/hope_agent.rs rename to crates/kaneru/src/kaneru_agent.rs index 5e5ca763..6911c256 100644 --- a/crates/hope_agents/src/hope_agent.rs +++ b/crates/kaneru/src/kaneru_agent.rs @@ -1,13 +1,16 @@ -//! The main HOPE Agent orchestrator. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! The main Kaneru Agent orchestrator. //! -//! This module integrates all HOPE (Hierarchical, Optimistic, Predictive, Emergent) +//! This module integrates all Kaneru (Unified Multi-Agent Execution System) //! components into a unified, advanced agent that can perceive, learn, plan, and act. //! //! ## Architecture //! //! ```text //! ┌─────────────────────────────────────────────────────────────┐ -//! │ HOPE Agent │ +//! │ Kaneru Agent │ //! ├─────────────────────────────────────────────────────────────┤ //! │ │ //! │ Observation → State → Decision → Action → Learning │ @@ -31,7 +34,7 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::collections::VecDeque; -/// Defines the operational mode of a `HopeAgent`. +/// Defines the operational mode of a `KaneruAgent`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] pub enum OperationMode { /// The agent prioritizes exploring its environment to gather new knowledge, @@ -49,9 +52,9 @@ pub enum OperationMode { Adaptive, } -/// Configuration for a `HopeAgent`. +/// Configuration for a `KaneruAgent`. #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct HopeConfig { +pub struct KaneruConfig { /// Configuration for the agent's learning engine. pub learning: LearningConfig, /// Configuration for the agent's predictive model. @@ -72,7 +75,7 @@ pub struct HopeConfig { pub auto_decompose_goals: bool, } -impl Default for HopeConfig { +impl Default for KaneruConfig { fn default() -> Self { Self { learning: LearningConfig::default(), @@ -143,7 +146,7 @@ impl Default for AgentStats { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SerializedState { /// The agent's configuration. - pub config: HopeConfig, + pub config: KaneruConfig, /// The agent's performance statistics. pub stats: AgentStats, /// The agent's last known state. @@ -192,11 +195,11 @@ impl Outcome { } } -/// The main HOPE Agent, integrating learning, planning, and predictive capabilities. +/// The main Kaneru Agent, integrating learning, planning, and predictive capabilities. /// /// This is the most advanced agent implementation in the framework, designed for /// complex, dynamic environments where adaptability is key. -pub struct HopeAgent { +pub struct KaneruAgent { /// The core reinforcement learning engine (e.g., Q-learning). learning: LearningEngine, /// The hierarchical goal solver for planning and task decomposition. @@ -215,7 +218,7 @@ pub struct HopeAgent { action_history: VecDeque, /// The agent's configuration. - config: HopeConfig, + config: KaneruConfig, /// The agent's performance statistics. stats: AgentStats, @@ -228,9 +231,9 @@ pub struct HopeAgent { available_actions: Vec, } -impl HopeAgent { - /// Creates a new `HopeAgent` with the given configuration. - pub fn new(config: HopeConfig) -> Self { +impl KaneruAgent { + /// Creates a new `KaneruAgent` with the given configuration. + pub fn new(config: KaneruConfig) -> Self { let learning = LearningEngine::new(config.learning.clone()); let goal_solver = HierarchicalGoalSolver::new(); let predictive = PredictiveModel::new(config.predictive.clone()); @@ -251,9 +254,9 @@ impl HopeAgent { } } - /// Creates a `HopeAgent` with a default configuration. + /// Creates a `KaneruAgent` with a default configuration. pub fn with_default_config() -> Self { - Self::new(HopeConfig::default()) + Self::new(KaneruConfig::default()) } /// The main agent lifecycle step. The agent observes its environment, @@ -298,7 +301,13 @@ impl HopeAgent { /// * `outcome` - An `Outcome` struct containing the action, result, reward, /// and new observation. pub fn learn(&mut self, outcome: Outcome) { - let prev_state = self.current_state.as_ref().unwrap(); + let prev_state = match self.current_state.as_ref() { + Some(s) => s, + None => { + log::warn!("learn() called before any observation — skipping"); + return; + } + }; let action_id = ActionId::from_action(&outcome.action); // Update to new state @@ -318,8 +327,18 @@ impl HopeAgent { self.episode_reward += outcome.reward; // 2. Update predictive model + let prev_obs = match self.observation_history.back() { + Some(obs) => obs, + None => { + log::warn!("learn() called with empty observation history — skipping predictive update"); + // Still update goal progress below + self.current_state = Some(new_state); + self.observation_history.push_back(outcome.new_observation); + return; + } + }; self.predictive.record_transition( - self.observation_history.back().unwrap(), + prev_obs, &outcome.action, outcome.reward, &outcome.new_observation, @@ -700,7 +719,7 @@ impl HopeAgent { } } -impl Default for HopeAgent { +impl Default for KaneruAgent { fn default() -> Self { Self::with_default_config() } @@ -712,15 +731,15 @@ mod tests { use crate::{Goal, GoalStatus, Observation, Priority}; #[test] - fn test_hope_agent_creation() { - let agent = HopeAgent::with_default_config(); + fn test_kaneru_agent_creation() { + let agent = KaneruAgent::with_default_config(); assert_eq!(agent.stats.total_steps, 0); assert_eq!(agent.mode(), OperationMode::Adaptive); } #[test] fn test_step_and_learn_cycle() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Step 1: Observe let obs1 = Observation::sensor("temperature", 20.0); @@ -740,7 +759,7 @@ mod tests { #[test] fn test_goal_integration() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Set a goal let goal = Goal::maintain("temperature", 20.0..25.0).with_priority(Priority::High); @@ -752,7 +771,7 @@ mod tests { #[test] fn test_mode_switching() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); agent.set_mode(OperationMode::Exploration); assert_eq!(agent.mode(), OperationMode::Exploration); @@ -763,7 +782,7 @@ mod tests { #[test] fn test_anomaly_detection() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Record normal observations for i in 0..10 { @@ -783,7 +802,7 @@ mod tests { #[test] fn test_statistics_tracking() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); let obs = Observation::sensor("temp", 20.0); let action = agent.step(obs.clone()); @@ -806,7 +825,7 @@ mod tests { #[test] fn test_serialization() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Do some steps let obs = Observation::sensor("temp", 20.0); @@ -817,7 +836,7 @@ mod tests { assert_eq!(state.stats.total_steps, 1); // Create new agent and load state - let mut new_agent = HopeAgent::with_default_config(); + let mut new_agent = KaneruAgent::with_default_config(); new_agent.load_state(state); assert_eq!(new_agent.stats.total_steps, 1); @@ -825,7 +844,7 @@ mod tests { #[test] fn test_multiple_episodes() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); for episode in 0..3 { for step in 0..5 { @@ -852,7 +871,7 @@ mod tests { #[test] fn test_goal_completion() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); let goal = Goal::maintain("test", 20.0..25.0); let goal_id = agent.set_goal(goal); @@ -880,7 +899,7 @@ mod tests { #[test] fn test_exploration_vs_exploitation() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Set exploration mode agent.set_mode(OperationMode::Exploration); diff --git a/crates/hope_agents/src/learning/engine.rs b/crates/kaneru/src/learning/engine.rs similarity index 98% rename from crates/hope_agents/src/learning/engine.rs rename to crates/kaneru/src/learning/engine.rs index 185be50e..a78328f1 100644 --- a/crates/hope_agents/src/learning/engine.rs +++ b/crates/kaneru/src/learning/engine.rs @@ -1,4 +1,7 @@ -//! The core reinforcement learning engine for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! The core reinforcement learning engine for Kaneru. //! //! Provides implementations of reinforcement learning algorithms including //! Q-Learning, SARSA, and others, along with experience replay. @@ -223,7 +226,7 @@ impl Default for LearningConfig { } } -/// The main reinforcement learning engine for HOPE Agents. +/// The main reinforcement learning engine for Kaneru. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LearningEngine { /// The table of learned Q-values for state-action pairs. diff --git a/crates/hope_agents/src/learning/mod.rs b/crates/kaneru/src/learning/mod.rs similarity index 91% rename from crates/hope_agents/src/learning/mod.rs rename to crates/kaneru/src/learning/mod.rs index ddb2a867..b1ae4b96 100644 --- a/crates/hope_agents/src/learning/mod.rs +++ b/crates/kaneru/src/learning/mod.rs @@ -1,4 +1,7 @@ -//! Learning module for HOPE Agents +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Learning module for Kaneru //! //! This module provides reinforcement learning capabilities for agents including: //! - Q-Learning @@ -14,7 +17,7 @@ //! ## Basic Example //! //! ```rust -//! use hope_agents::{Agent, SimpleAgent, Observation, LearningConfig, LearningAlgorithm}; +//! use kaneru::{Agent, SimpleAgent, Observation, LearningConfig, LearningAlgorithm}; //! //! // Create an agent with learning enabled //! let mut agent = SimpleAgent::new("learning_agent"); @@ -53,7 +56,7 @@ //! ## Advanced Example: Direct Learning Engine Usage //! //! ```rust -//! use hope_agents::learning::{ +//! use kaneru::learning::{ //! LearningEngine, LearningConfig, LearningAlgorithm, //! StateId, ActionId, Experience //! }; diff --git a/crates/hope_agents/src/learning/value_function.rs b/crates/kaneru/src/learning/value_function.rs similarity index 98% rename from crates/hope_agents/src/learning/value_function.rs rename to crates/kaneru/src/learning/value_function.rs index 11d0aa55..7e9f1608 100644 --- a/crates/hope_agents/src/learning/value_function.rs +++ b/crates/kaneru/src/learning/value_function.rs @@ -1,4 +1,7 @@ -//! Value function approximation for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Value function approximation for Kaneru. //! //! Provides different methods for approximating state-value functions (V-functions), //! which estimate how good it is for an agent to be in a given state. diff --git a/crates/hope_agents/src/lib.rs b/crates/kaneru/src/lib.rs similarity index 87% rename from crates/hope_agents/src/lib.rs rename to crates/kaneru/src/lib.rs index f07bbd83..e7bda4a0 100644 --- a/crates/hope_agents/src/lib.rs +++ b/crates/kaneru/src/lib.rs @@ -1,11 +1,14 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![doc = include_str!("../README.md")] -//! # HOPE Agents - Hierarchical Optimizing Policy Engine +//! # Kaneru — Unified Multi-Agent Execution System //! //! Autonomous AI agents framework for AIngle semantic networks. //! //! ## Overview //! -//! HOPE Agents provides a complete framework for building autonomous AI agents that can: +//! Kaneru provides a complete framework for building autonomous AI agents that can: //! - **Observe** their environment (IoT sensors, network events, user inputs) //! - **Decide** based on learned policies and hierarchical goals //! - **Execute** actions in the AIngle network @@ -18,7 +21,7 @@ //! //! ```text //! ┌─────────────────────────────────────────────────────────────┐ -//! │ HOPE Agent │ +//! │ Kaneru Agent │ //! ├─────────────────────────────────────────────────────────────┤ //! │ │ //! │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ @@ -45,7 +48,7 @@ //! ### Simple Reactive Agent //! //! ```rust,ignore -//! use hope_agents::{Agent, SimpleAgent, Goal, Observation, Rule, Condition, Action}; +//! use kaneru::{Agent, SimpleAgent, Goal, Observation, Rule, Condition, Action}; //! //! // Create a simple reactive agent //! let mut agent = SimpleAgent::new("sensor_monitor"); @@ -66,13 +69,13 @@ //! agent.learn(&obs, &action, &result); //! ``` //! -//! ### HOPE Agent with Learning +//! ### Kaneru Agent with Learning //! //! ```rust,ignore -//! use hope_agents::{HopeAgent, HopeConfig, Observation, Goal, Priority, Outcome}; +//! use kaneru::{KaneruAgent, KaneruConfig, Observation, Goal, Priority, Outcome}; //! -//! // Create a HOPE agent with learning, prediction, and hierarchical goals -//! let mut agent = HopeAgent::with_default_config(); +//! // Create a Kaneru agent with learning, prediction, and hierarchical goals +//! let mut agent = KaneruAgent::with_default_config(); //! //! // Set a goal //! let goal = Goal::maintain("temperature", 20.0..25.0) @@ -96,15 +99,15 @@ //! ### Multi-Agent Coordination //! //! ```rust,ignore -//! use hope_agents::{AgentCoordinator, HopeAgent, Message, Observation}; +//! use kaneru::{AgentCoordinator, KaneruAgent, Message, Observation}; //! use std::collections::HashMap; //! //! // Create coordinator //! let mut coordinator = AgentCoordinator::new(); //! //! // Register agents -//! let agent1 = HopeAgent::with_default_config(); -//! let agent2 = HopeAgent::with_default_config(); +//! let agent1 = KaneruAgent::with_default_config(); +//! let agent2 = KaneruAgent::with_default_config(); //! //! let id1 = coordinator.register_agent(agent1); //! let id2 = coordinator.register_agent(agent2); @@ -123,10 +126,10 @@ //! ### State Persistence //! //! ```rust,ignore -//! use hope_agents::{HopeAgent, AgentPersistence}; +//! use kaneru::{KaneruAgent, AgentPersistence}; //! use std::path::Path; //! -//! let mut agent = HopeAgent::with_default_config(); +//! let mut agent = KaneruAgent::with_default_config(); //! //! // Train the agent... //! @@ -134,7 +137,7 @@ //! agent.save_to_file(Path::new("agent_state.json")).unwrap(); //! //! // Later, load agent state -//! let loaded_agent = HopeAgent::load_from_file(Path::new("agent_state.json")).unwrap(); +//! let loaded_agent = KaneruAgent::load_from_file(Path::new("agent_state.json")).unwrap(); //! ``` //! //! ## Agent Types @@ -151,7 +154,7 @@ pub mod coordination; pub mod error; pub mod goal; pub mod hierarchical; -pub mod hope_agent; +pub mod kaneru_agent; pub mod learning; #[cfg(feature = "memory")] pub mod memory; @@ -175,8 +178,8 @@ pub use hierarchical::{ DecompositionRule, DecompositionStrategy, GoalConflict, GoalTree, GoalTypeFilter, HierarchicalGoalSolver, ParallelStrategy, SequentialStrategy, }; -pub use hope_agent::{ - AgentStats, GoalSelectionStrategy, HopeAgent, HopeConfig, OperationMode, Outcome, +pub use kaneru_agent::{ + AgentStats, GoalSelectionStrategy, KaneruAgent, KaneruConfig, OperationMode, Outcome, SerializedState, }; pub use learning::{ @@ -195,7 +198,7 @@ pub use predictive::{ }; pub use types::*; -/// HOPE framework version +/// Kaneru framework version pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Creates a simple agent with default configuration. @@ -211,7 +214,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// # Examples /// /// ``` -/// use hope_agents::create_agent; +/// use kaneru::{create_agent, Agent}; /// /// let agent = create_agent("my_agent"); /// assert_eq!(agent.name(), "my_agent"); @@ -239,7 +242,7 @@ pub fn create_agent(name: &str) -> SimpleAgent { /// # Examples /// /// ``` -/// use hope_agents::create_iot_agent; +/// use kaneru::{create_iot_agent, Agent}; /// /// let agent = create_iot_agent("sensor_agent"); /// assert!(agent.config().max_memory_bytes <= 128 * 1024); diff --git a/crates/hope_agents/src/memory.rs b/crates/kaneru/src/memory.rs similarity index 87% rename from crates/hope_agents/src/memory.rs rename to crates/kaneru/src/memory.rs index 806be46c..a3433bf6 100644 --- a/crates/hope_agents/src/memory.rs +++ b/crates/kaneru/src/memory.rs @@ -1,6 +1,9 @@ -//! Memory integration for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Memory integration for Kaneru. //! -//! This module provides a `MemoryAgent`, a wrapper that integrates the `titans_memory` +//! This module provides a `MemoryAgent`, a wrapper that integrates the `ineru` //! system with a `SimpleAgent` to give it memory capabilities. use crate::action::{Action, ActionResult}; @@ -8,9 +11,9 @@ use crate::agent::{Agent, AgentId, AgentState, SimpleAgent}; use crate::config::AgentConfig; use crate::error::Result; use crate::observation::Observation; -use titans_memory::{MemoryConfig, MemoryEntry, MemoryQuery, TitansMemory}; +use ineru::{MemoryConfig, MemoryEntry, MemoryQuery, IneruMemory}; -/// An agent wrapper that adds memory capabilities using `TitansMemory`. +/// An agent wrapper that adds memory capabilities using `IneruMemory`. /// /// This struct decorates a `SimpleAgent` with a memory system, allowing it to /// remember observations, actions, and their outcomes, and to query that history @@ -18,16 +21,16 @@ use titans_memory::{MemoryConfig, MemoryEntry, MemoryQuery, TitansMemory}; pub struct MemoryAgent { /// The inner, core agent logic. inner: SimpleAgent, - /// The integrated memory system from the `titans_memory` crate. - memory: TitansMemory, + /// The integrated memory system from the `ineru` crate. + memory: IneruMemory, } impl MemoryAgent { - /// Creates a new `MemoryAgent` with a default `SimpleAgent` and an IoT-optimized `TitansMemory`. + /// Creates a new `MemoryAgent` with a default `SimpleAgent` and an IoT-optimized `IneruMemory`. pub fn new(name: &str) -> Self { Self { inner: SimpleAgent::new(name), - memory: TitansMemory::iot_mode(), + memory: IneruMemory::iot_mode(), } } @@ -35,17 +38,17 @@ impl MemoryAgent { pub fn with_config(name: &str, agent_config: AgentConfig, memory_config: MemoryConfig) -> Self { Self { inner: SimpleAgent::with_config(name, agent_config), - memory: TitansMemory::new(memory_config), + memory: IneruMemory::new(memory_config), } } - /// Returns a reference to the `TitansMemory` system. - pub fn memory(&self) -> &TitansMemory { + /// Returns a reference to the `IneruMemory` system. + pub fn memory(&self) -> &IneruMemory { &self.memory } - /// Returns a mutable reference to the `TitansMemory` system. - pub fn memory_mut(&mut self) -> &mut TitansMemory { + /// Returns a mutable reference to the `IneruMemory` system. + pub fn memory_mut(&mut self) -> &mut IneruMemory { &mut self.memory } @@ -129,8 +132,8 @@ impl MemoryAgent { Ok(()) } - /// Returns statistics from the underlying `TitansMemory` system. - pub fn memory_stats(&self) -> titans_memory::MemoryStats { + /// Returns statistics from the underlying `IneruMemory` system. + pub fn memory_stats(&self) -> ineru::MemoryStats { self.memory.stats() } } diff --git a/crates/hope_agents/src/observation.rs b/crates/kaneru/src/observation.rs similarity index 95% rename from crates/hope_agents/src/observation.rs rename to crates/kaneru/src/observation.rs index be17a258..a90f0d02 100644 --- a/crates/hope_agents/src/observation.rs +++ b/crates/kaneru/src/observation.rs @@ -1,4 +1,7 @@ -//! Observation types for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Observation types for Kaneru. //! //! Observations represent the data an agent perceives from its environment, //! forming the basis for its state and decision-making processes. @@ -15,7 +18,7 @@ use std::collections::HashMap; /// # Examples /// /// ``` -/// # use hope_agents::ObservationType; +/// # use kaneru::ObservationType; /// let sensor_type = ObservationType::sensor("temperature"); /// let network_type = ObservationType::network("peer_connected"); /// let alert_type = ObservationType::alert("System overload"); @@ -48,7 +51,7 @@ impl ObservationType { /// # Examples /// /// ``` - /// # use hope_agents::ObservationType; + /// # use kaneru::ObservationType; /// let obs_type = ObservationType::sensor("temperature"); /// ``` pub fn sensor(name: &str) -> Self { @@ -64,7 +67,7 @@ impl ObservationType { /// # Examples /// /// ``` - /// # use hope_agents::ObservationType; + /// # use kaneru::ObservationType; /// let obs_type = ObservationType::network("peer_connected"); /// ``` pub fn network(event: &str) -> Self { @@ -80,7 +83,7 @@ impl ObservationType { /// # Examples /// /// ``` - /// # use hope_agents::ObservationType; + /// # use kaneru::ObservationType; /// let obs_type = ObservationType::alert("Critical error"); /// ``` pub fn alert(msg: &str) -> Self { @@ -97,7 +100,7 @@ impl ObservationType { /// # Examples /// /// ``` -/// # use hope_agents::Observation; +/// # use kaneru::Observation; /// // Simple sensor observation /// let temp_obs = Observation::sensor("temperature", 23.5); /// @@ -136,7 +139,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::{Observation, ObservationType}; + /// # use kaneru::{Observation, ObservationType}; /// let obs = Observation::new(ObservationType::sensor("pressure"), 1013.25); /// ``` pub fn new(obs_type: ObservationType, value: impl Into) -> Self { @@ -159,7 +162,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let temp = Observation::sensor("temperature", 23.5); /// let humidity = Observation::sensor("humidity", 65); /// ``` @@ -176,7 +179,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let alert = Observation::alert("Temperature threshold exceeded"); /// ``` pub fn alert(message: &str) -> Self { @@ -196,7 +199,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::state_change("system_mode", "active"); /// ``` pub fn state_change(state_name: &str, new_value: impl Into) -> Self { @@ -215,7 +218,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::event("user_login"); /// ``` pub fn event(event_name: &str) -> Self { @@ -237,7 +240,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::error("network", "Connection timeout"); /// ``` pub fn error(error_type: &str, message: &str) -> Self { @@ -257,7 +260,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::network("peer_connected", "peer_123"); /// ``` pub fn network(event: &str, data: impl Into) -> Self { @@ -273,7 +276,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::timer("hourly_check"); /// ``` pub fn timer(timer_name: &str) -> Self { @@ -295,7 +298,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::sensor("noisy_sensor", 42.0) /// .with_confidence(0.7); /// ``` @@ -316,7 +319,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::sensor("temperature", 23.5) /// .with_metadata("location", "room_a") /// .with_metadata("sensor_id", "temp_001"); @@ -333,7 +336,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// # use std::thread; /// # use std::time::Duration; /// let obs = Observation::sensor("temp", 20.0); @@ -355,7 +358,7 @@ impl Observation { /// # Examples /// /// ``` - /// # use hope_agents::Observation; + /// # use kaneru::Observation; /// let obs = Observation::sensor("temp", 20.0); /// assert!(obs.is_recent(10)); // Fresh observation is recent /// ``` @@ -373,7 +376,7 @@ impl Observation { /// # Examples /// /// ``` -/// # use hope_agents::{Observation, observation::Sensor}; +/// # use kaneru::{Observation, observation::Sensor}; /// struct TemperatureSensor { /// name: String, /// current_temp: f64, @@ -395,7 +398,7 @@ pub trait Sensor { /// # Examples /// /// ``` - /// # use hope_agents::observation::{Sensor, ValueSensor}; + /// # use kaneru::observation::{Sensor, ValueSensor}; /// let sensor = ValueSensor::new("temp_sensor"); /// assert_eq!(sensor.name(), "temp_sensor"); /// ``` @@ -411,7 +414,7 @@ pub trait Sensor { /// # Examples /// /// ``` - /// # use hope_agents::observation::{Sensor, ValueSensor}; + /// # use kaneru::observation::{Sensor, ValueSensor}; /// let mut sensor = ValueSensor::new("temp"); /// sensor.set_value(23.5); /// let obs = sensor.read().unwrap(); @@ -426,7 +429,7 @@ pub trait Sensor { /// # Examples /// /// ``` - /// # use hope_agents::observation::{Sensor, ValueSensor}; + /// # use kaneru::observation::{Sensor, ValueSensor}; /// let sensor = ValueSensor::new("temp"); /// // Sensor with no value is not available /// assert!(!sensor.is_available()); @@ -444,7 +447,7 @@ pub trait Sensor { /// # Examples /// /// ``` -/// # use hope_agents::observation::{Sensor, ValueSensor}; +/// # use kaneru::observation::{Sensor, ValueSensor}; /// let mut sensor = ValueSensor::new("temperature"); /// /// // Initially, sensor has no value @@ -474,7 +477,7 @@ impl ValueSensor { /// # Examples /// /// ``` - /// # use hope_agents::observation::ValueSensor; + /// # use kaneru::observation::ValueSensor; /// let sensor = ValueSensor::new("my_sensor"); /// ``` pub fn new(name: &str) -> Self { @@ -493,7 +496,7 @@ impl ValueSensor { /// # Examples /// /// ``` - /// # use hope_agents::observation::ValueSensor; + /// # use kaneru::observation::ValueSensor; /// let mut sensor = ValueSensor::new("temp"); /// sensor.set_value(25.0); /// ``` @@ -508,7 +511,7 @@ impl ValueSensor { /// # Examples /// /// ``` - /// # use hope_agents::observation::{Sensor, ValueSensor}; + /// # use kaneru::observation::{Sensor, ValueSensor}; /// let mut sensor = ValueSensor::new("temp"); /// sensor.set_value(25.0); /// sensor.clear(); @@ -544,7 +547,7 @@ impl Sensor for ValueSensor { /// # Examples /// /// ``` -/// # use hope_agents::{Observation, observation::ObservationBuffer}; +/// # use kaneru::{Observation, observation::ObservationBuffer}; /// let mut buffer = ObservationBuffer::new(100); /// /// buffer.push(Observation::sensor("temp", 20.0)); diff --git a/crates/hope_agents/src/persistence.rs b/crates/kaneru/src/persistence.rs similarity index 91% rename from crates/hope_agents/src/persistence.rs rename to crates/kaneru/src/persistence.rs index 00b5248c..c20b51c8 100644 --- a/crates/hope_agents/src/persistence.rs +++ b/crates/kaneru/src/persistence.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Agent State Persistence. //! //! Provides mechanisms for serializing and deserializing agent state to enable: @@ -9,10 +12,10 @@ //! ## Example //! //! ```rust,ignore -//! use hope_agents::{HopeAgent, AgentPersistence}; +//! use kaneru::{KaneruAgent, AgentPersistence}; //! use std::path::Path; //! -//! let mut agent = HopeAgent::with_default_config(); +//! let mut agent = KaneruAgent::with_default_config(); //! //! // ... train the agent ... //! @@ -20,10 +23,10 @@ //! agent.save_to_file(Path::new("agent_state.json")).unwrap(); //! //! // Later, load from the file -//! let loaded_agent = HopeAgent::load_from_file(Path::new("agent_state.json")).unwrap(); +//! let loaded_agent = KaneruAgent::load_from_file(Path::new("agent_state.json")).unwrap(); //! ``` -use crate::{HopeAgent, LearningConfig, LearningEngine}; +use crate::{KaneruAgent, LearningConfig, LearningEngine}; use serde::{Deserialize, Serialize}; use std::fs; use std::io::{Read, Write}; @@ -146,7 +149,7 @@ pub trait AgentPersistence: Sized { /// # Example /// /// ```rust,ignore - /// let agent = HopeAgent::load_from_file(Path::new("agent.json"))?; + /// let agent = KaneruAgent::load_from_file(Path::new("agent.json"))?; /// ``` fn load_from_file(path: &Path) -> Result; @@ -176,7 +179,7 @@ pub trait AgentPersistence: Sized { /// # Example /// /// ```rust,ignore - /// let agent = HopeAgent::from_bytes(&bytes)?; + /// let agent = KaneruAgent::from_bytes(&bytes)?; /// ``` fn from_bytes(bytes: &[u8]) -> Result; @@ -187,7 +190,7 @@ pub trait AgentPersistence: Sized { ) -> Result; } -impl AgentPersistence for HopeAgent { +impl AgentPersistence for KaneruAgent { fn save_to_file(&self, path: &Path) -> Result<(), PersistenceError> { self.save_to_file_with_options(path, &PersistenceOptions::default()) } @@ -202,6 +205,7 @@ impl AgentPersistence for HopeAgent { let mut file = fs::File::create(path)?; file.write_all(&bytes)?; + file.sync_all()?; log::info!("Saved agent state to {:?}", path); Ok(()) @@ -219,9 +223,9 @@ impl AgentPersistence for HopeAgent { let mut bytes = Vec::new(); file.read_to_end(&mut bytes)?; - let state: crate::hope_agent::SerializedState = deserialize_with_options(&bytes, options)?; + let state: crate::kaneru_agent::SerializedState = deserialize_with_options(&bytes, options)?; - let mut agent = HopeAgent::new(state.config.clone()); + let mut agent = KaneruAgent::new(state.config.clone()); agent.load_state(state); log::info!("Loaded agent state from {:?}", path); @@ -249,9 +253,9 @@ impl AgentPersistence for HopeAgent { bytes: &[u8], options: &PersistenceOptions, ) -> Result { - let state: crate::hope_agent::SerializedState = deserialize_with_options(bytes, options)?; + let state: crate::kaneru_agent::SerializedState = deserialize_with_options(bytes, options)?; - let mut agent = HopeAgent::new(state.config.clone()); + let mut agent = KaneruAgent::new(state.config.clone()); agent.load_state(state); Ok(agent) @@ -272,6 +276,7 @@ impl AgentPersistence for LearningEngine { let mut file = fs::File::create(path)?; file.write_all(&bytes)?; + file.sync_all()?; log::info!("Saved learning engine to {:?}", path); Ok(()) @@ -453,7 +458,7 @@ impl CheckpointManager { /// Saves a checkpoint of the agent's state. pub fn save_checkpoint( &mut self, - agent: &HopeAgent, + agent: &KaneruAgent, step: u64, ) -> Result<(), PersistenceError> { // Create checkpoint directory if it doesn't exist @@ -474,7 +479,7 @@ impl CheckpointManager { } /// Loads the most recent checkpoint from the checkpoint directory. - pub fn load_latest_checkpoint(&self) -> Result { + pub fn load_latest_checkpoint(&self) -> Result { let checkpoints = self.list_checkpoints()?; if checkpoints.is_empty() { @@ -484,7 +489,7 @@ impl CheckpointManager { } let latest = checkpoints.last().unwrap(); - HopeAgent::load_from_file(latest) + KaneruAgent::load_from_file(latest) } /// Lists all checkpoint files in the directory, sorted by step number. @@ -532,18 +537,18 @@ impl CheckpointManager { #[cfg(test)] mod tests { use super::*; - use crate::{HopeAgent, Observation}; + use crate::{KaneruAgent, Observation}; use std::path::PathBuf; fn temp_path(name: &str) -> PathBuf { let mut path = std::env::temp_dir(); - path.push(format!("hope_agents_test_{}", name)); + path.push(format!("kaneru_test_{}", name)); path } #[test] - fn test_save_and_load_hope_agent() { - let mut agent = HopeAgent::with_default_config(); + fn test_save_and_load_kaneru_agent() { + let mut agent = KaneruAgent::with_default_config(); // Do some steps to create state for i in 0..5 { @@ -558,7 +563,7 @@ mod tests { assert!(path.exists()); // Load - let loaded_agent = HopeAgent::load_from_file(&path).unwrap(); + let loaded_agent = KaneruAgent::load_from_file(&path).unwrap(); assert_eq!( loaded_agent.get_statistics().total_steps, agent.get_statistics().total_steps @@ -570,7 +575,7 @@ mod tests { #[test] fn test_save_with_different_options() { - let agent = HopeAgent::with_default_config(); + let agent = KaneruAgent::with_default_config(); // Save with compact options let path = temp_path("agent_compact.bin"); @@ -579,7 +584,7 @@ mod tests { assert!(path.exists()); // Load with same options - let _loaded = HopeAgent::load_from_file_with_options(&path, &options).unwrap(); + let _loaded = KaneruAgent::load_from_file_with_options(&path, &options).unwrap(); // Cleanup let _ = fs::remove_file(&path); @@ -587,7 +592,7 @@ mod tests { #[test] fn test_to_bytes_and_from_bytes() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Do some steps let obs = Observation::sensor("temp", 25.0); @@ -598,7 +603,7 @@ mod tests { assert!(!bytes.is_empty()); // Deserialize from bytes - let loaded_agent = HopeAgent::from_bytes(&bytes).unwrap(); + let loaded_agent = KaneruAgent::from_bytes(&bytes).unwrap(); assert_eq!( loaded_agent.get_statistics().total_steps, agent.get_statistics().total_steps @@ -627,7 +632,7 @@ mod tests { let checkpoint_dir = temp_path("checkpoints"); let mut manager = CheckpointManager::new(&checkpoint_dir, 3).with_interval(10); - let agent = HopeAgent::with_default_config(); + let agent = KaneruAgent::with_default_config(); // Should checkpoint at intervals assert!(manager.should_checkpoint(10)); @@ -649,7 +654,7 @@ mod tests { let checkpoint_dir = temp_path("checkpoints_cleanup"); let mut manager = CheckpointManager::new(&checkpoint_dir, 2).with_interval(1); - let agent = HopeAgent::with_default_config(); + let agent = KaneruAgent::with_default_config(); // Save more checkpoints than max manager.save_checkpoint(&agent, 1).unwrap(); @@ -667,7 +672,7 @@ mod tests { #[test] fn test_roundtrip_with_compression() { - let agent = HopeAgent::with_default_config(); + let agent = KaneruAgent::with_default_config(); let options = PersistenceOptions { format: PersistenceFormat::Json, @@ -676,7 +681,7 @@ mod tests { }; let bytes = agent.to_bytes_with_options(&options).unwrap(); - let loaded = HopeAgent::from_bytes_with_options(&bytes, &options).unwrap(); + let loaded = KaneruAgent::from_bytes_with_options(&bytes, &options).unwrap(); assert_eq!( loaded.get_statistics().total_steps, @@ -687,7 +692,7 @@ mod tests { #[test] fn test_persistence_error_handling() { let invalid_path = PathBuf::from("/invalid/path/that/does/not/exist/agent.json"); - let result = HopeAgent::load_from_file(&invalid_path); + let result = KaneruAgent::load_from_file(&invalid_path); assert!(result.is_err()); } } diff --git a/crates/hope_agents/src/policy.rs b/crates/kaneru/src/policy.rs similarity index 99% rename from crates/hope_agents/src/policy.rs rename to crates/kaneru/src/policy.rs index 8751d931..a7961416 100644 --- a/crates/hope_agents/src/policy.rs +++ b/crates/kaneru/src/policy.rs @@ -1,4 +1,7 @@ -//! Policy engine for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Policy engine for Kaneru. //! //! Policies and rules define how an agent makes decisions based on its observations. diff --git a/crates/hope_agents/src/predictive/anomaly.rs b/crates/kaneru/src/predictive/anomaly.rs similarity index 98% rename from crates/hope_agents/src/predictive/anomaly.rs rename to crates/kaneru/src/predictive/anomaly.rs index 94b2898c..57b7fff6 100644 --- a/crates/hope_agents/src/predictive/anomaly.rs +++ b/crates/kaneru/src/predictive/anomaly.rs @@ -1,4 +1,7 @@ -//! Anomaly detection for HOPE Agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Anomaly detection for Kaneru. use crate::Observation; use std::collections::VecDeque; diff --git a/crates/hope_agents/src/predictive/mod.rs b/crates/kaneru/src/predictive/mod.rs similarity index 81% rename from crates/hope_agents/src/predictive/mod.rs rename to crates/kaneru/src/predictive/mod.rs index b568010a..005b239b 100644 --- a/crates/hope_agents/src/predictive/mod.rs +++ b/crates/kaneru/src/predictive/mod.rs @@ -1,4 +1,7 @@ -//! Predictive modeling for state and reward prediction in HOPE agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Predictive modeling for state and reward prediction in Kaneru agents. //! //! This module provides capabilities for: //! - Predicting next states given current state and action @@ -18,8 +21,8 @@ //! ## Example //! //! ```rust,ignore -//! use hope_agents::predictive::{PredictiveModel, PredictiveConfig}; -//! use hope_agents::{Observation, Action, ActionType}; +//! use kaneru::predictive::{PredictiveModel, PredictiveConfig}; +//! use kaneru::{Observation, Action, ActionType}; //! //! // Create predictive model //! let mut model = PredictiveModel::with_default_config(); diff --git a/crates/hope_agents/src/predictive/model.rs b/crates/kaneru/src/predictive/model.rs similarity index 98% rename from crates/hope_agents/src/predictive/model.rs rename to crates/kaneru/src/predictive/model.rs index 604e5a03..68d52211 100644 --- a/crates/hope_agents/src/predictive/model.rs +++ b/crates/kaneru/src/predictive/model.rs @@ -1,4 +1,7 @@ -//! The core predictive model for HOPE agents. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! The core predictive model for Kaneru agents. use crate::predictive::{AnomalyDetector, StateEncoder, TransitionModel}; use crate::{Action, Observation, Timestamp}; diff --git a/crates/hope_agents/src/predictive/transition.rs b/crates/kaneru/src/predictive/transition.rs similarity index 98% rename from crates/hope_agents/src/predictive/transition.rs rename to crates/kaneru/src/predictive/transition.rs index c31a661b..cf09407b 100644 --- a/crates/hope_agents/src/predictive/transition.rs +++ b/crates/kaneru/src/predictive/transition.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A model for learning and predicting state transitions. use crate::{Action, Observation}; diff --git a/crates/hope_agents/src/types.rs b/crates/kaneru/src/types.rs similarity index 98% rename from crates/hope_agents/src/types.rs rename to crates/kaneru/src/types.rs index 25906e99..34feffa2 100644 --- a/crates/hope_agents/src/types.rs +++ b/crates/kaneru/src/types.rs @@ -1,4 +1,7 @@ -//! Core, general-purpose data types for the HOPE Agents framework. +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Core, general-purpose data types for the Kaneru framework. use serde::{Deserialize, Serialize}; diff --git a/crates/hope_agents/tests/agent_persistence_tests.rs b/crates/kaneru/tests/agent_persistence_tests.rs similarity index 96% rename from crates/hope_agents/tests/agent_persistence_tests.rs rename to crates/kaneru/tests/agent_persistence_tests.rs index f2de63a9..e4e9eed5 100644 --- a/crates/hope_agents/tests/agent_persistence_tests.rs +++ b/crates/kaneru/tests/agent_persistence_tests.rs @@ -1,10 +1,13 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Integration tests for agent persistence //! //! Tests agent state serialization, learning state persistence, //! goal manager persistence, and checkpoint management. -use hope_agents::policy::Condition; -use hope_agents::{ +use kaneru::policy::Condition; +use kaneru::{ Action, Agent, AgentConfig, Goal, GoalStatus, Observation, Policy, Rule, SimpleAgent, }; @@ -222,7 +225,7 @@ fn test_complete_agent_state_persistence() { let stats_json = serde_json::to_string(stats).unwrap(); // Verify we can restore stats - let restored_stats: hope_agents::agent::AgentStats = serde_json::from_str(&stats_json).unwrap(); + let restored_stats: kaneru::agent::AgentStats = serde_json::from_str(&stats_json).unwrap(); assert_eq!(restored_stats.observations_received, 10); } diff --git a/crates/hope_agents/tests/integration_test.rs b/crates/kaneru/tests/integration_test.rs similarity index 90% rename from crates/hope_agents/tests/integration_test.rs rename to crates/kaneru/tests/integration_test.rs index e6d7f1fd..2b60ec2d 100644 --- a/crates/hope_agents/tests/integration_test.rs +++ b/crates/kaneru/tests/integration_test.rs @@ -1,9 +1,12 @@ -//! Integration tests for HOPE Agents +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Integration tests for Kaneru //! -//! These tests demonstrate the complete functionality of the HOPE Agents framework, +//! These tests demonstrate the complete functionality of the Kaneru framework, //! including coordination, persistence, learning, and goal management. -use hope_agents::*; +use kaneru::*; use std::collections::HashMap; /// Test basic agent creation and operation @@ -38,10 +41,10 @@ fn test_simple_agent_workflow() { assert_eq!(agent.stats().actions_executed, 1); } -/// Test HOPE agent with full learning cycle +/// Test Kaneru agent with full learning cycle #[test] -fn test_hope_agent_learning_cycle() { - let mut agent = HopeAgent::with_default_config(); +fn test_kaneru_agent_learning_cycle() { + let mut agent = KaneruAgent::with_default_config(); // Set a goal let goal = Goal::maintain("temperature", 20.0..25.0).with_priority(Priority::High); @@ -83,9 +86,9 @@ fn test_multi_agent_coordination() { let mut coordinator = AgentCoordinator::new(); // Create and register multiple agents - let agent1 = HopeAgent::with_default_config(); - let agent2 = HopeAgent::with_default_config(); - let agent3 = HopeAgent::with_default_config(); + let agent1 = KaneruAgent::with_default_config(); + let agent2 = KaneruAgent::with_default_config(); + let agent3 = KaneruAgent::with_default_config(); let id1 = coordinator.register_agent(agent1); let id2 = coordinator.register_agent(agent2); @@ -132,7 +135,7 @@ fn test_consensus_mechanism() { // Register agents let agents: Vec<_> = (0..5) - .map(|_| coordinator.register_agent(HopeAgent::with_default_config())) + .map(|_| coordinator.register_agent(KaneruAgent::with_default_config())) .collect(); // Create a proposal @@ -196,7 +199,7 @@ fn test_consensus_mechanism() { /// Test agent persistence (save/load) #[test] fn test_agent_persistence() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Train the agent for i in 0..20 { @@ -216,7 +219,7 @@ fn test_agent_persistence() { assert!(temp_path.exists()); // Load from file - let loaded_agent = HopeAgent::load_from_file(&temp_path).unwrap(); + let loaded_agent = KaneruAgent::load_from_file(&temp_path).unwrap(); assert_eq!(loaded_agent.get_statistics().total_steps, original_steps); assert_eq!( @@ -231,7 +234,7 @@ fn test_agent_persistence() { /// Test persistence with different formats #[test] fn test_persistence_formats() { - let agent = HopeAgent::with_default_config(); + let agent = KaneruAgent::with_default_config(); // Test JSON format let json_options = PersistenceOptions { @@ -240,7 +243,7 @@ fn test_persistence_formats() { compress: false, }; let json_bytes = agent.to_bytes_with_options(&json_options).unwrap(); - let loaded_from_json = HopeAgent::from_bytes_with_options(&json_bytes, &json_options).unwrap(); + let loaded_from_json = KaneruAgent::from_bytes_with_options(&json_bytes, &json_options).unwrap(); assert_eq!( loaded_from_json.get_statistics().total_steps, agent.get_statistics().total_steps @@ -254,7 +257,7 @@ fn test_persistence_formats() { }; let compressed_bytes = agent.to_bytes_with_options(&compressed_options).unwrap(); let loaded_compressed = - HopeAgent::from_bytes_with_options(&compressed_bytes, &compressed_options).unwrap(); + KaneruAgent::from_bytes_with_options(&compressed_bytes, &compressed_options).unwrap(); assert_eq!( loaded_compressed.get_statistics().total_steps, agent.get_statistics().total_steps @@ -264,10 +267,10 @@ fn test_persistence_formats() { /// Test checkpoint manager #[test] fn test_checkpoint_manager() { - let checkpoint_dir = std::env::temp_dir().join("hope_checkpoints"); + let checkpoint_dir = std::env::temp_dir().join("kaneru_checkpoints"); let mut manager = CheckpointManager::new(&checkpoint_dir, 3).with_interval(10); - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Train and checkpoint for step in 1..=35 { @@ -293,7 +296,7 @@ fn test_checkpoint_manager() { /// Test hierarchical goal decomposition #[test] fn test_hierarchical_goals() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Create a complex goal let parent_goal = Goal::achieve("optimize_system", 1.0).with_priority(Priority::High); @@ -308,7 +311,7 @@ fn test_hierarchical_goals() { /// Test operation mode switching #[test] fn test_operation_modes() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Test different modes agent.set_mode(OperationMode::Exploration); @@ -332,7 +335,7 @@ fn test_operation_modes() { /// Test anomaly detection #[test] fn test_anomaly_detection() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); // Establish normal pattern for i in 0..20 { @@ -436,11 +439,11 @@ fn test_complete_multi_agent_scenario() { let mut coordinator = AgentCoordinator::new(); // Create agents with different goals - let mut agent1 = HopeAgent::with_default_config(); + let mut agent1 = KaneruAgent::with_default_config(); let goal1 = Goal::maintain("temperature", 20.0..25.0); agent1.set_goal(goal1); - let mut agent2 = HopeAgent::with_default_config(); + let mut agent2 = KaneruAgent::with_default_config(); let goal2 = Goal::maintain("humidity", 40.0..60.0); agent2.set_goal(goal2); @@ -500,7 +503,7 @@ fn test_complete_multi_agent_scenario() { /// Benchmark-style test to verify performance #[test] fn test_performance() { - let mut agent = HopeAgent::with_default_config(); + let mut agent = KaneruAgent::with_default_config(); let start = std::time::Instant::now(); diff --git a/crates/hope_agents/tests/integration_tests.rs b/crates/kaneru/tests/integration_tests.rs similarity index 96% rename from crates/hope_agents/tests/integration_tests.rs rename to crates/kaneru/tests/integration_tests.rs index 99ea6348..f8228570 100644 --- a/crates/hope_agents/tests/integration_tests.rs +++ b/crates/kaneru/tests/integration_tests.rs @@ -1,10 +1,13 @@ -//! Integration tests for HOPE Agents with Titans Memory +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + +//! Integration tests for Kaneru with Ineru memory //! //! These tests verify the complete workflow of memory-enabled agents. #![cfg(feature = "memory")] -use hope_agents::{ +use kaneru::{ action::{Action, ActionType}, agent::Agent, config::AgentConfig, @@ -13,7 +16,7 @@ use hope_agents::{ observation::Observation, policy::{Condition, Rule}, }; -use titans_memory::MemoryConfig; +use ineru::MemoryConfig; /// Test: Create a memory agent and store observations #[test] diff --git a/crates/kitsune_p2p/bootstrap/Cargo.toml b/crates/kitsune_p2p/bootstrap/Cargo.toml index b6e99c85..c024cb82 100644 --- a/crates/kitsune_p2p/bootstrap/Cargo.toml +++ b/crates/kitsune_p2p/bootstrap/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_bootstrap" version = "0.0.1" description = "Bootstrap server written in rust for kitsune nodes to find each other" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_bootstrap" @@ -16,7 +16,7 @@ edition = "2018" [dependencies] tokio = { version = "1", features = ["full"] } warp = "0.3" -rmp-serde = "0.15" +rmp-serde = "1" parking_lot = "0.12" rand = "0.9" serde = { version = "1", features = [ "derive", "rc" ] } diff --git a/crates/kitsune_p2p/bootstrap/benches/bench.rs b/crates/kitsune_p2p/bootstrap/benches/bench.rs index 02557da8..1c2502bd 100644 --- a/crates/kitsune_p2p/bootstrap/benches/bench.rs +++ b/crates/kitsune_p2p/bootstrap/benches/bench.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::sync::Arc; use criterion::criterion_group; diff --git a/crates/kitsune_p2p/bootstrap/src/clear.rs b/crates/kitsune_p2p/bootstrap/src/clear.rs index a188ac37..f4fda51f 100644 --- a/crates/kitsune_p2p/bootstrap/src/clear.rs +++ b/crates/kitsune_p2p/bootstrap/src/clear.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::store::Store; use super::*; diff --git a/crates/kitsune_p2p/bootstrap/src/lib.rs b/crates/kitsune_p2p/bootstrap/src/lib.rs index 5f7638f2..261e390f 100644 --- a/crates/kitsune_p2p/bootstrap/src/lib.rs +++ b/crates/kitsune_p2p/bootstrap/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::net::SocketAddr; use std::sync::atomic::AtomicUsize; diff --git a/crates/kitsune_p2p/bootstrap/src/main.rs b/crates/kitsune_p2p/bootstrap/src/main.rs index 4fa365c2..722e562c 100644 --- a/crates/kitsune_p2p/bootstrap/src/main.rs +++ b/crates/kitsune_p2p/bootstrap/src/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use tokio::sync::oneshot; #[tokio::main] diff --git a/crates/kitsune_p2p/bootstrap/src/now.rs b/crates/kitsune_p2p/bootstrap/src/now.rs index 4262edd1..08945e07 100644 --- a/crates/kitsune_p2p/bootstrap/src/now.rs +++ b/crates/kitsune_p2p/bootstrap/src/now.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use warp::Filter; diff --git a/crates/kitsune_p2p/bootstrap/src/put.rs b/crates/kitsune_p2p/bootstrap/src/put.rs index 07edc761..978a9795 100644 --- a/crates/kitsune_p2p/bootstrap/src/put.rs +++ b/crates/kitsune_p2p/bootstrap/src/put.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::store::Store; use super::*; diff --git a/crates/kitsune_p2p/bootstrap/src/random.rs b/crates/kitsune_p2p/bootstrap/src/random.rs index ca7507a5..e36aec6c 100644 --- a/crates/kitsune_p2p/bootstrap/src/random.rs +++ b/crates/kitsune_p2p/bootstrap/src/random.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::store::Store; use super::*; diff --git a/crates/kitsune_p2p/bootstrap/src/store.rs b/crates/kitsune_p2p/bootstrap/src/store.rs index bc7921be..c5a85ace 100644 --- a/crates/kitsune_p2p/bootstrap/src/store.rs +++ b/crates/kitsune_p2p/bootstrap/src/store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::{collections::HashMap, sync::Arc}; use kitsune_p2p_types::{ diff --git a/crates/kitsune_p2p/direct/Cargo.toml b/crates/kitsune_p2p/direct/Cargo.toml index 0a83a0c1..771096d9 100644 --- a/crates/kitsune_p2p/direct/Cargo.toml +++ b/crates/kitsune_p2p/direct/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_direct" version = "0.0.1" description = "Kitsune P2p Direct Application Framework" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_direct" @@ -16,9 +16,9 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test_util [dependencies] arrayref = "0.3.6" -base64 = "0.21" +base64 = "0.22" derive_more = "0.99" -futures = "0.3.14" +futures = "0.3.32" hyper = { version = "0.14", features = ["server","http1","http2","tcp"] } kitsune_p2p_direct_api = { version = "0.0.1", path = "../direct_api" } kitsune_p2p_types = { version = "0.0.1", path = "../types" } diff --git a/crates/kitsune_p2p/direct/examples/srv-echo.rs b/crates/kitsune_p2p/direct/examples/srv-echo.rs index eae59ac0..caf0f266 100644 --- a/crates/kitsune_p2p/direct/examples/srv-echo.rs +++ b/crates/kitsune_p2p/direct/examples/srv-echo.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use kitsune_p2p_direct::prelude::*; diff --git a/crates/kitsune_p2p/direct/src/bin/kd/cmd_node.rs b/crates/kitsune_p2p/direct/src/bin/kd/cmd_node.rs index 13caf648..5621e226 100644 --- a/crates/kitsune_p2p/direct/src/bin/kd/cmd_node.rs +++ b/crates/kitsune_p2p/direct/src/bin/kd/cmd_node.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/direct/src/bin/kd/cmd_proxy.rs b/crates/kitsune_p2p/direct/src/bin/kd/cmd_proxy.rs index ce3535ec..e569bd8e 100644 --- a/crates/kitsune_p2p/direct/src/bin/kd/cmd_proxy.rs +++ b/crates/kitsune_p2p/direct/src/bin/kd/cmd_proxy.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; pub(crate) async fn run(_opt: KdOptProxy) -> KdResult<()> { diff --git a/crates/kitsune_p2p/direct/src/bin/kd/main.rs b/crates/kitsune_p2p/direct/src/bin/kd/main.rs index 89f1014d..ea58402a 100644 --- a/crates/kitsune_p2p/direct/src/bin/kd/main.rs +++ b/crates/kitsune_p2p/direct/src/bin/kd/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use kitsune_p2p_direct::prelude::*; use structopt::StructOpt; diff --git a/crates/kitsune_p2p/direct/src/handle_ws.rs b/crates/kitsune_p2p/direct/src/handle_ws.rs index f3d20b5a..81de0572 100644 --- a/crates/kitsune_p2p/direct/src/handle_ws.rs +++ b/crates/kitsune_p2p/direct/src/handle_ws.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::types::handle::*; use crate::*; use futures::future::{BoxFuture, FutureExt}; diff --git a/crates/kitsune_p2p/direct/src/lib.rs b/crates/kitsune_p2p/direct/src/lib.rs index 39707e34..80949ae5 100644 --- a/crates/kitsune_p2p/direct/src/lib.rs +++ b/crates/kitsune_p2p/direct/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Kitsune P2p Direct Application Framework #![deny(warnings)] #![deny(missing_docs)] diff --git a/crates/kitsune_p2p/direct/src/persist_mem.rs b/crates/kitsune_p2p/direct/src/persist_mem.rs index d825c81f..2af94b75 100644 --- a/crates/kitsune_p2p/direct/src/persist_mem.rs +++ b/crates/kitsune_p2p/direct/src/persist_mem.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! in-memory persistence module for kitsune direct use crate::types::metric_store::KdMetricStore; diff --git a/crates/kitsune_p2p/direct/src/srv.rs b/crates/kitsune_p2p/direct/src/srv.rs index d850c663..399fe92c 100644 --- a/crates/kitsune_p2p/direct/src/srv.rs +++ b/crates/kitsune_p2p/direct/src/srv.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::dependencies::tracing; use crate::*; diff --git a/crates/kitsune_p2p/direct/src/test.rs b/crates/kitsune_p2p/direct/src/test.rs index f2ccc08a..8ebffc7a 100644 --- a/crates/kitsune_p2p/direct/src/test.rs +++ b/crates/kitsune_p2p/direct/src/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::prelude::*; use futures::stream::StreamExt; use kitsune_p2p_direct_api::kd_sys_kind::{self, *}; diff --git a/crates/kitsune_p2p/direct/src/types.rs b/crates/kitsune_p2p/direct/src/types.rs index a1a7288c..3b5b41f2 100644 --- a/crates/kitsune_p2p/direct/src/types.rs +++ b/crates/kitsune_p2p/direct/src/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect types pub mod direct; diff --git a/crates/kitsune_p2p/direct/src/types/direct.rs b/crates/kitsune_p2p/direct/src/types/direct.rs index e5a275ef..73b920fb 100644 --- a/crates/kitsune_p2p/direct/src/types/direct.rs +++ b/crates/kitsune_p2p/direct/src/types/direct.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect entrypoint type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/types/handle.rs b/crates/kitsune_p2p/direct/src/types/handle.rs index fcbd0a5d..fd74b05c 100644 --- a/crates/kitsune_p2p/direct/src/types/handle.rs +++ b/crates/kitsune_p2p/direct/src/types/handle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect api type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/types/kdagent.rs b/crates/kitsune_p2p/direct/src/types/kdagent.rs index f7b39b98..520178cf 100644 --- a/crates/kitsune_p2p/direct/src/types/kdagent.rs +++ b/crates/kitsune_p2p/direct/src/types/kdagent.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdagent type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/types/kdentry.rs b/crates/kitsune_p2p/direct/src/types/kdentry.rs index a085751f..7761170e 100644 --- a/crates/kitsune_p2p/direct/src/types/kdentry.rs +++ b/crates/kitsune_p2p/direct/src/types/kdentry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdentry type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/types/kdhash.rs b/crates/kitsune_p2p/direct/src/types/kdhash.rs index f14830e6..23c0ac0c 100644 --- a/crates/kitsune_p2p/direct/src/types/kdhash.rs +++ b/crates/kitsune_p2p/direct/src/types/kdhash.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdhash type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/types/metric_store.rs b/crates/kitsune_p2p/direct/src/types/metric_store.rs index 784ede23..5682bb43 100644 --- a/crates/kitsune_p2p/direct/src/types/metric_store.rs +++ b/crates/kitsune_p2p/direct/src/types/metric_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! An in-memory implementation of a metric store. //! A real implementation would use a database. diff --git a/crates/kitsune_p2p/direct/src/types/persist.rs b/crates/kitsune_p2p/direct/src/types/persist.rs index 1bae4cbc..573b00b4 100644 --- a/crates/kitsune_p2p/direct/src/types/persist.rs +++ b/crates/kitsune_p2p/direct/src/types/persist.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect persist type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/types/srv.rs b/crates/kitsune_p2p/direct/src/types/srv.rs index c408a917..fa7e84af 100644 --- a/crates/kitsune_p2p/direct/src/types/srv.rs +++ b/crates/kitsune_p2p/direct/src/types/srv.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect srv type use crate::*; diff --git a/crates/kitsune_p2p/direct/src/v1.rs b/crates/kitsune_p2p/direct/src/v1.rs index 98195e5d..32beb91c 100644 --- a/crates/kitsune_p2p/direct/src/v1.rs +++ b/crates/kitsune_p2p/direct/src/v1.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::field_reassign_with_default)] use crate::prelude::*; use crate::*; diff --git a/crates/kitsune_p2p/direct_api/Cargo.toml b/crates/kitsune_p2p/direct_api/Cargo.toml index 4136397c..d8bf9daf 100644 --- a/crates/kitsune_p2p/direct_api/Cargo.toml +++ b/crates/kitsune_p2p/direct_api/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_direct_api" version = "0.0.1" description = "Kitsune P2p Direct Application Framework Test Harness Common API Types" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_direct_api" @@ -13,6 +13,6 @@ edition = "2018" [dependencies] arrayref = "0.3.6" -base64 = "0.21" +base64 = "0.22" serde = { version = "1", features = ["derive", "rc"] } serde_json = { version = "1", features = ["preserve_order"] } diff --git a/crates/kitsune_p2p/direct_api/src/kd_sys_kind.rs b/crates/kitsune_p2p/direct_api/src/kd_sys_kind.rs index 814c87da..4cf5316f 100644 --- a/crates/kitsune_p2p/direct_api/src/kd_sys_kind.rs +++ b/crates/kitsune_p2p/direct_api/src/kd_sys_kind.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdsyskind types use crate::*; diff --git a/crates/kitsune_p2p/direct_api/src/kdagent.rs b/crates/kitsune_p2p/direct_api/src/kdagent.rs index 7fa7b13e..f7e72035 100644 --- a/crates/kitsune_p2p/direct_api/src/kdagent.rs +++ b/crates/kitsune_p2p/direct_api/src/kdagent.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdagent type use crate::*; diff --git a/crates/kitsune_p2p/direct_api/src/kdapi.rs b/crates/kitsune_p2p/direct_api/src/kdapi.rs index 506f7ade..051e6e6c 100644 --- a/crates/kitsune_p2p/direct_api/src/kdapi.rs +++ b/crates/kitsune_p2p/direct_api/src/kdapi.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdapi types use crate::*; diff --git a/crates/kitsune_p2p/direct_api/src/kdentry.rs b/crates/kitsune_p2p/direct_api/src/kdentry.rs index 6e8cc193..9c06d3c3 100644 --- a/crates/kitsune_p2p/direct_api/src/kdentry.rs +++ b/crates/kitsune_p2p/direct_api/src/kdentry.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdentry type use crate::*; diff --git a/crates/kitsune_p2p/direct_api/src/kderror.rs b/crates/kitsune_p2p/direct_api/src/kderror.rs index 74617d0f..62f10861 100644 --- a/crates/kitsune_p2p/direct_api/src/kderror.rs +++ b/crates/kitsune_p2p/direct_api/src/kderror.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kderror type use crate::*; diff --git a/crates/kitsune_p2p/direct_api/src/kdhash.rs b/crates/kitsune_p2p/direct_api/src/kdhash.rs index c24900f8..8d1c4d8c 100644 --- a/crates/kitsune_p2p/direct_api/src/kdhash.rs +++ b/crates/kitsune_p2p/direct_api/src/kdhash.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! kdirect kdhash type use crate::*; diff --git a/crates/kitsune_p2p/direct_api/src/lib.rs b/crates/kitsune_p2p/direct_api/src/lib.rs index b7b9806d..f89927a2 100644 --- a/crates/kitsune_p2p/direct_api/src/lib.rs +++ b/crates/kitsune_p2p/direct_api/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Kitsune P2p Direct Application Framework Test Harness Common API Types #![deny(warnings)] #![deny(missing_docs)] diff --git a/crates/kitsune_p2p/direct_test/Cargo.toml b/crates/kitsune_p2p/direct_test/Cargo.toml index 4748a9b9..760764fc 100644 --- a/crates/kitsune_p2p/direct_test/Cargo.toml +++ b/crates/kitsune_p2p/direct_test/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_direct_test" version = "0.0.1" description = "Kitsune P2p Direct Application Framework Test Harness" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_direct_test" diff --git a/crates/kitsune_p2p/direct_test/src/bin/direct-test-local-periodic.rs b/crates/kitsune_p2p/direct_test/src/bin/direct-test-local-periodic.rs index d3193a03..b87333db 100644 --- a/crates/kitsune_p2p/direct_test/src/bin/direct-test-local-periodic.rs +++ b/crates/kitsune_p2p/direct_test/src/bin/direct-test-local-periodic.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::field_reassign_with_default)] use futures::future::FutureExt; use kitsune_p2p_direct::dependencies::*; diff --git a/crates/kitsune_p2p/direct_test/src/lib.rs b/crates/kitsune_p2p/direct_test/src/lib.rs index e1e05fa6..93c5403d 100644 --- a/crates/kitsune_p2p/direct_test/src/lib.rs +++ b/crates/kitsune_p2p/direct_test/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Kitsune P2p Direct Application Framework Test Harness #![deny(warnings)] #![deny(missing_docs)] diff --git a/crates/kitsune_p2p/kitsune_p2p/Cargo.toml b/crates/kitsune_p2p/kitsune_p2p/Cargo.toml index e0d600e5..11c1cb55 100644 --- a/crates/kitsune_p2p/kitsune_p2p/Cargo.toml +++ b/crates/kitsune_p2p/kitsune_p2p/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p" version = "0.0.1" description = "p2p / sgd communication framework" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p" @@ -16,7 +16,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test_util [dependencies] arrayref = "0.3.6" -base64 = "0.21" +base64 = "0.22" bloomfilter = { version = "1.0.5", features = [ "serde" ] } derive_more = "0.99.11" futures = "0.3" diff --git a/crates/kitsune_p2p/kitsune_p2p/src/config.rs b/crates/kitsune_p2p/kitsune_p2p/src/config.rs index 1ad78ce0..2358044f 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/config.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use kitsune_p2p_types::config::KitsuneP2pTuningParams; use kitsune_p2p_types::tx2::tx2_utils::*; use kitsune_p2p_types::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/fixt.rs b/crates/kitsune_p2p/kitsune_p2p/src/fixt.rs index 158889ea..6e43445c 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/fixt.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/fixt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Fixturator definitions for kitsune_p2p. use crate::agent_store::AgentInfoSigned; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/gossip.rs b/crates/kitsune_p2p/kitsune_p2p/src/gossip.rs index cfbae281..fae5e173 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/gossip.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/gossip.rs @@ -1 +1,4 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + pub mod simple_bloom; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom.rs b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom.rs index e565e11e..704d216b 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::agent_store::AgentInfoSigned; use crate::event::MetricQuery; use crate::event::MetricQueryAnswer; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_1_check_inner.rs b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_1_check_inner.rs index c3cf9c19..4253d271 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_1_check_inner.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_1_check_inner.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use kitsune_p2p_types::*; use observability::tracing; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_2_local_sync_inner.rs b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_2_local_sync_inner.rs index 63ad8e9f..b567f631 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_2_local_sync_inner.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_2_local_sync_inner.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use kitsune_p2p_types::sgd_arc::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_3_initiate_inner.rs b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_3_initiate_inner.rs index 23069ee8..a779d2bc 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_3_initiate_inner.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_3_initiate_inner.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; impl SimpleBloomMod { diff --git a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_4_com_loop_inner.rs b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_4_com_loop_inner.rs index 9050e770..7f31fff9 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_4_com_loop_inner.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/gossip/simple_bloom/step_4_com_loop_inner.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use kitsune_p2p_types::codec::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/lib.rs b/crates/kitsune_p2p/kitsune_p2p/src/lib.rs index 671a6d2b..1f11721b 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/lib.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] diff --git a/crates/kitsune_p2p/kitsune_p2p/src/spawn.rs b/crates/kitsune_p2p/kitsune_p2p/src/spawn.rs index e0059057..bb686059 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/spawn.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/spawn.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::actor::*; use crate::event::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor.rs b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor.rs index 5e7be360..93a52ca4 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + // this is largely a passthrough that routes to a specific space handler use crate::actor; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/bootstrap.rs b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/bootstrap.rs index 0f9343c3..df6012dc 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/bootstrap.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/bootstrap.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::types::agent_store::AgentInfoSigned; use kitsune_p2p_types::bootstrap::RandomQuery; use once_cell::sync::Lazy; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/discover.rs b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/discover.rs index ae2bacc0..018faef7 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/discover.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/discover.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(dead_code)] use super::*; use ghost_actor::dependencies::must_future::MustBoxFuture; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/space.rs b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/space.rs index d5e20a28..4b222b16 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/space.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/spawn/actor/space.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use crate::types::gossip::GossipModule; use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/test.rs b/crates/kitsune_p2p/kitsune_p2p/src/test.rs index 0920483b..a46d3a1b 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/test.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #[cfg(test)] mod tests { use crate::test_util::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/test_util.rs b/crates/kitsune_p2p/kitsune_p2p/src/test_util.rs index f3fda13b..34339642 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/test_util.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/test_util.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Utilities to make kitsune testing a little more sane. use crate::types::actor::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_actor.rs b/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_actor.rs index 595dc693..c78d8a4a 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_actor.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; ghost_actor::ghost_chan! { diff --git a/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_agent.rs b/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_agent.rs index 9a071464..d81eb2de 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_agent.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_agent.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; ghost_actor::ghost_chan! { diff --git a/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_event.rs b/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_event.rs index de805049..dc50147a 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_event.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/test_util/harness_event.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::*; use futures::sink::SinkExt; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/test_util/metric_store.rs b/crates/kitsune_p2p/kitsune_p2p/src/test_util/metric_store.rs index fa4f3d4e..c6ab2bc8 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/test_util/metric_store.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/test_util/metric_store.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! An in-memory implementation of a metric store. //! A real implementation would use a database. // NB: this is a copy of `KdMetricStore` from `kitsune_p2p_direct`, which diff --git a/crates/kitsune_p2p/kitsune_p2p/src/types.rs b/crates/kitsune_p2p/kitsune_p2p/src/types.rs index ef9ec76e..5445474b 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/types.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::sync::Arc; /// KitsuneP2p Error Type. diff --git a/crates/kitsune_p2p/kitsune_p2p/src/types/actor.rs b/crates/kitsune_p2p/kitsune_p2p/src/types/actor.rs index 0292eac5..31891d87 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/types/actor.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/types/actor.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Definitions related to the KitsuneP2p peer-to-peer / sgd communications actor. use std::sync::Arc; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/types/event.rs b/crates/kitsune_p2p/kitsune_p2p/src/types/event.rs index 0e151ce5..60129a65 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/types/event.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/types/event.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Definitions for events emited from the KitsuneP2p actor. use crate::types::agent_store::AgentInfoSigned; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/types/gossip.rs b/crates/kitsune_p2p/kitsune_p2p/src/types/gossip.rs index ee1686f8..9ab2b7df 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/types/gossip.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/types/gossip.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::types::*; use kitsune_p2p_types::config::*; use kitsune_p2p_types::tx2::tx2_api::*; diff --git a/crates/kitsune_p2p/kitsune_p2p/src/types/metrics.rs b/crates/kitsune_p2p/kitsune_p2p/src/types/metrics.rs index 35b49e4b..934014f3 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/types/metrics.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/types/metrics.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ghost_actor::dependencies::tracing; observability::metrics!( diff --git a/crates/kitsune_p2p/kitsune_p2p/src/types/wire.rs b/crates/kitsune_p2p/kitsune_p2p/src/types/wire.rs index 2727fb35..110eb3c1 100644 --- a/crates/kitsune_p2p/kitsune_p2p/src/types/wire.rs +++ b/crates/kitsune_p2p/kitsune_p2p/src/types/wire.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! KitsuneP2p Wire Protocol Encoding Decoding use crate::types::*; diff --git a/crates/kitsune_p2p/mdns/Cargo.toml b/crates/kitsune_p2p/mdns/Cargo.toml index 15dde2e0..91169802 100644 --- a/crates/kitsune_p2p/mdns/Cargo.toml +++ b/crates/kitsune_p2p/mdns/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_mdns" version = "0.0.1" description = "p2p / mdns discovery framework" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_mdns" @@ -25,7 +25,7 @@ mdns = "=3.0.0" futures-util = "0.3.1" futures-core = "0.3.1" async-stream = "0.3.6" -base64 = "0.21" +base64 = "0.22" thiserror = "2.0" tokio = { version = "1", features = [ "full" ] } tokio-stream = { version = "0.1" } diff --git a/crates/kitsune_p2p/mdns/examples/broadcast.rs b/crates/kitsune_p2p/mdns/examples/broadcast.rs index 73e7c03e..a88700e2 100644 --- a/crates/kitsune_p2p/mdns/examples/broadcast.rs +++ b/crates/kitsune_p2p/mdns/examples/broadcast.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use kitsune_p2p_mdns::*; #[tokio::main(flavor = "multi_thread")] diff --git a/crates/kitsune_p2p/mdns/examples/discover.rs b/crates/kitsune_p2p/mdns/examples/discover.rs index 6d299b58..def3488f 100644 --- a/crates/kitsune_p2p/mdns/examples/discover.rs +++ b/crates/kitsune_p2p/mdns/examples/discover.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures_util::{self, pin_mut, stream::StreamExt}; use kitsune_p2p_mdns::*; diff --git a/crates/kitsune_p2p/mdns/src/lib.rs b/crates/kitsune_p2p/mdns/src/lib.rs index f7fb0a96..b3845fa8 100644 --- a/crates/kitsune_p2p/mdns/src/lib.rs +++ b/crates/kitsune_p2p/mdns/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Crate for discovering AIngle peers over MDNS //! Works by broadcasting a service named `AI_SERVICE_NAME` //! and adding base64 encoded data in a TXT record diff --git a/crates/kitsune_p2p/proxy/Cargo.toml b/crates/kitsune_p2p/proxy/Cargo.toml index c52dbe4d..0498da99 100644 --- a/crates/kitsune_p2p/proxy/Cargo.toml +++ b/crates/kitsune_p2p/proxy/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_proxy" version = "0.0.1" description = "Proxy transport module for kitsune-p2p" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_proxy" @@ -16,7 +16,7 @@ edition = "2018" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test_utils"))'] } [dependencies] -base64 = "0.21" +base64 = "0.22" blake2b_simd = "0.5.10" derive_more = "0.99.7" futures = "0.3" @@ -26,7 +26,7 @@ lair_keystore_api = "0.0.11" nanoid = "0.4" observability = { version = "0.1", package = "aingle-observability" } parking_lot = "0.12" -rmp-serde = "0.15" +rmp-serde = "1" rustls = { workspace = true } rustls-pki-types = "1" serde = { version = "1", features = [ "derive" ] } diff --git a/crates/kitsune_p2p/proxy/benches/old_proxy_thru.rs b/crates/kitsune_p2p/proxy/benches/old_proxy_thru.rs index 6765fc77..834a2838 100644 --- a/crates/kitsune_p2p/proxy/benches/old_proxy_thru.rs +++ b/crates/kitsune_p2p/proxy/benches/old_proxy_thru.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use criterion::{/*black_box,*/ criterion_group, criterion_main, Criterion}; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/proxy/benches/thru.rs b/crates/kitsune_p2p/proxy/benches/thru.rs index 8b2b6be7..fdb0c0ca 100644 --- a/crates/kitsune_p2p/proxy/benches/thru.rs +++ b/crates/kitsune_p2p/proxy/benches/thru.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use criterion::{/*black_box,*/ criterion_group, criterion_main, Criterion}; use futures::stream::StreamExt; use kitsune_p2p_proxy::tx2::*; diff --git a/crates/kitsune_p2p/proxy/examples/cli-chat.rs b/crates/kitsune_p2p/proxy/examples/cli-chat.rs index ec849bc0..805f4416 100644 --- a/crates/kitsune_p2p/proxy/examples/cli-chat.rs +++ b/crates/kitsune_p2p/proxy/examples/cli-chat.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crossterm::ExecutableCommand; use futures::stream::{BoxStream, StreamExt}; use kitsune_p2p_proxy::tx2::*; diff --git a/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/main.rs b/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/main.rs index 11484d20..5a336ea6 100644 --- a/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/main.rs +++ b/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/main.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use ghost_actor::dependencies::tracing; use kitsune_p2p_proxy::*; diff --git a/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/opt.rs b/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/opt.rs index a996e952..47e8c26b 100644 --- a/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/opt.rs +++ b/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-proxy/opt.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// Option Parsing #[derive(structopt::StructOpt, Debug)] #[structopt(name = "kitsune-p2p-proxy")] diff --git a/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-tx2-proxy.rs b/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-tx2-proxy.rs index 0c0609c4..762342db 100644 --- a/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-tx2-proxy.rs +++ b/crates/kitsune_p2p/proxy/src/bin/kitsune-p2p-tx2-proxy.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use kitsune_p2p_proxy::tx2::*; use kitsune_p2p_transport_quic::tx2::*; diff --git a/crates/kitsune_p2p/proxy/src/bin/proxy-cli.rs b/crates/kitsune_p2p/proxy/src/bin/proxy-cli.rs index 33467fd7..c4b7bb7a 100644 --- a/crates/kitsune_p2p/proxy/src/bin/proxy-cli.rs +++ b/crates/kitsune_p2p/proxy/src/bin/proxy-cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use kitsune_p2p_proxy::*; use kitsune_p2p_transport_quic::*; diff --git a/crates/kitsune_p2p/proxy/src/bin/proxy-stress.rs b/crates/kitsune_p2p/proxy/src/bin/proxy-stress.rs index 47f4b1f2..d76c0afc 100644 --- a/crates/kitsune_p2p/proxy/src/bin/proxy-stress.rs +++ b/crates/kitsune_p2p/proxy/src/bin/proxy-stress.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::{sink::SinkExt, stream::StreamExt}; use ghost_actor::dependencies::tracing; use kitsune_p2p_proxy::*; diff --git a/crates/kitsune_p2p/proxy/src/bin/proxy-tx2-cli.rs b/crates/kitsune_p2p/proxy/src/bin/proxy-tx2-cli.rs index 173611a8..43de2561 100644 --- a/crates/kitsune_p2p/proxy/src/bin/proxy-tx2-cli.rs +++ b/crates/kitsune_p2p/proxy/src/bin/proxy-tx2-cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use kitsune_p2p_proxy::tx2::*; use kitsune_p2p_transport_quic::tx2::*; diff --git a/crates/kitsune_p2p/proxy/src/config.rs b/crates/kitsune_p2p/proxy/src/config.rs index 9cb02c87..da4deea0 100644 --- a/crates/kitsune_p2p/proxy/src/config.rs +++ b/crates/kitsune_p2p/proxy/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; pub use kitsune_p2p_types::tls::TlsConfig; diff --git a/crates/kitsune_p2p/proxy/src/inner_listen.rs b/crates/kitsune_p2p/proxy/src/inner_listen.rs index 4cd650df..b7715ef8 100644 --- a/crates/kitsune_p2p/proxy/src/inner_listen.rs +++ b/crates/kitsune_p2p/proxy/src/inner_listen.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::sink::SinkExt; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/proxy/src/lib.rs b/crates/kitsune_p2p/proxy/src/lib.rs index f044f513..608ae3c9 100644 --- a/crates/kitsune_p2p/proxy/src/lib.rs +++ b/crates/kitsune_p2p/proxy/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! Proxy transport module for kitsune-p2p diff --git a/crates/kitsune_p2p/proxy/src/proxy_url.rs b/crates/kitsune_p2p/proxy/src/proxy_url.rs index 0b636798..9e11b9b6 100644 --- a/crates/kitsune_p2p/proxy/src/proxy_url.rs +++ b/crates/kitsune_p2p/proxy/src/proxy_url.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Utilities for dealing with proxy urls. use crate::*; diff --git a/crates/kitsune_p2p/proxy/src/tls_cli.rs b/crates/kitsune_p2p/proxy/src/tls_cli.rs index efc1af4e..dfee848b 100644 --- a/crates/kitsune_p2p/proxy/src/tls_cli.rs +++ b/crates/kitsune_p2p/proxy/src/tls_cli.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::sink::SinkExt; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/proxy/src/tls_srv.rs b/crates/kitsune_p2p/proxy/src/tls_srv.rs index b935b6c8..c10a9ab3 100644 --- a/crates/kitsune_p2p/proxy/src/tls_srv.rs +++ b/crates/kitsune_p2p/proxy/src/tls_srv.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::sink::SinkExt; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/proxy/src/tls_tests.rs b/crates/kitsune_p2p/proxy/src/tls_tests.rs index 5dcd74dd..a9595d48 100644 --- a/crates/kitsune_p2p/proxy/src/tls_tests.rs +++ b/crates/kitsune_p2p/proxy/src/tls_tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::stream::StreamExt; use ghost_actor::dependencies::tracing; diff --git a/crates/kitsune_p2p/proxy/src/tx2.rs b/crates/kitsune_p2p/proxy/src/tx2.rs index 4fda7ffb..ebc3f7ed 100644 --- a/crates/kitsune_p2p/proxy/src/tx2.rs +++ b/crates/kitsune_p2p/proxy/src/tx2.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::new_ret_no_self)] #![allow(clippy::blocks_in_conditions)] //! Next-gen performance kitsune transport proxy diff --git a/crates/kitsune_p2p/proxy/src/wire.rs b/crates/kitsune_p2p/proxy/src/wire.rs index 8a2dff82..a8faa01a 100644 --- a/crates/kitsune_p2p/proxy/src/wire.rs +++ b/crates/kitsune_p2p/proxy/src/wire.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! KitsuneP2p Proxy Wire Protocol Items. use crate::*; diff --git a/crates/kitsune_p2p/proxy/src/wire_read.rs b/crates/kitsune_p2p/proxy/src/wire_read.rs index 0cb74c13..a8ea078d 100644 --- a/crates/kitsune_p2p/proxy/src/wire_read.rs +++ b/crates/kitsune_p2p/proxy/src/wire_read.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::sink::SinkExt; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/proxy/src/wire_write.rs b/crates/kitsune_p2p/proxy/src/wire_write.rs index 2ab42318..a5ce2708 100644 --- a/crates/kitsune_p2p/proxy/src/wire_write.rs +++ b/crates/kitsune_p2p/proxy/src/wire_write.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::sink::SinkExt; use futures::stream::StreamExt; diff --git a/crates/kitsune_p2p/proxy/tests/no_proxy.rs b/crates/kitsune_p2p/proxy/tests/no_proxy.rs index 1723ab2f..1841eee5 100644 --- a/crates/kitsune_p2p/proxy/tests/no_proxy.rs +++ b/crates/kitsune_p2p/proxy/tests/no_proxy.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use ghost_actor::dependencies::tracing; use kitsune_p2p_proxy::*; use kitsune_p2p_types::config::KitsuneP2pTuningParams; diff --git a/crates/kitsune_p2p/proxy/tests/proxy_integration.rs b/crates/kitsune_p2p/proxy/tests/proxy_integration.rs index fb3dd1fe..0cac408f 100644 --- a/crates/kitsune_p2p/proxy/tests/proxy_integration.rs +++ b/crates/kitsune_p2p/proxy/tests/proxy_integration.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use ghost_actor::dependencies::tracing; use kitsune_p2p_proxy::*; diff --git a/crates/kitsune_p2p/proxy/tests/srv_cli_dump_test.rs b/crates/kitsune_p2p/proxy/tests/srv_cli_dump_test.rs index 64722671..08dadf03 100644 --- a/crates/kitsune_p2p/proxy/tests/srv_cli_dump_test.rs +++ b/crates/kitsune_p2p/proxy/tests/srv_cli_dump_test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::io::Read; fn run_srv() -> (String, std::process::Child) { diff --git a/crates/kitsune_p2p/proxy/tests/tx2_con_up_down.rs b/crates/kitsune_p2p/proxy/tests/tx2_con_up_down.rs index 23f35c91..fb1b0c1d 100644 --- a/crates/kitsune_p2p/proxy/tests/tx2_con_up_down.rs +++ b/crates/kitsune_p2p/proxy/tests/tx2_con_up_down.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use futures::stream::StreamExt; use kitsune_p2p_proxy::tx2::*; use kitsune_p2p_proxy::ProxyUrl; diff --git a/crates/kitsune_p2p/transport_quic/Cargo.toml b/crates/kitsune_p2p/transport_quic/Cargo.toml index afe7c6ff..74ea4d9b 100644 --- a/crates/kitsune_p2p/transport_quic/Cargo.toml +++ b/crates/kitsune_p2p/transport_quic/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_transport_quic" version = "0.0.1" description = "QUIC transport module for kitsune-p2p" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_transport_quic" @@ -17,7 +17,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test_util [dependencies] blake2b_simd = "0.5.10" futures = "0.3" -if-addrs = "0.6" +if-addrs = "0.13" kitsune_p2p_types = { version = "0.0.1", path = "../types" } lair_keystore_api = "0.0.11" nanoid = "0.4" diff --git a/crates/kitsune_p2p/transport_quic/src/config.rs b/crates/kitsune_p2p/transport_quic/src/config.rs index db4d1d29..b181ec64 100644 --- a/crates/kitsune_p2p/transport_quic/src/config.rs +++ b/crates/kitsune_p2p/transport_quic/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; /// Configuration struct for spawn_transport_listener_quic() diff --git a/crates/kitsune_p2p/transport_quic/src/lib.rs b/crates/kitsune_p2p/transport_quic/src/lib.rs index d1f2b7e4..9172114c 100644 --- a/crates/kitsune_p2p/transport_quic/src/lib.rs +++ b/crates/kitsune_p2p/transport_quic/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! QUIC transport module for kitsune-p2p diff --git a/crates/kitsune_p2p/transport_quic/src/listener.rs b/crates/kitsune_p2p/transport_quic/src/listener.rs index 5002f684..f2291973 100644 --- a/crates/kitsune_p2p/transport_quic/src/listener.rs +++ b/crates/kitsune_p2p/transport_quic/src/listener.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; use futures::future::FutureExt; use futures::sink::SinkExt; diff --git a/crates/kitsune_p2p/transport_quic/src/test.rs b/crates/kitsune_p2p/transport_quic/src/test.rs index 137447b5..3b1334fe 100644 --- a/crates/kitsune_p2p/transport_quic/src/test.rs +++ b/crates/kitsune_p2p/transport_quic/src/test.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #[cfg(test)] mod tests { use crate::*; diff --git a/crates/kitsune_p2p/transport_quic/src/tx2.rs b/crates/kitsune_p2p/transport_quic/src/tx2.rs index b01d1a33..87ae3250 100644 --- a/crates/kitsune_p2p/transport_quic/src/tx2.rs +++ b/crates/kitsune_p2p/transport_quic/src/tx2.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::new_ret_no_self)] //! kitsune tx2 quic transport backend diff --git a/crates/kitsune_p2p/types/Cargo.toml b/crates/kitsune_p2p/types/Cargo.toml index da9b3362..e733a5e9 100644 --- a/crates/kitsune_p2p/types/Cargo.toml +++ b/crates/kitsune_p2p/types/Cargo.toml @@ -2,7 +2,7 @@ name = "kitsune_p2p_types" version = "0.0.1" description = "types subcrate for kitsune-p2p" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/kitsune_p2p_types" @@ -15,7 +15,7 @@ edition = "2018" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("test_utils"))'] } [dependencies] -base64 = "0.21" +base64 = "0.22" derive_more = "0.99.7" futures = "0.3" ghost_actor = { version = "0.3.0-alpha.1", features = ["test_utils"] } @@ -27,14 +27,14 @@ observability = { version = "0.1", package = "aingle-observability" } once_cell = "1.4" parking_lot = "0.12" paste = "1.0" -rmp-serde = "0.15" +rmp-serde = "1" rustls = { workspace = true } rustls-pki-types = "1" serde = { version = "1", features = [ "derive", "rc" ] } serde_bytes = "0.11" serde_json = { version = "1", features = [ "preserve_order" ] } shrinkwraprs = "0.3.0" -sysinfo = "0.37" +sysinfo = "0.38" thiserror = "2.0" tokio = { version = "1", features = [ "full" ] } tokio-stream = { version = "0.1", features = [ "sync", "net" ] } diff --git a/crates/kitsune_p2p/types/benches/api_thru.rs b/crates/kitsune_p2p/types/benches/api_thru.rs index 45d89f47..c2297318 100644 --- a/crates/kitsune_p2p/types/benches/api_thru.rs +++ b/crates/kitsune_p2p/types/benches/api_thru.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(irrefutable_let_patterns)] use criterion::{/*black_box,*/ criterion_group, criterion_main, Criterion}; diff --git a/crates/kitsune_p2p/types/examples/codec.rs b/crates/kitsune_p2p/types/examples/codec.rs index 918eea31..fa2d1558 100644 --- a/crates/kitsune_p2p/types/examples/codec.rs +++ b/crates/kitsune_p2p/types/examples/codec.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use kitsune_p2p_types::codec::*; use kitsune_p2p_types::*; diff --git a/crates/kitsune_p2p/types/src/agent_info.rs b/crates/kitsune_p2p/types/src/agent_info.rs index 297478cd..78c0ec5f 100644 --- a/crates/kitsune_p2p/types/src/agent_info.rs +++ b/crates/kitsune_p2p/types/src/agent_info.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Data structures to be stored in the agent/peer database. use crate::bin_types::*; diff --git a/crates/kitsune_p2p/types/src/async_lazy.rs b/crates/kitsune_p2p/types/src/async_lazy.rs index 1f466d3c..e8a4cebc 100644 --- a/crates/kitsune_p2p/types/src/async_lazy.rs +++ b/crates/kitsune_p2p/types/src/async_lazy.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! utility for lazy init-ing things use futures::future::{BoxFuture, FutureExt, Shared}; diff --git a/crates/kitsune_p2p/types/src/auto_stream_select.rs b/crates/kitsune_p2p/types/src/auto_stream_select.rs index 4f234673..e147751c 100644 --- a/crates/kitsune_p2p/types/src/auto_stream_select.rs +++ b/crates/kitsune_p2p/types/src/auto_stream_select.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::task::Poll::*; /// Item Type for auto_stream_select function. diff --git a/crates/kitsune_p2p/types/src/bin_types.rs b/crates/kitsune_p2p/types/src/bin_types.rs index 87cc9f2d..b1a30b43 100644 --- a/crates/kitsune_p2p/types/src/bin_types.rs +++ b/crates/kitsune_p2p/types/src/bin_types.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Binary types, hashes, signatures, etc used by kitsune. use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; diff --git a/crates/kitsune_p2p/types/src/bootstrap.rs b/crates/kitsune_p2p/types/src/bootstrap.rs index 2700368e..d472fc92 100644 --- a/crates/kitsune_p2p/types/src/bootstrap.rs +++ b/crates/kitsune_p2p/types/src/bootstrap.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types for the bootstrap server use crate::bin_types::{KitsuneBinType, KitsuneSpace}; use std::sync::Arc; diff --git a/crates/kitsune_p2p/types/src/codec.rs b/crates/kitsune_p2p/types/src/codec.rs index e2283574..c3d6c2db 100644 --- a/crates/kitsune_p2p/types/src/codec.rs +++ b/crates/kitsune_p2p/types/src/codec.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Encoding / Decoding utilities. /// Encode a serde::Serialize item as message-pack data to given writer. @@ -8,8 +11,7 @@ where S: serde::Serialize, { let mut se = rmp_serde::encode::Serializer::new(write) - .with_struct_map() - .with_string_variants(); + .with_struct_map(); item.serialize(&mut se).map_err(std::io::Error::other)?; Ok(()) } diff --git a/crates/kitsune_p2p/types/src/config.rs b/crates/kitsune_p2p/types/src/config.rs index 2762fd03..f238a6a0 100644 --- a/crates/kitsune_p2p/types/src/config.rs +++ b/crates/kitsune_p2p/types/src/config.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Kitsune Config Tuning Params /// How long kitsune should wait before timing out when joining the network. diff --git a/crates/kitsune_p2p/types/src/lib.rs b/crates/kitsune_p2p/types/src/lib.rs index e1ca8520..c81620a7 100644 --- a/crates/kitsune_p2p/types/src/lib.rs +++ b/crates/kitsune_p2p/types/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![deny(missing_docs)] //! Types subcrate for kitsune-p2p. diff --git a/crates/kitsune_p2p/types/src/metrics.rs b/crates/kitsune_p2p/types/src/metrics.rs index 4663db5b..2dc1ca2b 100644 --- a/crates/kitsune_p2p/types/src/metrics.rs +++ b/crates/kitsune_p2p/types/src/metrics.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Utilities for helping with metric tracking. use crate::tracing; diff --git a/crates/kitsune_p2p/types/src/sgd_arc.rs b/crates/kitsune_p2p/types/src/sgd_arc.rs index fcde87f1..68dcfab9 100644 --- a/crates/kitsune_p2p/types/src/sgd_arc.rs +++ b/crates/kitsune_p2p/types/src/sgd_arc.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A type for indicating ranges on the sgd arc use derive_more::From; diff --git a/crates/kitsune_p2p/types/src/sgd_arc/gaps.rs b/crates/kitsune_p2p/types/src/sgd_arc/gaps.rs index 9c2651fb..d0c07c9b 100644 --- a/crates/kitsune_p2p/types/src/sgd_arc/gaps.rs +++ b/crates/kitsune_p2p/types/src/sgd_arc/gaps.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Functions for checking gaps in coverage for tests. use super::*; diff --git a/crates/kitsune_p2p/types/src/sgd_arc/tests.rs b/crates/kitsune_p2p/types/src/sgd_arc/tests.rs index 32fd2d93..941bbb32 100644 --- a/crates/kitsune_p2p/types/src/sgd_arc/tests.rs +++ b/crates/kitsune_p2p/types/src/sgd_arc/tests.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use gaps::check_for_gaps; use gaps::check_redundancy; diff --git a/crates/kitsune_p2p/types/src/timeout.rs b/crates/kitsune_p2p/types/src/timeout.rs index 800a5553..2f8b5071 100644 --- a/crates/kitsune_p2p/types/src/timeout.rs +++ b/crates/kitsune_p2p/types/src/timeout.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; /// Kitsune Timeout diff --git a/crates/kitsune_p2p/types/src/tls.rs b/crates/kitsune_p2p/types/src/tls.rs index b48f7357..47ca2619 100644 --- a/crates/kitsune_p2p/types/src/tls.rs +++ b/crates/kitsune_p2p/types/src/tls.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! TLS utils for kitsune use crate::config::*; diff --git a/crates/kitsune_p2p/types/src/transport.rs b/crates/kitsune_p2p/types/src/transport.rs index 66a8a24c..ce95a9df 100644 --- a/crates/kitsune_p2p/types/src/transport.rs +++ b/crates/kitsune_p2p/types/src/transport.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A collection of definitions related to remote communication. use futures::future::FutureExt; diff --git a/crates/kitsune_p2p/types/src/transport_mem.rs b/crates/kitsune_p2p/types/src/transport_mem.rs index 9b50c057..0a326c0f 100644 --- a/crates/kitsune_p2p/types/src/transport_mem.rs +++ b/crates/kitsune_p2p/types/src/transport_mem.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! A mem-only transport - largely for testing use crate::transport::*; diff --git a/crates/kitsune_p2p/types/src/transport_pool.rs b/crates/kitsune_p2p/types/src/transport_pool.rs index 65fbd9bf..67f76388 100644 --- a/crates/kitsune_p2p/types/src/transport_pool.rs +++ b/crates/kitsune_p2p/types/src/transport_pool.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Unify multiple sub-transports into one pool. use crate::transport::*; diff --git a/crates/kitsune_p2p/types/src/tx2.rs b/crates/kitsune_p2p/types/src/tx2.rs index 05086ff9..e2bce1fc 100644 --- a/crates/kitsune_p2p/types/src/tx2.rs +++ b/crates/kitsune_p2p/types/src/tx2.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Next-gen performance kitsune transport abstractions mod framed; diff --git a/crates/kitsune_p2p/types/src/tx2/framed.rs b/crates/kitsune_p2p/types/src/tx2/framed.rs index f19d4cf5..b6ebfdc9 100644 --- a/crates/kitsune_p2p/types/src/tx2/framed.rs +++ b/crates/kitsune_p2p/types/src/tx2/framed.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::tx2::tx2_utils::*; use crate::*; use futures::future::{BoxFuture, FutureExt}; diff --git a/crates/kitsune_p2p/types/src/tx2/mem.rs b/crates/kitsune_p2p/types/src/tx2/mem.rs index 718d6343..3c63a4f2 100644 --- a/crates/kitsune_p2p/types/src/tx2/mem.rs +++ b/crates/kitsune_p2p/types/src/tx2/mem.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::new_ret_no_self)] #![allow(clippy::never_loop)] diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_adapter.rs b/crates/kitsune_p2p/types/src/tx2/tx2_adapter.rs index 823a24a2..65e41eb7 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_adapter.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_adapter.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Types and Traits for writing tx2 adapters. use crate::tx2::tx2_utils::TxUrl; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_api.rs b/crates/kitsune_p2p/types/src/tx2/tx2_api.rs index 77227831..e44c5eab 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_api.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_api.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(enum_intrinsics_non_enums)] // these actually *are* enums... //! Usability api for tx2 kitsune transports. diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_pool.rs b/crates/kitsune_p2p/types/src/tx2/tx2_pool.rs index 1be44730..b9f51097 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_pool.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_pool.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! Abstraction traits / types for tx2 networking transport. use crate::tx2::tx2_adapter::{Tx2ConDir, Uniq}; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_pool_promote.rs b/crates/kitsune_p2p/types/src/tx2/tx2_pool_promote.rs index 9971b14b..2e525c2d 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_pool_promote.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_pool_promote.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::new_ret_no_self)] #![allow(clippy::manual_async_fn)] #![allow(clippy::too_many_arguments)] diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils.rs index baa8166b..20db8e3b 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::never_loop)] // using for block breaking //! Utilities to help with developing / testing tx2. diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/active.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/active.rs index 3010d94d..a1c9c862 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/active.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/active.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::tx2::tx2_utils::*; use crate::*; use futures::future::FutureExt; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/latency.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/latency.rs index 2c577dc3..3e612be2 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/latency.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/latency.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use once_cell::sync::Lazy; /// this is a reference instance in time diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/logic_chan.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/logic_chan.rs index f4fb0dc3..fadef815 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/logic_chan.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/logic_chan.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::tx2::tx2_utils::*; use crate::*; use futures::future::{BoxFuture, FutureExt}; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/mem_chan.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/mem_chan.rs index c3a39dec..e4da6c57 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/mem_chan.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/mem_chan.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::tx2::tx2_utils::*; use crate::*; use futures::io::{Error, ErrorKind}; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/notify_all.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/notify_all.rs index 4aec97c3..1b1bdfbc 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/notify_all.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/notify_all.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(clippy::blocks_in_conditions)] use crate::tx2::tx2_utils::*; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/pool_buf.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/pool_buf.rs index 7731cce0..7c8546d7 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/pool_buf.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/pool_buf.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::cell::RefCell; // TODO - expirement with these values for efficiency. diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/resource_bucket.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/resource_bucket.rs index fe9aea13..a382df28 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/resource_bucket.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/resource_bucket.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::tx2::tx2_utils::*; use crate::*; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/share.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/share.rs index f2fbefdf..baa29069 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/share.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/share.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::*; /// Synchronized droppable share-lock around internal state date. diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/t_chan.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/t_chan.rs index 133186b4..8f5480df 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/t_chan.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/t_chan.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::tx2::tx2_utils::*; use crate::*; use std::future::Future; diff --git a/crates/kitsune_p2p/types/src/tx2/tx2_utils/tx_url.rs b/crates/kitsune_p2p/types/src/tx2/tx2_utils/tx_url.rs index c8b7a0e1..7b0c8f6b 100644 --- a/crates/kitsune_p2p/types/src/tx2/tx2_utils/tx_url.rs +++ b/crates/kitsune_p2p/types/src/tx2/tx2_utils/tx_url.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::sync::Arc; /// New-type for sync ref-counted Urls diff --git a/crates/mr_bundle/Cargo.toml b/crates/mr_bundle/Cargo.toml index 5aad2c46..0bc39c0b 100644 --- a/crates/mr_bundle/Cargo.toml +++ b/crates/mr_bundle/Cargo.toml @@ -4,20 +4,20 @@ version = "0.0.1" authors = ["Apilium Technologies "] edition = "2018" description = "Implements the un-/packing of bundles that either embed or reference a set of resources" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" homepage = "https://apilium.com" repository = "https://github.com/ApiliumCode/aingle" documentation = "https://docs.rs/mr_bundle" [dependencies] -bytes = "1.0" +bytes = "1.11" derive_more = "0.99" either = "1.5" -flate2 = "1.0" +flate2 = "1.1" aingle_util = { path = "../aingle_util", version = "0.0.1" } futures = "0.3" reqwest = "0.12" -rmp-serde = "0.15" +rmp-serde = "1" serde = { version = "1.0", features = [ "serde_derive", "derive" ] } serde_bytes = "0.11" serde_derive = "1.0" diff --git a/crates/mr_bundle/src/bundle.rs b/crates/mr_bundle/src/bundle.rs index e3b48354..2ed8eae8 100644 --- a/crates/mr_bundle/src/bundle.rs +++ b/crates/mr_bundle/src/bundle.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::{ error::{BundleError, MrBundleResult}, location::Location, diff --git a/crates/mr_bundle/src/encoding.rs b/crates/mr_bundle/src/encoding.rs index c87c3b75..58411cbc 100644 --- a/crates/mr_bundle/src/encoding.rs +++ b/crates/mr_bundle/src/encoding.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::error::MrBundleResult; use std::io::Read; use std::io::Write; diff --git a/crates/mr_bundle/src/error.rs b/crates/mr_bundle/src/error.rs index 987acaad..19c80772 100644 --- a/crates/mr_bundle/src/error.rs +++ b/crates/mr_bundle/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(missing_docs)] use aingle_util::ffs::IoError; diff --git a/crates/mr_bundle/src/lib.rs b/crates/mr_bundle/src/lib.rs index 34c6649d..2fab4482 100644 --- a/crates/mr_bundle/src/lib.rs +++ b/crates/mr_bundle/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + #![allow(rustdoc::bare_urls)] #![allow(rustdoc::invalid_html_tags)] #![allow(rustdoc::redundant_explicit_links)] diff --git a/crates/mr_bundle/src/location.rs b/crates/mr_bundle/src/location.rs index c737c63f..d0ca233c 100644 --- a/crates/mr_bundle/src/location.rs +++ b/crates/mr_bundle/src/location.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use crate::{ error::{BundleError, MrBundleResult}, ResourceBytes, diff --git a/crates/mr_bundle/src/manifest.rs b/crates/mr_bundle/src/manifest.rs index f8c9a19d..1e4865a4 100644 --- a/crates/mr_bundle/src/manifest.rs +++ b/crates/mr_bundle/src/manifest.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::PathBuf; use crate::location::Location; diff --git a/crates/mr_bundle/src/packing.rs b/crates/mr_bundle/src/packing.rs index 08a2429a..62a85911 100644 --- a/crates/mr_bundle/src/packing.rs +++ b/crates/mr_bundle/src/packing.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use super::Bundle; use crate::{ error::{MrBundleResult, PackingError, UnpackingError, UnpackingResult}, diff --git a/crates/mr_bundle/src/resource.rs b/crates/mr_bundle/src/resource.rs index bd9185de..fa25bc49 100644 --- a/crates/mr_bundle/src/resource.rs +++ b/crates/mr_bundle/src/resource.rs @@ -1,2 +1,5 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + /// Arbitrary opaque bytes representing a Resource in a [`Bundle`](crate::Bundle) pub type ResourceBytes = Vec; diff --git a/crates/mr_bundle/src/util.rs b/crates/mr_bundle/src/util.rs index f2b228fe..8ca4f6b6 100644 --- a/crates/mr_bundle/src/util.rs +++ b/crates/mr_bundle/src/util.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::path::{Path, PathBuf}; #[cfg(feature = "packing")] diff --git a/crates/mr_bundle/tests/integration.rs b/crates/mr_bundle/tests/integration.rs index b826b93d..d42e4f6c 100644 --- a/crates/mr_bundle/tests/integration.rs +++ b/crates/mr_bundle/tests/integration.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use mr_bundle::{Bundle, Location, Manifest}; use std::{collections::HashSet, path::PathBuf}; diff --git a/crates/test_utils/wasm/Cargo.toml b/crates/test_utils/wasm/Cargo.toml index b0b4e046..1a82f137 100644 --- a/crates/test_utils/wasm/Cargo.toml +++ b/crates/test_utils/wasm/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_wasm_test_utils" version = "0.0.1" description = "WASM test utilities for AIngle development" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" authors = ["Apilium Technologies "] keywords = ["aingle", "wasm", "testing", "utilities"] @@ -31,5 +31,5 @@ strum_macros = "0.26" aingle_util = { version = "0.0.1", path = "../../aingle_util" } [build-dependencies] -toml = "0.5" +toml = "0.9" walkdir = "2.3.1" diff --git a/crates/test_utils/wasm/build.rs b/crates/test_utils/wasm/build.rs index a560bec3..4b8687e5 100644 --- a/crates/test_utils/wasm/build.rs +++ b/crates/test_utils/wasm/build.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use std::io::Write; use std::process::Stdio; diff --git a/crates/test_utils/wasm/src/lib.rs b/crates/test_utils/wasm/src/lib.rs index 4026c931..2268171e 100644 --- a/crates/test_utils/wasm/src/lib.rs +++ b/crates/test_utils/wasm/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use aingle_types::prelude::*; use strum_macros::EnumIter; diff --git a/crates/test_utils/wasm/wasm_workspace/adk_extern/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/adk_extern/src/lib.rs index 1b0f6dcd..c8bdda4b 100644 --- a/crates/test_utils/wasm/wasm_workspace/adk_extern/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/adk_extern/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/agent_info/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/agent_info/src/lib.rs index 47cf0c6f..aede4161 100644 --- a/crates/test_utils/wasm/wasm_workspace/agent_info/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/agent_info/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/anchor/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/anchor/src/lib.rs index 6b34d6af..5089a34f 100644 --- a/crates/test_utils/wasm/wasm_workspace/anchor/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/anchor/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; use aingle_test_wasm_common::*; diff --git a/crates/test_utils/wasm/wasm_workspace/bench/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/bench/src/lib.rs index 3516479d..7dd19989 100644 --- a/crates/test_utils/wasm/wasm_workspace/bench/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/bench/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + //! externs to help bench the wasm ribosome use adk::prelude::*; diff --git a/crates/test_utils/wasm/wasm_workspace/capability/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/capability/src/lib.rs index 97e02e1f..6e3d6bdb 100644 --- a/crates/test_utils/wasm/wasm_workspace/capability/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/capability/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[derive(serde::Serialize, serde::Deserialize, Debug)] diff --git a/crates/test_utils/wasm/wasm_workspace/crd/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/crd/src/lib.rs index 4638017a..abea129f 100644 --- a/crates/test_utils/wasm/wasm_workspace/crd/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/crd/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry(id = "thing")] diff --git a/crates/test_utils/wasm/wasm_workspace/create_entry/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/create_entry/src/lib.rs index 8f7ae167..843ca8b0 100644 --- a/crates/test_utils/wasm/wasm_workspace/create_entry/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/create_entry/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry( diff --git a/crates/test_utils/wasm/wasm_workspace/crud/src/countree.rs b/crates/test_utils/wasm/wasm_workspace/crud/src/countree.rs index 259410d7..2070b0f9 100644 --- a/crates/test_utils/wasm/wasm_workspace/crud/src/countree.rs +++ b/crates/test_utils/wasm/wasm_workspace/crud/src/countree.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry(id = "countree")] diff --git a/crates/test_utils/wasm/wasm_workspace/crud/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/crud/src/lib.rs index 4397e45c..7c0ae921 100644 --- a/crates/test_utils/wasm/wasm_workspace/crud/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/crud/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; mod countree; diff --git a/crates/test_utils/wasm/wasm_workspace/debug/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/debug/src/lib.rs index a42f059b..ecfcde95 100644 --- a/crates/test_utils/wasm/wasm_workspace/debug/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/debug/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/emit_signal/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/emit_signal/src/lib.rs index 41278ebc..130927c7 100644 --- a/crates/test_utils/wasm/wasm_workspace/emit_signal/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/emit_signal/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/entry_defs/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/entry_defs/src/lib.rs index e8ce5189..94e541b0 100644 --- a/crates/test_utils/wasm/wasm_workspace/entry_defs/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/entry_defs/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry(id = "post")] diff --git a/crates/test_utils/wasm/wasm_workspace/foo/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/foo/src/lib.rs index 2080a46f..4e6b2b87 100644 --- a/crates/test_utils/wasm/wasm_workspace/foo/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/foo/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/genesis_self_check_invalid/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/genesis_self_check_invalid/src/lib.rs index ccb2856e..c65f1c51 100644 --- a/crates/test_utils/wasm/wasm_workspace/genesis_self_check_invalid/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/genesis_self_check_invalid/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/genesis_self_check_valid/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/genesis_self_check_valid/src/lib.rs index df9cd691..35b9542b 100644 --- a/crates/test_utils/wasm/wasm_workspace/genesis_self_check_valid/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/genesis_self_check_valid/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/hash_entry/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/hash_entry/src/lib.rs index 36fda563..5e7a33f5 100644 --- a/crates/test_utils/wasm/wasm_workspace/hash_entry/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/hash_entry/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[derive(Serialize, Deserialize, Debug)] diff --git a/crates/test_utils/wasm/wasm_workspace/hash_path/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/hash_path/src/lib.rs index 8d77361a..3f6d8736 100644 --- a/crates/test_utils/wasm/wasm_workspace/hash_path/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/hash_path/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; entry_defs![Path::entry_def()]; diff --git a/crates/test_utils/wasm/wasm_workspace/init_fail/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/init_fail/src/lib.rs index 0012459a..e19948b6 100644 --- a/crates/test_utils/wasm/wasm_workspace/init_fail/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/init_fail/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/init_pass/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/init_pass/src/lib.rs index 9f0ecf9f..1bc88c7d 100644 --- a/crates/test_utils/wasm/wasm_workspace/init_pass/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/init_pass/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/link/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/link/src/lib.rs index 3eb9e3f6..61520a38 100644 --- a/crates/test_utils/wasm/wasm_workspace/link/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/link/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; entry_defs![Path::entry_def()]; diff --git a/crates/test_utils/wasm/wasm_workspace/migrate_agent_fail/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/migrate_agent_fail/src/lib.rs index 01537a82..69cb71eb 100644 --- a/crates/test_utils/wasm/wasm_workspace/migrate_agent_fail/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/migrate_agent_fail/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/migrate_agent_pass/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/migrate_agent_pass/src/lib.rs index 9d747f60..f273a189 100644 --- a/crates/test_utils/wasm/wasm_workspace/migrate_agent_pass/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/migrate_agent_pass/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/multiple_calls/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/multiple_calls/src/lib.rs index f189ec16..e454a47f 100644 --- a/crates/test_utils/wasm/wasm_workspace/multiple_calls/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/multiple_calls/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry(id = "post")] diff --git a/crates/test_utils/wasm/wasm_workspace/post_commit_fail/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/post_commit_fail/src/lib.rs index b8991302..5ae9d5ca 100644 --- a/crates/test_utils/wasm/wasm_workspace/post_commit_fail/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/post_commit_fail/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/post_commit_success/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/post_commit_success/src/lib.rs index 2c097c53..ce04fd7f 100644 --- a/crates/test_utils/wasm/wasm_workspace/post_commit_success/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/post_commit_success/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/query/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/query/src/lib.rs index bc5e0f73..b3a4be70 100644 --- a/crates/test_utils/wasm/wasm_workspace/query/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/query/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; entry_defs![Path::entry_def()]; diff --git a/crates/test_utils/wasm/wasm_workspace/random_bytes/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/random_bytes/src/lib.rs index 91448b93..b94555c8 100644 --- a/crates/test_utils/wasm/wasm_workspace/random_bytes/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/random_bytes/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/ser_regression/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/ser_regression/src/lib.rs index e81329e0..a74230a6 100644 --- a/crates/test_utils/wasm/wasm_workspace/ser_regression/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/ser_regression/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; use derive_more::*; diff --git a/crates/test_utils/wasm/wasm_workspace/sign/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/sign/src/lib.rs index b91b4918..2411504e 100644 --- a/crates/test_utils/wasm/wasm_workspace/sign/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/sign/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/sys_time/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/sys_time/src/lib.rs index c7d782e6..406732cf 100644 --- a/crates/test_utils/wasm/wasm_workspace/sys_time/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/sys_time/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/update_entry/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/update_entry/src/lib.rs index bb6914f4..3a22d808 100644 --- a/crates/test_utils/wasm/wasm_workspace/update_entry/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/update_entry/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry(id = "post", required_validations = 5)] diff --git a/crates/test_utils/wasm/wasm_workspace/validate/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validate/src/lib.rs index 0c9ab6f1..8ee82db7 100644 --- a/crates/test_utils/wasm/wasm_workspace/validate/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validate/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; /// an example inner value that can be serialized into the contents of Entry::App() diff --git a/crates/test_utils/wasm/wasm_workspace/validate_invalid/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validate_invalid/src/lib.rs index 5c31d52b..4edf012b 100644 --- a/crates/test_utils/wasm/wasm_workspace/validate_invalid/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validate_invalid/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/validate_link/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validate_link/src/lib.rs index 6d128b9c..b9fc8cb4 100644 --- a/crates/test_utils/wasm/wasm_workspace/validate_link/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validate_link/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_entry(id = "maybe_linkable")] diff --git a/crates/test_utils/wasm/wasm_workspace/validate_link_add_invalid/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validate_link_add_invalid/src/lib.rs index a97ac854..898d41a0 100644 --- a/crates/test_utils/wasm/wasm_workspace/validate_link_add_invalid/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validate_link_add_invalid/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/validate_link_add_valid/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validate_link_add_valid/src/lib.rs index 1fd8bbba..7d1bfb61 100644 --- a/crates/test_utils/wasm/wasm_workspace/validate_link_add_valid/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validate_link_add_valid/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/validate_valid/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validate_valid/src/lib.rs index 417f5a21..afce0f06 100644 --- a/crates/test_utils/wasm/wasm_workspace/validate_valid/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validate_valid/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/validation_package_fail/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validation_package_fail/src/lib.rs index 12571c7c..e4365f9d 100644 --- a/crates/test_utils/wasm/wasm_workspace/validation_package_fail/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validation_package_fail/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/validation_package_success/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/validation_package_success/src/lib.rs index b9cd9399..8774884e 100644 --- a/crates/test_utils/wasm/wasm_workspace/validation_package_success/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/validation_package_success/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; const NUM_SONGS: usize = 30; diff --git a/crates/test_utils/wasm/wasm_workspace/whoami/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/whoami/src/lib.rs index 9087e95a..641863c8 100644 --- a/crates/test_utils/wasm/wasm_workspace/whoami/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/whoami/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/x_salsa20_poly1305/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/x_salsa20_poly1305/src/lib.rs index b94f5e6d..2753a0c6 100644 --- a/crates/test_utils/wasm/wasm_workspace/x_salsa20_poly1305/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/x_salsa20_poly1305/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm/wasm_workspace/zome_info/src/lib.rs b/crates/test_utils/wasm/wasm_workspace/zome_info/src/lib.rs index f11b2909..bd675a99 100644 --- a/crates/test_utils/wasm/wasm_workspace/zome_info/src/lib.rs +++ b/crates/test_utils/wasm/wasm_workspace/zome_info/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[adk_extern] diff --git a/crates/test_utils/wasm_common/Cargo.toml b/crates/test_utils/wasm_common/Cargo.toml index b4afb249..782c7e74 100644 --- a/crates/test_utils/wasm_common/Cargo.toml +++ b/crates/test_utils/wasm_common/Cargo.toml @@ -2,7 +2,7 @@ name = "aingle_test_wasm_common" version = "0.0.1" description = "Common utilities for AIngle WASM testing" -license = "Apache-2.0" +license = "Apache-2.0 OR LicenseRef-Commercial" repository = "https://github.com/ApiliumCode/aingle" authors = ["Apilium Technologies "] keywords = ["aingle", "wasm", "testing"] diff --git a/crates/test_utils/wasm_common/src/lib.rs b/crates/test_utils/wasm_common/src/lib.rs index ece08cae..e185b5a3 100644 --- a/crates/test_utils/wasm_common/src/lib.rs +++ b/crates/test_utils/wasm_common/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2026 Apilium Technologies OÜ. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR Commercial + use adk::prelude::*; #[derive(Clone, serde::Serialize, serde::Deserialize, SerializedBytes, Debug)] diff --git a/docker-compose.yml b/docker-compose.yml index 58c13e21..87b964ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -67,16 +67,16 @@ services: container_name: aingle-cortex restart: unless-stopped ports: - - "4000:4000" # REST/GraphQL API + - "19090:19090" # REST/GraphQL API environment: - RUST_LOG=info - - CORTEX_PORT=4000 + - CORTEX_PORT=19090 - AINGLE_ADMIN_URL=ws://aingle:8888 depends_on: - aingle networks: - aingle-network - command: ["aingle-cortex", "--port", "4000"] + command: ["aingle-cortex", "--port", "19090"] # ========================================================================== # Development Environment diff --git a/docs/PROJECT_STATE.md b/docs/PROJECT_STATE.md index d24ecbe7..eda6b901 100644 --- a/docs/PROJECT_STATE.md +++ b/docs/PROJECT_STATE.md @@ -72,12 +72,12 @@ | Componente | Ubicación | Estado | LOC | Tests | Notas | |------------|-----------|--------|-----|-------|-------| -| HOPE Agents | `hope_agents/` | ✅ 100% | 8,440 | 133 | Learning Engine, Hierarchical Goals, Predictive Model | -| Titans Memory | `aingle_ai/titans/` | ✅ 100% | 1,200+ | 45+ | STM+LTM completo | +| Kaneru | `kaneru/` | ✅ 100% | 8,440 | 133 | Learning Engine, Hierarchical Goals, Predictive Model | +| Ineru | `aingle_ai/ineru/` | ✅ 100% | 1,200+ | 45+ | STM+LTM completo | | Nested Learning | `aingle_ai/nested_learning/` | ✅ 100% | 900+ | 32+ | Meta-optimization completa | | Emergent | `aingle_ai/emergent/` | ✅ 100% | 650+ | 28+ | Redes neuronales emergentes funcionales | -#### HOPE Agents - Detalle (100% Complete - 133 Tests, 8,440 LOC) +#### Kaneru - Detalle (100% Complete - 133 Tests, 8,440 LOC) - ✅ Learning Engine (2,100 LOC): Q-Learning, SARSA, TD(λ), Experience Replay, Deep Q-Networks - ✅ Hierarchical Goal Solver (2,200 LOC): Goal decomposition, conflict detection, priority management - ✅ Predictive Model (1,800 LOC): Anomaly detection, state prediction, pattern recognition @@ -156,8 +156,8 @@ aingle/crates/ │ ├── AI/ML │ ├── aingle_ai/ # AI integration layer -│ ├── hope_agents/ # HOPE Agents framework -│ └── titans_memory/ # Dual memory system +│ ├── kaneru/ # Kaneru framework +│ └── ineru/ # Dual memory system │ ├── Advanced │ ├── aingle_cortex/ # REST/GraphQL/SPARQL API @@ -240,7 +240,7 @@ aingle/crates/ ### Diciembre 17, 2025 - Proyecto Completado al 100% -#### 1. HOPE Agents Framework (100% - 133 Tests, 8,440 LOC) +#### 1. Kaneru Framework (100% - 133 Tests, 8,440 LOC) - ✅ Learning Engine (2,100 LOC): Q-Learning, SARSA, TD(λ), Experience Replay, Deep Q-Networks - ✅ Hierarchical Goal Solver (2,200 LOC): Descomposición jerárquica, detección de conflictos, priority management - ✅ Predictive Model (1,800 LOC): Detección de anomalías, predicción de estados, pattern recognition @@ -304,7 +304,7 @@ aingle/crates/ ### Completado al 100% -- ✅ HOPE Agents Framework: 100% (8,440 LOC, 133 tests) +- ✅ Kaneru Framework: 100% (8,440 LOC, 133 tests) - ✅ Cortex API: 100% (6,087 LOC, 74 tests) - ✅ ZK Proofs: 100% (3,908 LOC, 81 tests) - ✅ DAG Visualization: 100% (4,540 LOC, 15 tests) diff --git a/docs/api/hope_agents.md b/docs/api/kaneru.md similarity index 97% rename from docs/api/hope_agents.md rename to docs/api/kaneru.md index 5634e199..dc8d0b3d 100644 --- a/docs/api/hope_agents.md +++ b/docs/api/kaneru.md @@ -1,11 +1,11 @@ -# HOPE Agents API Reference +# Kaneru API Reference -Hierarchical Optimizing Policy Engine for AIngle AI agents. +Unified Multi-Agent Execution System for AIngle AI agents. ## Overview ```rust -use hope_agents::{ +use kaneru::{ Agent, SimpleAgent, AgentConfig, Observation, ObservationType, Action, ActionResult, ActionType, @@ -295,7 +295,7 @@ pub struct AgentStats { ## Complete Example ```rust -use hope_agents::*; +use kaneru::*; fn main() { // Create agent diff --git a/docs/api/smart_node.md b/docs/api/smart_node.md index b1495f92..5cfb06a2 100644 --- a/docs/api/smart_node.md +++ b/docs/api/smart_node.md @@ -1,17 +1,17 @@ # SmartNode API Reference -SmartNode combines MinimalNode with HOPE Agent capabilities for intelligent IoT devices. +SmartNode combines MinimalNode with Kaneru capabilities for intelligent IoT devices. ## Overview ```rust use aingle_minimal::{SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder}; -use hope_agents::{Goal, Observation}; +use kaneru::{Goal, Observation}; ``` ## SmartNode -The main struct that combines a MinimalNode with a HOPE Agent. +The main struct that combines a MinimalNode with a Kaneru Agent. ### Creation @@ -86,7 +86,7 @@ Configuration for SmartNode. | Field | Type | Default | Description | |-------|------|---------|-------------| | `node_config` | `Config` | `Config::default()` | MinimalNode configuration | -| `agent_config` | `AgentConfig` | `AgentConfig::default()` | HOPE agent configuration | +| `agent_config` | `AgentConfig` | `AgentConfig::default()` | Kaneru agent configuration | | `auto_publish_observations` | `bool` | `true` | Auto-publish observations to DAG | ### Constructors @@ -191,7 +191,7 @@ pub struct SmartNodeStats { ```rust use aingle_minimal::{SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder, Result}; -use hope_agents::Goal; +use kaneru::Goal; fn main() -> Result<()> { // Create smart node @@ -234,5 +234,5 @@ fn main() -> Result<()> { ## See Also - [MinimalNode API](./minimal_node.md) -- [HOPE Agents](./hope_agents.md) +- [Kaneru](./kaneru.md) - [IoT Tutorial](../tutorials/iot-sensor-app.md) diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index dcbcb2eb..53a192a5 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -12,7 +12,7 @@ │ │ (Server) │ │ (IoT Device) │ │ (AI Agent) │ │ │ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ │ │ • Full DAG │ │ • Pruned DAG │ │ • MinimalNode │ │ -│ │ • Validation │ │ • CoAP Transport│ │ • HOPE Agent │ │ +│ │ • Validation │ │ • CoAP Transport│ │ • Kaneru Agent │ │ │ │ • Websocket API │ │ • Gossip Sync │ │ • Policy Engine │ │ │ │ • App Hosting │ │ • <1MB RAM │ │ • Learning │ │ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ @@ -86,9 +86,9 @@ aingle (main conductor) ├── aingle_minimal # Lightweight IoT node │ ├── coap # CoAP transport (RFC 7252) │ ├── gossip # Optimized gossip protocol -│ └── smart # SmartNode (with HOPE agents) -├── hope_agents # HOPE agent framework -└── titans_memory # Neural memory system +│ └── smart # SmartNode (with Kaneru agents) +├── kaneru # Kaneru agent framework +└── ineru # Neural memory system ``` ## Data Flow diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md index 779eb9d7..8153ea71 100644 --- a/docs/tutorials/README.md +++ b/docs/tutorials/README.md @@ -34,11 +34,11 @@ Build an IoT sensor network: --- -### 3. [AI-Powered App with HOPE Agents](./ai-powered-app.md) +### 3. [AI-Powered App with Kaneru](./ai-powered-app.md) **Time:** 90-120 minutes | **Level:** Advanced Add artificial intelligence to your applications: -- Configure and train HOPE Agents +- Configure and train Kaneru agents - Q-Learning and reinforcement learning - Hierarchical goals and planning - Automatic anomaly detection @@ -137,7 +137,7 @@ Smart contracts in AIngle |----------|---------------| | Getting Started | Nodes, DAG, Entries, Hash, mDNS, Gossip | | IoT Sensor Network | CoAP, Minimal node, Power modes, Batch readings | -| AI-Powered App | HOPE Agents, Q-Learning, Hierarchical goals, Anomaly detection | +| AI-Powered App | Kaneru, Q-Learning, Hierarchical goals, Anomaly detection | | Semantic Queries | REST API, GraphQL, SPARQL, WebSocket, Filtering | | Privacy with ZK | Commitments, Range proofs, Schnorr proofs, Batch verification | | DAG Visualization | D3.js, Force-directed layout, Graph export, Real-time updates | @@ -224,7 +224,7 @@ Found an error in a tutorial or want to add a new one? - Bilingual structure: English (main) + Spanish (es/) - Updated Getting Started tutorial - Created complete IoT Sensor Network tutorial -- Created complete AI with HOPE Agents tutorial +- Created complete AI with Kaneru tutorial - Created complete Semantic Queries tutorial - Created complete Privacy with ZK tutorial - Updated DAG Visualization tutorial diff --git a/docs/tutorials/ai-powered-app.md b/docs/tutorials/ai-powered-app.md index 07f97fe6..c5513a58 100644 --- a/docs/tutorials/ai-powered-app.md +++ b/docs/tutorials/ai-powered-app.md @@ -1,8 +1,8 @@ -# Tutorial: AI-Powered Application using HOPE Agents +# Tutorial: AI-Powered Application using Kaneru ## Objective -Build an intelligent application that automatically learns and makes decisions using HOPE Agents (Hierarchical Optimized Policy Engine). Agents can learn from experience, handle hierarchical goals, detect anomalies, and execute autonomous actions. +Build an intelligent application that automatically learns and makes decisions using Kaneru (Unified Multi-Agent Execution System). Agents can learn from experience, handle hierarchical goals, detect anomalies, and execute autonomous actions. ## Prerequisites @@ -16,9 +16,9 @@ Build an intelligent application that automatically learns and makes decisions u --- -## Step 1: Understanding HOPE Agents +## Step 1: Understanding Kaneru -HOPE (Hierarchical Optimized Policy Engine) combines: +Kaneru (Unified Multi-Agent Execution System) combines: - **Q-Learning**: Learns state-action values - **SARSA**: Learns on-policy policies @@ -31,7 +31,7 @@ HOPE (Hierarchical Optimized Policy Engine) combines: ``` ┌─────────────────────────────────────────┐ -│ HOPE Agent │ +│ Kaneru Agent │ ├─────────────────────────────────────────┤ │ ┌──────────┐ ┌──────────────────┐ │ │ │ Learning │ │ Goal Solver │ │ @@ -47,7 +47,7 @@ HOPE (Hierarchical Optimized Policy Engine) combines: --- -## Step 2: Set up basic HOPE Agent +## Step 2: Set up basic Kaneru Agent Create a new project: @@ -66,7 +66,7 @@ version = "0.1.0" edition = "2021" [dependencies] -hope_agents = { path = "../../crates/hope_agents" } +kaneru = { path = "../../crates/kaneru" } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -78,7 +78,7 @@ Create your first agent: ```rust // src/main.rs -use hope_agents::{HopeAgent, AgentConfig, Observation, ActionResult}; +use kaneru::{KaneruAgent, AgentConfig, Observation, ActionResult}; use std::time::Duration; #[tokio::main] @@ -99,13 +99,13 @@ async fn main() -> anyhow::Result<()> { action_timeout: Duration::from_secs(10), }; - println!("🤖 Creating HOPE Agent: {}", config.name); + println!("🤖 Creating Kaneru Agent: {}", config.name); println!(" Learning: {}", config.learning_enabled); println!(" Learning rate: {}", config.learning_rate); println!(" Exploration: {}%\n", config.exploration_rate * 100.0); // Create agent - let mut agent = HopeAgent::new(config)?; + let mut agent = KaneruAgent::new(config)?; println!("✓ Agent created and ready to learn\n"); @@ -129,7 +129,7 @@ Agents learn through environment observations and executing actions: ```rust // src/thermostat.rs use serde::{Deserialize, Serialize}; -use hope_agents::{Observation, Action, ActionResult}; +use kaneru::{Observation, Action, ActionResult}; /// Thermostat state #[derive(Serialize, Deserialize, Debug, Clone)] @@ -279,11 +279,11 @@ impl ThermostatAction { ## Step 4: Hierarchical goals -HOPE Agents support hierarchical goals that decompose into subgoals: +Kaneru supports hierarchical goals that decompose into subgoals: ```rust // src/goals.rs -use hope_agents::{Goal, GoalPriority}; +use kaneru::{Goal, GoalPriority}; /// Define thermostat objectives pub fn create_comfort_goals() -> Vec { @@ -368,11 +368,11 @@ Train the agent with learning episodes: ```rust // src/training.rs -use hope_agents::HopeAgent; +use kaneru::KaneruAgent; use crate::thermostat::{ThermostatState, ThermostatAction}; use crate::goals::create_comfort_goals; -pub async fn train_agent(agent: &mut HopeAgent, episodes: usize) -> anyhow::Result<()> { +pub async fn train_agent(agent: &mut KaneruAgent, episodes: usize) -> anyhow::Result<()> { println!("🎓 Starting training ({} episodes)...\n", episodes); // Load objectives @@ -465,11 +465,11 @@ The predictive model detects abnormal behaviors: ```rust // src/anomaly.rs -use hope_agents::HopeAgent; +use kaneru::KaneruAgent; use crate::thermostat::ThermostatState; pub async fn detect_anomalies( - agent: &HopeAgent, + agent: &KaneruAgent, state: &ThermostatState, ) -> anyhow::Result { let observation = state.to_observation(); @@ -498,7 +498,7 @@ pub async fn detect_anomalies( /// Continuously monitor and alert anomalies pub async fn monitor_anomalies( - agent: &HopeAgent, + agent: &KaneruAgent, states: Vec, ) -> anyhow::Result<()> { println!("🔍 Monitoring anomalies...\n"); @@ -544,12 +544,12 @@ The agent executes decisions automatically: ```rust // src/autonomous.rs -use hope_agents::HopeAgent; +use kaneru::KaneruAgent; use crate::thermostat::{ThermostatState, ThermostatAction}; use tokio::time::{interval, Duration}; pub async fn run_autonomous_mode( - agent: &mut HopeAgent, + agent: &mut KaneruAgent, initial_state: ThermostatState, duration_secs: u64, ) -> anyhow::Result<()> { @@ -631,7 +631,7 @@ mod training; mod anomaly; mod autonomous; -use hope_agents::{HopeAgent, AgentConfig}; +use kaneru::{KaneruAgent, AgentConfig}; use thermostat::ThermostatState; use std::time::Duration; @@ -641,8 +641,8 @@ async fn main() -> anyhow::Result<()> { // 1. Create agent let config = AgentConfig::ai_mode(); - let mut agent = HopeAgent::new(config)?; - println!("✓ HOPE Agent created\n"); + let mut agent = KaneruAgent::new(config)?; + println!("✓ Kaneru Agent created\n"); // 2. Train training::train_agent(&mut agent, 100).await?; @@ -691,7 +691,7 @@ async fn main() -> anyhow::Result<()> { ## Expected output ``` -✓ HOPE Agent created +✓ Kaneru Agent created 🎓 Starting training (100 episodes)... @@ -792,7 +792,7 @@ agent.set_anomaly_threshold(2.5); // More sensitive (default: 3.0) ## Key concepts learned -- **HOPE Agent**: Hierarchical learning agent +- **Kaneru Agent**: Hierarchical learning agent - **Q-Learning**: Learns state-action values - **Exploration vs Exploitation**: Balance between exploring and using learned knowledge - **Hierarchical Goals**: Decompose complex objectives @@ -803,7 +803,7 @@ agent.set_anomaly_threshold(2.5); // More sensitive (default: 3.0) ## References -- [HOPE Agents Implementation](../../crates/hope_agents/IMPLEMENTATION_SUMMARY.md) +- [Kaneru Implementation](../../crates/kaneru/IMPLEMENTATION_SUMMARY.md) - [Reinforcement Learning: An Introduction](http://incompleteideas.net/book/the-book-2nd.html) - [Q-Learning Tutorial](https://en.wikipedia.org/wiki/Q-learning) - [Hierarchical Reinforcement Learning](https://people.cs.umass.edu/~mahadeva/papers/hrl.pdf) diff --git a/docs/tutorials/dag-visualization.md b/docs/tutorials/dag-visualization.md index 831bb856..73414dd3 100644 --- a/docs/tutorials/dag-visualization.md +++ b/docs/tutorials/dag-visualization.md @@ -844,7 +844,7 @@ async function fetchDag() { ## Next steps 1. **Custom dashboard**: Create app-specific metrics -2. **[AI Integration](./ai-powered-app.md)**: Visualize HOPE Agents decisions +2. **[AI Integration](./ai-powered-app.md)**: Visualize Kaneru decisions 3. **Network analysis**: Detect patterns and anomalies in the graph 4. **Collaboration**: Multiple users viewing the same DAG in real-time diff --git a/docs/tutorials/es/README.md b/docs/tutorials/es/README.md index a6155467..aaf20913 100644 --- a/docs/tutorials/es/README.md +++ b/docs/tutorials/es/README.md @@ -34,11 +34,11 @@ Construye una red de sensores IoT: --- -### 3. [Aplicación con IA usando HOPE Agents](./ai-powered-app.md) +### 3. [Aplicación con IA usando Kaneru](./ai-powered-app.md) **Tiempo:** 90-120 minutos | **Nivel:** Avanzado Añade inteligencia artificial a tus aplicaciones: -- Configurar y entrenar HOPE Agents +- Configurar y entrenar Kaneru - Q-Learning y aprendizaje por refuerzo - Metas jerárquicas y planificación - Detección automática de anomalías @@ -137,7 +137,7 @@ Contratos inteligentes en AIngle |----------|----------------------| | Getting Started | Nodos, DAG, Entries, Hash, mDNS, Gossip | | IoT Sensor Network | CoAP, Minimal node, Power modes, Batch readings | -| AI-Powered App | HOPE Agents, Q-Learning, Hierarchical goals, Anomaly detection | +| AI-Powered App | Kaneru, Q-Learning, Hierarchical goals, Anomaly detection | | Semantic Queries | REST API, GraphQL, SPARQL, WebSocket, Filtering | | Privacy with ZK | Commitments, Range proofs, Schnorr proofs, Batch verification | | DAG Visualization | D3.js, Force-directed layout, Graph export, Real-time updates | diff --git a/docs/tutorials/es/ai-powered-app.md b/docs/tutorials/es/ai-powered-app.md index 5cf83c9d..68c59fce 100644 --- a/docs/tutorials/es/ai-powered-app.md +++ b/docs/tutorials/es/ai-powered-app.md @@ -1,8 +1,8 @@ -# Tutorial: Aplicación con IA usando HOPE Agents +# Tutorial: Aplicación con IA usando Kaneru ## Objetivo -Construir una aplicación inteligente que aprende automáticamente y toma decisiones usando HOPE Agents (Hierarchical Optimized Policy Engine). Los agentes pueden aprender de la experiencia, manejar metas jerárquicas, detectar anomalías y ejecutar acciones autónomas. +Construir una aplicación inteligente que aprende automáticamente y toma decisiones usando Kaneru (Unified Multi-Agent Execution System). Los agentes pueden aprender de la experiencia, manejar metas jerárquicas, detectar anomalías y ejecutar acciones autónomas. ## Prerrequisitos @@ -16,9 +16,9 @@ Construir una aplicación inteligente que aprende automáticamente y toma decisi --- -## Paso 1: Entender HOPE Agents +## Paso 1: Entender Kaneru -HOPE (Hierarchical Optimized Policy Engine) combina: +Kaneru (Unified Multi-Agent Execution System) combina: - **Q-Learning**: Aprende valores de estado-acción - **SARSA**: Aprende políticas on-policy @@ -31,7 +31,7 @@ HOPE (Hierarchical Optimized Policy Engine) combina: ``` ┌─────────────────────────────────────────┐ -│ HOPE Agent │ +│ Kaneru Agent │ ├─────────────────────────────────────────┤ │ ┌──────────┐ ┌──────────────────┐ │ │ │ Learning │ │ Goal Solver │ │ @@ -47,7 +47,7 @@ HOPE (Hierarchical Optimized Policy Engine) combina: --- -## Paso 2: Configurar HOPE Agent básico +## Paso 2: Configurar Kaneru Agent básico Crea un nuevo proyecto: @@ -66,7 +66,7 @@ version = "0.1.0" edition = "2021" [dependencies] -hope_agents = { path = "../../crates/hope_agents" } +kaneru = { path = "../../crates/kaneru" } tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -78,7 +78,7 @@ Crea tu primer agente: ```rust // src/main.rs -use hope_agents::{HopeAgent, AgentConfig, Observation, ActionResult}; +use kaneru::{KaneruAgent, AgentConfig, Observation, ActionResult}; use std::time::Duration; #[tokio::main] @@ -99,13 +99,13 @@ async fn main() -> anyhow::Result<()> { action_timeout: Duration::from_secs(10), }; - println!("🤖 Creando HOPE Agent: {}", config.name); + println!("🤖 Creando Kaneru Agent: {}", config.name); println!(" Learning: {}", config.learning_enabled); println!(" Learning rate: {}", config.learning_rate); println!(" Exploration: {}%\n", config.exploration_rate * 100.0); // Crear agente - let mut agent = HopeAgent::new(config)?; + let mut agent = KaneruAgent::new(config)?; println!("✓ Agente creado y listo para aprender\n"); @@ -129,7 +129,7 @@ Los agentes aprenden mediante observaciones del entorno y ejecutando acciones: ```rust // src/thermostat.rs use serde::{Deserialize, Serialize}; -use hope_agents::{Observation, Action, ActionResult}; +use kaneru::{Observation, Action, ActionResult}; /// Estado del termostato #[derive(Serialize, Deserialize, Debug, Clone)] @@ -279,11 +279,11 @@ impl ThermostatAction { ## Paso 4: Metas jerárquicas -HOPE Agents soportan metas jerárquicas que se descomponen en submetas: +Kaneru soporta metas jerárquicas que se descomponen en submetas: ```rust // src/goals.rs -use hope_agents::{Goal, GoalPriority}; +use kaneru::{Goal, GoalPriority}; /// Definir objetivos del termostato pub fn create_comfort_goals() -> Vec { @@ -368,11 +368,11 @@ Entrena el agente con episodios de aprendizaje: ```rust // src/training.rs -use hope_agents::HopeAgent; +use kaneru::KaneruAgent; use crate::thermostat::{ThermostatState, ThermostatAction}; use crate::goals::create_comfort_goals; -pub async fn train_agent(agent: &mut HopeAgent, episodes: usize) -> anyhow::Result<()> { +pub async fn train_agent(agent: &mut KaneruAgent, episodes: usize) -> anyhow::Result<()> { println!("🎓 Iniciando entrenamiento ({} episodios)...\n", episodes); // Cargar objetivos @@ -465,11 +465,11 @@ El modelo predictivo detecta comportamientos anormales: ```rust // src/anomaly.rs -use hope_agents::HopeAgent; +use kaneru::KaneruAgent; use crate::thermostat::ThermostatState; pub async fn detect_anomalies( - agent: &HopeAgent, + agent: &KaneruAgent, state: &ThermostatState, ) -> anyhow::Result { let observation = state.to_observation(); @@ -498,7 +498,7 @@ pub async fn detect_anomalies( /// Monitorear continuamente y alertar anomalías pub async fn monitor_anomalies( - agent: &HopeAgent, + agent: &KaneruAgent, states: Vec, ) -> anyhow::Result<()> { println!("🔍 Monitoreando anomalías...\n"); @@ -544,12 +544,12 @@ El agente ejecuta decisiones automáticamente: ```rust // src/autonomous.rs -use hope_agents::HopeAgent; +use kaneru::KaneruAgent; use crate::thermostat::{ThermostatState, ThermostatAction}; use tokio::time::{interval, Duration}; pub async fn run_autonomous_mode( - agent: &mut HopeAgent, + agent: &mut KaneruAgent, initial_state: ThermostatState, duration_secs: u64, ) -> anyhow::Result<()> { @@ -631,7 +631,7 @@ mod training; mod anomaly; mod autonomous; -use hope_agents::{HopeAgent, AgentConfig}; +use kaneru::{KaneruAgent, AgentConfig}; use thermostat::ThermostatState; use std::time::Duration; @@ -641,8 +641,8 @@ async fn main() -> anyhow::Result<()> { // 1. Crear agente let config = AgentConfig::ai_mode(); - let mut agent = HopeAgent::new(config)?; - println!("✓ HOPE Agent creado\n"); + let mut agent = KaneruAgent::new(config)?; + println!("✓ Kaneru Agent creado\n"); // 2. Entrenar training::train_agent(&mut agent, 100).await?; @@ -691,7 +691,7 @@ async fn main() -> anyhow::Result<()> { ## Resultado esperado ``` -✓ HOPE Agent creado +✓ Kaneru Agent creado 🎓 Iniciando entrenamiento (100 episodios)... @@ -792,7 +792,7 @@ agent.set_anomaly_threshold(2.5); // Más sensible (default: 3.0) ## Conceptos clave aprendidos -- **HOPE Agent**: Agente de aprendizaje jerárquico +- **Kaneru Agent**: Agente de aprendizaje jerárquico - **Q-Learning**: Aprende valores de estado-acción - **Exploration vs Exploitation**: Balance entre explorar y usar lo aprendido - **Hierarchical Goals**: Descomponer objetivos complejos @@ -803,7 +803,7 @@ agent.set_anomaly_threshold(2.5); // Más sensible (default: 3.0) ## Referencias -- [HOPE Agents Implementation](../../crates/hope_agents/IMPLEMENTATION_SUMMARY.md) +- [Kaneru Implementation](../../crates/kaneru/IMPLEMENTATION_SUMMARY.md) - [Reinforcement Learning: An Introduction](http://incompleteideas.net/book/the-book-2nd.html) - [Q-Learning Tutorial](https://en.wikipedia.org/wiki/Q-learning) - [Hierarchical Reinforcement Learning](https://people.cs.umass.edu/~mahadeva/papers/hrl.pdf) diff --git a/docs/tutorials/es/dag-visualization.md b/docs/tutorials/es/dag-visualization.md index 2414a78e..98a2bb4d 100644 --- a/docs/tutorials/es/dag-visualization.md +++ b/docs/tutorials/es/dag-visualization.md @@ -844,7 +844,7 @@ async function fetchDag() { ## Próximos pasos 1. **Dashboard personalizado**: Crea métricas específicas de tu app -2. **[Integración con IA](./ai-powered-app.md)**: Visualiza decisiones de HOPE Agents +2. **[Integración con IA](./ai-powered-app.md)**: Visualiza decisiones de Kaneru 3. **Análisis de red**: Detecta patrones y anomalías en el grafo 4. **Colaboración**: Múltiples usuarios viendo el mismo DAG en tiempo real diff --git a/docs/tutorials/es/getting-started.md b/docs/tutorials/es/getting-started.md index 0d627a02..75386393 100644 --- a/docs/tutorials/es/getting-started.md +++ b/docs/tutorials/es/getting-started.md @@ -182,7 +182,7 @@ let config = Config { node_id: Some("node-1".to_string()), transport: TransportConfig::Quic { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, }, enable_mdns: true, // Habilitar descubrimiento automático // ... resto de configuración @@ -205,8 +205,8 @@ use aingle_p2p::NetworkConfig; // Conectar a peers conocidos let network_config = NetworkConfig { bootstrap_nodes: vec![ - "quic://192.168.1.100:8443".to_string(), - "quic://192.168.1.101:8443".to_string(), + "quic://192.168.1.100:19081".to_string(), + "quic://192.168.1.101:19081".to_string(), ], ..Default::default() }; @@ -383,7 +383,7 @@ config.transport = TransportConfig::Quic { config.enable_mdns = true; // Habilitar mDNS // O configurar peers manualmente -let bootstrap_nodes = vec!["quic://192.168.1.100:8443"]; +let bootstrap_nodes = vec!["quic://192.168.1.100:19081"]; ``` --- @@ -393,7 +393,7 @@ let bootstrap_nodes = vec!["quic://192.168.1.100:8443"]; Ahora que tienes un nodo funcionando, puedes explorar: 1. **[Tutorial de Red de Sensores IoT](./iot-sensor-network.md)**: Configura dispositivos IoT que publican datos al DAG -2. **[Tutorial de IA con HOPE Agents](./ai-powered-app.md)**: Añade capacidades de aprendizaje automático +2. **[Tutorial de IA con Kaneru](./ai-powered-app.md)**: Añade capacidades de aprendizaje automático 3. **[Tutorial de Consultas Semánticas](./semantic-queries.md)**: Consulta datos con GraphQL y SPARQL 4. **[Tutorial de Visualización](./dag-visualization.md)**: Visualiza el DAG en tiempo real diff --git a/docs/tutorials/es/iot-sensor-app.md b/docs/tutorials/es/iot-sensor-app.md index 0d481ce6..fe9d9c62 100644 --- a/docs/tutorials/es/iot-sensor-app.md +++ b/docs/tutorials/es/iot-sensor-app.md @@ -22,7 +22,7 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] aingle_minimal = { version = "0.1", features = ["coap", "smart_agents"] } -hope_agents = "0.1" +kaneru = "0.1" smol = "2.0" log = "0.4" env_logger = "0.11" @@ -68,7 +68,7 @@ Upgrade to a SmartNode for intelligent decision-making: ```rust use aingle_minimal::{SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder}; -use hope_agents::{Observation, Action, Goal}; +use kaneru::{Observation, Action, Goal}; fn main() -> Result<()> { env_logger::init(); @@ -176,7 +176,7 @@ Monitor multiple sensors with policies: ```rust use aingle_minimal::{SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder}; -use hope_agents::{Action, Policy, Rule, Condition}; +use kaneru::{Action, Policy, Rule, Condition}; fn setup_sensors() -> Vec { vec![ @@ -274,7 +274,7 @@ Here's a full working example combining all concepts: use aingle_minimal::{ SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder, Result }; -use hope_agents::{Observation, Goal}; +use kaneru::{Observation, Goal}; use std::time::Duration; #[smol::main] @@ -336,5 +336,5 @@ async fn main() -> Result<()> { ## Resources - [RFC 7252 - CoAP](https://tools.ietf.org/html/rfc7252) -- [HOPE Agents Documentation](../api/hope_agents.md) +- [Kaneru Documentation](../api/kaneru.md) - [AIngle GitHub Repository](https://github.com/ApiliumCode/aingle) diff --git a/docs/tutorials/es/semantic-graph.md b/docs/tutorials/es/semantic-graph.md index 6040398b..61004101 100644 --- a/docs/tutorials/es/semantic-graph.md +++ b/docs/tutorials/es/semantic-graph.md @@ -342,4 +342,4 @@ let certified = engine.query(" --- -Copyright 2019-2025 Apilium Technologies +Copyright 2019-2026 Apilium Technologies diff --git a/docs/tutorials/es/semantic-queries.md b/docs/tutorials/es/semantic-queries.md index 10b32706..cb673178 100644 --- a/docs/tutorials/es/semantic-queries.md +++ b/docs/tutorials/es/semantic-queries.md @@ -56,7 +56,7 @@ pub async fn start_cortex_server() -> anyhow::Result<()> { // Configurar servidor let config = CortexConfig { host: "127.0.0.1".to_string(), - port: 8080, + port: 19090, cors_enabled: true, graphql_playground: true, tracing: true, @@ -100,16 +100,16 @@ cargo run **Resultado esperado:** ``` 🚀 Iniciando Córtex API Server... - Host: 127.0.0.1:8080 - REST API: http://127.0.0.1:8080/api/v1 - GraphQL: http://127.0.0.1:8080/graphql - SPARQL: http://127.0.0.1:8080/sparql + Host: 127.0.0.1:19090 + REST API: http://127.0.0.1:19090/api/v1 + GraphQL: http://127.0.0.1:19090/graphql + SPARQL: http://127.0.0.1:19090/sparql -[INFO] Córtex API server listening on 127.0.0.1:8080 +[INFO] Córtex API server listening on 127.0.0.1:19090 ``` **Explicación:** -- **Puerto 8080**: API REST, GraphQL y SPARQL +- **Puerto 19090**: API REST, GraphQL y SPARQL - **CORS enabled**: Permite llamadas desde navegador - **Rate limiting**: Máximo 100 requests/minuto por IP - **GraphQL Playground**: UI interactiva en `/graphql` @@ -189,7 +189,7 @@ Uso: ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { - let client = CortexClient::new("http://127.0.0.1:8080"); + let client = CortexClient::new("http://127.0.0.1:19090"); // Health check client.health_check().await?; @@ -414,7 +414,7 @@ impl GraphQLClient { Uso: ```rust -let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); +let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); // Consultar sensores de temperatura let entries = graphql @@ -509,7 +509,7 @@ impl SparqlClient { ### Query 1: Listar todos los sensores de temperatura ```rust -let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); +let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let query = r#" PREFIX aingle: @@ -781,7 +781,7 @@ impl WebSocketClient { Uso: ```rust -let ws_client = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); +let ws_client = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); // Subscribirse a nuevas entries de sensores IoT ws_client.subscribe_entries(Some("iot_sensors".to_string())).await?; @@ -836,14 +836,14 @@ async fn main() -> anyhow::Result<()> { // 1. REST API println!("═══ REST API ═══"); - let rest = CortexClient::new("http://127.0.0.1:8080"); + let rest = CortexClient::new("http://127.0.0.1:19090"); rest.health_check().await?; let entries = rest.list_entries(5).await?; println!(); // 2. GraphQL println!("═══ GraphQL ═══"); - let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); + let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); let gql_entries = graphql .query_entries("iot_sensors", "temperature", 5) .await?; @@ -851,7 +851,7 @@ async fn main() -> anyhow::Result<()> { // 3. SPARQL println!("═══ SPARQL ═══"); - let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); + let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let sparql_query = r#" PREFIX aingle: SELECT ?entry ?timestamp @@ -866,7 +866,7 @@ async fn main() -> anyhow::Result<()> { // 4. WebSocket (en background) tokio::spawn(async move { - let ws = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); + let ws = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); ws.subscribe_entries(None).await }); @@ -891,7 +891,7 @@ async fn main() -> anyhow::Result<()> { **Solución:** ```bash # Verificar que el servidor Córtex esté ejecutando -curl http://127.0.0.1:8080/api/v1/health +curl http://127.0.0.1:19090/api/v1/health ``` ### Rate limit excedido diff --git a/docs/tutorials/es/smart-contracts.md b/docs/tutorials/es/smart-contracts.md index 2eb3e155..f1b5d82a 100644 --- a/docs/tutorials/es/smart-contracts.md +++ b/docs/tutorials/es/smart-contracts.md @@ -326,4 +326,4 @@ let proxy = ContractBuilder::new("proxy") --- -Copyright 2019-2025 Apilium Technologies +Copyright 2019-2026 Apilium Technologies diff --git a/docs/tutorials/getting-started.md b/docs/tutorials/getting-started.md index 65771430..69f39e40 100644 --- a/docs/tutorials/getting-started.md +++ b/docs/tutorials/getting-started.md @@ -182,7 +182,7 @@ let config = Config { node_id: Some("node-1".to_string()), transport: TransportConfig::Quic { bind_addr: "0.0.0.0".to_string(), - port: 8443, + port: 19081, }, enable_mdns: true, // Enable automatic discovery // ... rest of configuration @@ -205,8 +205,8 @@ use aingle_p2p::NetworkConfig; // Connect to known peers let network_config = NetworkConfig { bootstrap_nodes: vec![ - "quic://192.168.1.100:8443".to_string(), - "quic://192.168.1.101:8443".to_string(), + "quic://192.168.1.100:19081".to_string(), + "quic://192.168.1.101:19081".to_string(), ], ..Default::default() }; @@ -383,7 +383,7 @@ config.transport = TransportConfig::Quic { config.enable_mdns = true; // Enable mDNS // Or configure peers manually -let bootstrap_nodes = vec!["quic://192.168.1.100:8443"]; +let bootstrap_nodes = vec!["quic://192.168.1.100:19081"]; ``` --- @@ -393,7 +393,7 @@ let bootstrap_nodes = vec!["quic://192.168.1.100:8443"]; Now that you have a working node, you can explore: 1. **[IoT Sensor Network Tutorial](./iot-sensor-network.md)**: Configure IoT devices that publish data to the DAG -2. **[AI with HOPE Agents Tutorial](./ai-powered-app.md)**: Add machine learning capabilities +2. **[AI with Kaneru Tutorial](./ai-powered-app.md)**: Add machine learning capabilities 3. **[Semantic Queries Tutorial](./semantic-queries.md)**: Query data with GraphQL and SPARQL 4. **[Visualization Tutorial](./dag-visualization.md)**: Visualize the DAG in real-time diff --git a/docs/tutorials/iot-sensor-app.md b/docs/tutorials/iot-sensor-app.md index 0d481ce6..fe9d9c62 100644 --- a/docs/tutorials/iot-sensor-app.md +++ b/docs/tutorials/iot-sensor-app.md @@ -22,7 +22,7 @@ Add dependencies to `Cargo.toml`: ```toml [dependencies] aingle_minimal = { version = "0.1", features = ["coap", "smart_agents"] } -hope_agents = "0.1" +kaneru = "0.1" smol = "2.0" log = "0.4" env_logger = "0.11" @@ -68,7 +68,7 @@ Upgrade to a SmartNode for intelligent decision-making: ```rust use aingle_minimal::{SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder}; -use hope_agents::{Observation, Action, Goal}; +use kaneru::{Observation, Action, Goal}; fn main() -> Result<()> { env_logger::init(); @@ -176,7 +176,7 @@ Monitor multiple sensors with policies: ```rust use aingle_minimal::{SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder}; -use hope_agents::{Action, Policy, Rule, Condition}; +use kaneru::{Action, Policy, Rule, Condition}; fn setup_sensors() -> Vec { vec![ @@ -274,7 +274,7 @@ Here's a full working example combining all concepts: use aingle_minimal::{ SmartNode, SmartNodeConfig, SensorAdapter, IoTPolicyBuilder, Result }; -use hope_agents::{Observation, Goal}; +use kaneru::{Observation, Goal}; use std::time::Duration; #[smol::main] @@ -336,5 +336,5 @@ async fn main() -> Result<()> { ## Resources - [RFC 7252 - CoAP](https://tools.ietf.org/html/rfc7252) -- [HOPE Agents Documentation](../api/hope_agents.md) +- [Kaneru Documentation](../api/kaneru.md) - [AIngle GitHub Repository](https://github.com/ApiliumCode/aingle) diff --git a/docs/tutorials/semantic-graph.md b/docs/tutorials/semantic-graph.md index e721224b..ab0218c9 100644 --- a/docs/tutorials/semantic-graph.md +++ b/docs/tutorials/semantic-graph.md @@ -342,4 +342,4 @@ let certified = engine.query(" --- -Copyright 2019-2025 Apilium Technologies +Copyright 2019-2026 Apilium Technologies diff --git a/docs/tutorials/semantic-queries.md b/docs/tutorials/semantic-queries.md index 3414f5a3..2a029c92 100644 --- a/docs/tutorials/semantic-queries.md +++ b/docs/tutorials/semantic-queries.md @@ -56,7 +56,7 @@ pub async fn start_cortex_server() -> anyhow::Result<()> { // Configure server let config = CortexConfig { host: "127.0.0.1".to_string(), - port: 8080, + port: 19090, cors_enabled: true, graphql_playground: true, tracing: true, @@ -100,16 +100,16 @@ cargo run **Expected output:** ``` 🚀 Starting Cortex API Server... - Host: 127.0.0.1:8080 - REST API: http://127.0.0.1:8080/api/v1 - GraphQL: http://127.0.0.1:8080/graphql - SPARQL: http://127.0.0.1:8080/sparql + Host: 127.0.0.1:19090 + REST API: http://127.0.0.1:19090/api/v1 + GraphQL: http://127.0.0.1:19090/graphql + SPARQL: http://127.0.0.1:19090/sparql -[INFO] Cortex API server listening on 127.0.0.1:8080 +[INFO] Cortex API server listening on 127.0.0.1:19090 ``` **Explanation:** -- **Port 8080**: REST API, GraphQL and SPARQL +- **Port 19090**: REST API, GraphQL and SPARQL - **CORS enabled**: Allows calls from browser - **Rate limiting**: Maximum 100 requests/minute per IP - **GraphQL Playground**: Interactive UI at `/graphql` @@ -189,7 +189,7 @@ Usage: ```rust #[tokio::main] async fn main() -> anyhow::Result<()> { - let client = CortexClient::new("http://127.0.0.1:8080"); + let client = CortexClient::new("http://127.0.0.1:19090"); // Health check client.health_check().await?; @@ -414,7 +414,7 @@ impl GraphQLClient { Usage: ```rust -let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); +let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); // Query temperature sensors let entries = graphql @@ -509,7 +509,7 @@ impl SparqlClient { ### Query 1: List all temperature sensors ```rust -let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); +let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let query = r#" PREFIX aingle: @@ -781,7 +781,7 @@ impl WebSocketClient { Usage: ```rust -let ws_client = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); +let ws_client = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); // Subscribe to new IoT sensor entries ws_client.subscribe_entries(Some("iot_sensors".to_string())).await?; @@ -836,14 +836,14 @@ async fn main() -> anyhow::Result<()> { // 1. REST API println!("═══ REST API ═══"); - let rest = CortexClient::new("http://127.0.0.1:8080"); + let rest = CortexClient::new("http://127.0.0.1:19090"); rest.health_check().await?; let entries = rest.list_entries(5).await?; println!(); // 2. GraphQL println!("═══ GraphQL ═══"); - let graphql = GraphQLClient::new("http://127.0.0.1:8080/graphql"); + let graphql = GraphQLClient::new("http://127.0.0.1:19090/graphql"); let gql_entries = graphql .query_entries("iot_sensors", "temperature", 5) .await?; @@ -851,7 +851,7 @@ async fn main() -> anyhow::Result<()> { // 3. SPARQL println!("═══ SPARQL ═══"); - let sparql = SparqlClient::new("http://127.0.0.1:8080/sparql"); + let sparql = SparqlClient::new("http://127.0.0.1:19090/sparql"); let sparql_query = r#" PREFIX aingle: SELECT ?entry ?timestamp @@ -866,7 +866,7 @@ async fn main() -> anyhow::Result<()> { // 4. WebSocket (in background) tokio::spawn(async move { - let ws = WebSocketClient::new("ws://127.0.0.1:8080/ws/updates"); + let ws = WebSocketClient::new("ws://127.0.0.1:19090/ws/updates"); ws.subscribe_entries(None).await }); @@ -891,7 +891,7 @@ async fn main() -> anyhow::Result<()> { **Solution:** ```bash # Verify that the Cortex server is running -curl http://127.0.0.1:8080/api/v1/health +curl http://127.0.0.1:19090/api/v1/health ``` ### Rate limit exceeded diff --git a/docs/tutorials/smart-contracts.md b/docs/tutorials/smart-contracts.md index d0aa361a..fa6508a5 100644 --- a/docs/tutorials/smart-contracts.md +++ b/docs/tutorials/smart-contracts.md @@ -326,4 +326,4 @@ let proxy = ContractBuilder::new("proxy") --- -Copyright 2019-2025 Apilium Technologies +Copyright 2019-2026 Apilium Technologies diff --git a/examples/ai_autonomous_agent/Cargo.toml b/examples/ai_autonomous_agent/Cargo.toml index c08449a5..5e1ee8cb 100644 --- a/examples/ai_autonomous_agent/Cargo.toml +++ b/examples/ai_autonomous_agent/Cargo.toml @@ -2,7 +2,7 @@ name = "ai_autonomous_agent" version = "0.1.0" edition = "2021" -description = "Autonomous AI agent example using HOPE Agents" +description = "Autonomous AI agent example using Kaneru" license = "Apache-2.0" publish = false @@ -11,8 +11,8 @@ name = "ai-agent" path = "src/main.rs" [dependencies] -hope_agents = { path = "../../crates/hope_agents" } -titans_memory = { path = "../../crates/titans_memory" } +kaneru = { path = "../../crates/kaneru" } +ineru = { path = "../../crates/ineru" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rand = "0.9" diff --git a/examples/ai_autonomous_agent/src/main.rs b/examples/ai_autonomous_agent/src/main.rs index 63ce9dde..892d79a6 100644 --- a/examples/ai_autonomous_agent/src/main.rs +++ b/examples/ai_autonomous_agent/src/main.rs @@ -1,6 +1,6 @@ //! Autonomous AI Agent Example //! -//! Demonstrates how to use HOPE Agents for autonomous decision-making. +//! Demonstrates how to use Kaneru for autonomous decision-making. //! //! # Features Demonstrated //! - Creating simple and advanced agents @@ -13,14 +13,14 @@ //! cargo run --release -p ai_autonomous_agent //! ``` -use hope_agents::{ +use kaneru::{ create_iot_agent, Action, ActionType, Agent, Condition, Goal, Observation, Rule, SimpleAgent, ValueRange, }; use rand::Rng; fn main() -> Result<(), Box> { - println!("=== HOPE Agents - Autonomous AI Example ===\n"); + println!("=== Kaneru - Autonomous AI Example ===\n"); // Example 1: Simple Reactive Agent simple_reactive_agent_demo()?; diff --git a/examples/dag_visualization/Cargo.toml b/examples/dag_visualization/Cargo.toml index 8ea9b6d9..5df01456 100644 --- a/examples/dag_visualization/Cargo.toml +++ b/examples/dag_visualization/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" aingle_viz = { path = "../../crates/aingle_viz" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.49", features = ["rt-multi-thread", "macros"] } chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } env_logger = "0.11" log = "0.4" diff --git a/examples/deep_context/Cargo.toml b/examples/deep_context/Cargo.toml index 2ddcf91a..ebb739a9 100644 --- a/examples/deep_context/Cargo.toml +++ b/examples/deep_context/Cargo.toml @@ -18,7 +18,7 @@ path = "src/main.rs" # Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -bincode = "1.3" +bincode = { version = "2.0", features = ["serde"] } # CLI clap = { version = "4.5", features = ["derive", "cargo"] } diff --git a/examples/deep_context/src/semantic_index.rs b/examples/deep_context/src/semantic_index.rs index 7022c8f4..3d1ad981 100644 --- a/examples/deep_context/src/semantic_index.rs +++ b/examples/deep_context/src/semantic_index.rs @@ -36,7 +36,7 @@ impl SemanticIndex { let decisions_tree = db.open_tree("decisions")?; for item in decisions_tree.iter() { let (_key, value) = item?; - let decision: ArchitecturalDecision = bincode::deserialize(&value)?; + let decision: ArchitecturalDecision = bincode::serde::decode_from_slice(&value, bincode::config::standard()).map(|(v, _)| v)?; graph.add_decision(decision); } @@ -44,7 +44,7 @@ impl SemanticIndex { let contexts_tree = db.open_tree("code_contexts")?; for item in contexts_tree.iter() { let (_, value) = item?; - let context: CodeContext = bincode::deserialize(&value)?; + let context: CodeContext = bincode::serde::decode_from_slice(&value, bincode::config::standard()).map(|(v, _)| v)?; graph.add_code_context(context); } @@ -52,7 +52,7 @@ impl SemanticIndex { let commits_tree = db.open_tree("commits")?; for item in commits_tree.iter() { let (_, value) = item?; - let commit: LinkedCommit = bincode::deserialize(&value)?; + let commit: LinkedCommit = bincode::serde::decode_from_slice(&value, bincode::config::standard()).map(|(v, _)| v)?; graph.add_commit(commit); } @@ -63,7 +63,7 @@ impl SemanticIndex { pub fn store_decision(&mut self, decision: ArchitecturalDecision) -> Result<()> { let tree = self.db.open_tree("decisions")?; let key = decision.id.as_bytes(); - let value = bincode::serialize(&decision)?; + let value = bincode::serde::encode_to_vec(&decision, bincode::config::standard())?; tree.insert(key, value)?; // Update in-memory graph @@ -78,7 +78,7 @@ impl SemanticIndex { let key = id.as_bytes(); if let Some(value) = tree.get(key)? { - let decision: ArchitecturalDecision = bincode::deserialize(&value)?; + let decision: ArchitecturalDecision = bincode::serde::decode_from_slice(&value, bincode::config::standard()).map(|(v, _)| v)?; Ok(Some(decision)) } else { Ok(None) @@ -89,7 +89,7 @@ impl SemanticIndex { pub fn store_code_context(&mut self, context: CodeContext) -> Result<()> { let tree = self.db.open_tree("code_contexts")?; let key = context.file_path.as_bytes(); - let value = bincode::serialize(&context)?; + let value = bincode::serde::encode_to_vec(&context, bincode::config::standard())?; tree.insert(key, value)?; // Update in-memory graph @@ -102,7 +102,7 @@ impl SemanticIndex { pub fn store_commit(&mut self, commit: LinkedCommit) -> Result<()> { let tree = self.db.open_tree("commits")?; let key = commit.commit_hash.as_bytes(); - let value = bincode::serialize(&commit)?; + let value = bincode::serde::encode_to_vec(&commit, bincode::config::standard())?; tree.insert(key, value)?; // Update in-memory graph @@ -118,7 +118,7 @@ impl SemanticIndex { let tree = self.db.open_tree("decisions")?; for item in tree.iter() { let (_, value) = item?; - let decision: ArchitecturalDecision = bincode::deserialize(&value)?; + let decision: ArchitecturalDecision = bincode::serde::decode_from_slice(&value, bincode::config::standard()).map(|(v, _)| v)?; if self.matches_query(&decision, query) { results.push(decision); diff --git a/examples/semantic_compliance/README.md b/examples/semantic_compliance/README.md index 39be4783..8ea1093a 100644 --- a/examples/semantic_compliance/README.md +++ b/examples/semantic_compliance/README.md @@ -601,7 +601,7 @@ docker build -t semantic-compliance . # Run container docker run -d \ - -p 8080:8080 \ + -p 19090:19090 \ -v $(pwd)/config:/app/config \ -v $(pwd)/data:/app/data \ semantic-compliance diff --git a/templates/README.md b/templates/README.md index 32a67ed9..12fd7d3c 100644 --- a/templates/README.md +++ b/templates/README.md @@ -7,7 +7,7 @@ Pre-built templates for common AIngle use cases, optimized for IoT and AI applic | Template | Description | Use Case | |----------|-------------|----------| | **iot-sensor** | IoT sensor data collection | Smart devices, environmental monitoring | -| **ai-agent** | AI agents with Titans Memory | Machine learning, autonomous systems | +| **ai-agent** | AI agents with Ineru | Machine learning, autonomous systems | | **supply-chain** | Product tracking & provenance | Logistics, authenticity verification | ## Quick Start @@ -24,9 +24,9 @@ cargo build --target wasm32-unknown-unknown --release cargo test ``` -## Integration with Titans Memory +## Integration with Ineru -All templates can leverage the Titans Memory system for AI-native memory management: +All templates can leverage the Ineru system for AI-native memory management: ```rust use aingle_minimal::{IoTMemory, Config}; @@ -48,7 +48,7 @@ memory.maintenance()?; ``` ┌─────────────────────────────────────────────────────────────┐ -│ Titans Memory System │ +│ Ineru Memory System │ ├─────────────────────────────────────────────────────────────┤ │ ┌──────────────────┐ ┌──────────────────────────────┐ │ │ │ Short-Term │ │ Long-Term Memory (LTM) │ │ @@ -60,12 +60,12 @@ memory.maintenance()?; └─────────────────────────────────────────────────────────────┘ ``` -## Integration with HOPE Agents +## Integration with Kaneru -Templates can use the HOPE (Hierarchical Optimizing Policy Engine) framework: +Templates can use the Kaneru (Unified Multi-Agent Execution System) framework: ```rust -use hope_agents::{Agent, SimpleAgent, Goal, Observation, Rule, Condition, Action}; +use kaneru::{Agent, SimpleAgent, Goal, Observation, Rule, Condition, Action}; // Create an IoT-optimized agent let mut agent = SimpleAgent::with_config("sensor_monitor", AgentConfig::iot_mode()); @@ -90,7 +90,7 @@ agent.execute(action); ### Memory-Enabled Agents ```rust -use hope_agents::memory::MemoryAgent; +use kaneru::memory::MemoryAgent; // Create memory-enabled agent let mut agent = MemoryAgent::new("smart_controller"); @@ -130,7 +130,7 @@ export AINGLE_IOT_MODE=1 ## AI Agent Template -For AI agents using the Titans Memory architecture. +For AI agents using the Ineru memory architecture. **Features:** - Short-term memory (sliding window) @@ -143,9 +143,9 @@ For AI agents using the Titans Memory architecture. - `LongTermMemory` - Knowledge checkpoints - `LearningEvent` - Training events -**Build with HOPE Agents:** +**Build with Kaneru:** ```bash -cargo build --features hope --target wasm32-unknown-unknown +cargo build --features kaneru --target wasm32-unknown-unknown ``` --- @@ -191,7 +191,7 @@ Full product provenance tracking. 1. **Keep entries small** - Under 1KB for IoT 2. **Use batch uploads** - Reduce network overhead 3. **Index with links** - Enable efficient queries -4. **Use Titans Memory** - For AI-enabled applications +4. **Use Ineru** - For AI-enabled applications 5. **Configure for IoT** - Set `AINGLE_PUBLISH_INTERVAL_MS=0` ## Support diff --git a/templates/ai-agent/Cargo.toml b/templates/ai-agent/Cargo.toml index 396ddf27..76b7af3c 100644 --- a/templates/ai-agent/Cargo.toml +++ b/templates/ai-agent/Cargo.toml @@ -16,4 +16,4 @@ serde_json = "1" [features] default = [] mock = ["adk/mock"] -titans = [] # Enable Titans Memory Layer +ineru = [] # Enable Ineru Memory Layer diff --git a/templates/ai-agent/src/lib.rs b/templates/ai-agent/src/lib.rs index ef4bdb0a..478283e6 100644 --- a/templates/ai-agent/src/lib.rs +++ b/templates/ai-agent/src/lib.rs @@ -1,7 +1,7 @@ //! AI Agent Zome Template //! //! A template for AI-integrated agents on the AIngle Semantic DAG. -//! Supports the Titans Memory Layer for persistent learning. +//! Supports the Ineru Memory Layer for persistent learning. //! //! ## Architecture //! - Memory: Compressed knowledge graphs in DAG @@ -13,15 +13,15 @@ //! # Copy template //! cp -r templates/ai-agent my-agent-zome //! -//! # Build with Titans support -//! cargo build --target wasm32-unknown-unknown --features titans +//! # Build with Ineru support +//! cargo build --target wasm32-unknown-unknown --features ineru //! ``` use adk::prelude::*; use serde::{Deserialize, Serialize}; // ============================================================================ -// Memory Types (Titans-inspired) +// Memory Types (Ineru-inspired) // ============================================================================ /// Short-term memory for recent context