diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e2dcde9..f7802ca 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,25 +24,13 @@ updates: update-types: ["version-update:semver-patch"] # Elixir/Mix - - package-ecosystem: "weeklymix" - directory: "/" + - package-ecosystem: "mix" + directory: "/elixir-orchestration" schedule: - interval: "daily" + interval: "weekly" - # Node.js/npm + # Node.js/npm — VQL playground - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "daily" - - # Python/pip - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "daily" - - # Nix flakes - - package-ecosystem: "nix" - directory: "/" + directory: "/playground" schedule: - interval: "daily" + interval: "weekly" diff --git a/.github/workflows/elixir-ci.yml b/.github/workflows/elixir-ci.yml new file mode 100644 index 0000000..3031b77 --- /dev/null +++ b/.github/workflows/elixir-ci.yml @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MPL-2.0 +# Elixir CI for the orchestration layer: compile, format check, test, audit. +name: elixir-ci + +on: + push: + branches: [main, master] + paths: + - "elixir-orchestration/**" + - ".github/workflows/elixir-ci.yml" + pull_request: + branches: [main, master] + paths: + - "elixir-orchestration/**" + - ".github/workflows/elixir-ci.yml" + workflow_dispatch: + +env: + MIX_ENV: test + +concurrency: + group: elixir-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-test: + name: compile + test + runs-on: ubuntu-latest + defaults: + run: + working-directory: elixir-orchestration + strategy: + matrix: + include: + - elixir: "1.17" + otp: "27" + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + - uses: actions/cache@v4 + with: + path: | + elixir-orchestration/deps + elixir-orchestration/_build + key: ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}-${{ hashFiles('elixir-orchestration/mix.lock') }} + restore-keys: | + ${{ runner.os }}-mix-${{ matrix.elixir }}-${{ matrix.otp }}- + - run: mix deps.get + - run: mix format --check-formatted + - run: mix compile --warnings-as-errors + - run: mix test + + audit: + name: hex audit + runs-on: ubuntu-latest + defaults: + run: + working-directory: elixir-orchestration + steps: + - uses: actions/checkout@v4 + - uses: erlef/setup-beam@v1 + with: + elixir-version: "1.17" + otp-version: "27" + - run: mix deps.get + - run: mix hex.audit + - run: mix deps.unlock --check-unused diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml new file mode 100644 index 0000000..ce066ec --- /dev/null +++ b/.github/workflows/rust-ci.yml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: MPL-2.0 +# Rust CI — test, lint, audit, docs on every push and PR. +# +# Mirrors the .gitlab-ci.yml security/lint/test stages so contributors +# using GitHub get the same checks before merge. +name: rust-ci + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +concurrency: + group: rust-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + fmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all -- --check + + clippy: + name: clippy (all-targets) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --workspace --all-targets --no-deps -- -D warnings + + test: + name: cargo test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo test --workspace --no-fail-fast + + doc: + name: cargo doc + runs-on: ubuntu-latest + env: + RUSTDOCFLAGS: "-D warnings" + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo doc --workspace --no-deps + + audit: + name: cargo audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: rustsec/audit-check@v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + bench-compile: + name: benchmarks compile + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo bench --no-run diff --git a/Cargo.lock b/Cargo.lock index e3206b3..8b98ab7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8723,7 +8723,9 @@ dependencies = [ "verisim-graph", "verisim-normalizer", "verisim-octad", + "verisim-provenance", "verisim-semantic", + "verisim-spatial", "verisim-temporal", "verisim-tensor", "verisim-vector", diff --git a/benches/Cargo.toml b/benches/Cargo.toml index f96033e..1123750 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -26,3 +26,5 @@ verisim-temporal = { path = "../rust-core/verisim-temporal" } verisim-octad = { path = "../rust-core/verisim-octad" } verisim-drift = { path = "../rust-core/verisim-drift" } verisim-normalizer = { path = "../rust-core/verisim-normalizer" } +verisim-provenance = { path = "../rust-core/verisim-provenance" } +verisim-spatial = { path = "../rust-core/verisim-spatial" } diff --git a/benches/modality_benchmarks.rs b/benches/modality_benchmarks.rs index 5f4a59e..40c35a8 100644 --- a/benches/modality_benchmarks.rs +++ b/benches/modality_benchmarks.rs @@ -8,14 +8,14 @@ use tokio::runtime::Runtime; use verisim_document::{Document, DocumentStore, TantivyDocumentStore}; use verisim_drift::{DriftDetector, DriftThresholds, DriftType}; -use verisim_graph::{GraphEdge, GraphNode, GraphObject, GraphStore, OxiGraphStore}; +use verisim_graph::{GraphEdge, GraphNode, GraphObject, GraphStore, SimpleGraphStore}; use verisim_octad::{ - OctadConfig, OctadDocumentInput, OctadId, OctadInput, OctadSnapshot, OctadStore, - OctadVectorInput, OctadSemanticInput, InMemoryOctadStore, -}; -use verisim_semantic::{ - InMemorySemanticStore, ProofBlob, ProofType, SemanticStore, SemanticType, + InMemoryOctadStore, OctadConfig, OctadDocumentInput, OctadInput, OctadSemanticInput, + OctadSnapshot, OctadStore, OctadVectorInput, }; +use verisim_provenance::InMemoryProvenanceStore; +use verisim_semantic::{InMemorySemanticStore, ProofBlob, ProofType, SemanticStore, SemanticType}; +use verisim_spatial::InMemorySpatialStore; use verisim_temporal::{InMemoryVersionStore, TemporalStore}; use verisim_tensor::{InMemoryTensorStore, ReduceOp, Tensor, TensorStore}; use verisim_vector::{DistanceMetric, Embedding, HnswConfig, HnswVectorStore, VectorStore}; @@ -31,8 +31,13 @@ fn bench_document_create(c: &mut Criterion) { group.bench_function("create_document", |b| { let store = TantivyDocumentStore::in_memory().unwrap(); b.to_async(&rt).iter(|| async { - let doc = Document::new("test-id", "Benchmark Title", "Benchmark body content for testing indexing performance."); - black_box(store.index(&doc).await.unwrap()) + let doc = Document::new( + "test-id", + "Benchmark Title", + "Benchmark body content for testing indexing performance.", + ); + store.index(&doc).await.unwrap(); + black_box(()) }); }); @@ -60,9 +65,8 @@ fn bench_document_search(c: &mut Criterion) { group.throughput(Throughput::Elements(1000)); group.bench_function("search_text", |b| { - b.to_async(&rt).iter(|| async { - black_box(store.search("machine learning", 10).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(store.search("machine learning", 10).await.unwrap()) }); }); group.finish(); @@ -90,7 +94,8 @@ fn bench_vector_insert(c: &mut Criterion) { }; let store_ref = &store; async move { - black_box(store_ref.upsert(&embedding).await.unwrap()) + store_ref.upsert(&embedding).await.unwrap(); + black_box(()) } }); }); @@ -123,9 +128,8 @@ fn bench_vector_search(c: &mut Criterion) { let query = vec![0.5f32; dim]; - b.to_async(&rt).iter(|| async { - black_box(store.search(&query, 10).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(store.search(&query, 10).await.unwrap()) }); }); } @@ -142,7 +146,7 @@ fn bench_graph_operations(c: &mut Criterion) { let mut group = c.benchmark_group("graph"); group.bench_function("insert_edge", |b| { - let store = OxiGraphStore::in_memory().unwrap(); + let store = SimpleGraphStore::in_memory().unwrap(); let mut counter = 0u64; b.to_async(&rt).iter(|| { @@ -150,33 +154,39 @@ fn bench_graph_operations(c: &mut Criterion) { let edge = GraphEdge { subject: GraphNode::new(format!("https://example.org/node/{}", counter)), predicate: GraphNode::new("https://example.org/relates_to"), - object: GraphObject::Node(GraphNode::new(format!("https://example.org/target/{}", counter))), + object: GraphObject::Node(GraphNode::new(format!( + "https://example.org/target/{}", + counter + ))), }; let store_ref = &store; async move { - black_box(store_ref.insert(&edge).await.unwrap()) + store_ref.insert(&edge).await.unwrap(); + black_box(()) } }); }); // Pre-populate for query benchmark - let query_store = OxiGraphStore::in_memory().unwrap(); + let query_store = SimpleGraphStore::in_memory().unwrap(); let query_node = GraphNode::new("https://example.org/hub"); rt.block_on(async { for i in 0..100 { let edge = GraphEdge { subject: query_node.clone(), predicate: GraphNode::new("https://example.org/connects"), - object: GraphObject::Node(GraphNode::new(format!("https://example.org/target/{}", i))), + object: GraphObject::Node(GraphNode::new(format!( + "https://example.org/target/{}", + i + ))), }; query_store.insert(&edge).await.unwrap(); } }); group.bench_function("query_outgoing", |b| { - b.to_async(&rt).iter(|| async { - black_box(query_store.outgoing(&query_node).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(query_store.outgoing(&query_node).await.unwrap()) }); }); group.finish(); @@ -190,12 +200,19 @@ fn bench_octad_operations(c: &mut Criterion) { let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("octad"); - let graph_store = Arc::new(OxiGraphStore::in_memory().unwrap()); - let vector_store = Arc::new(HnswVectorStore::new(384, DistanceMetric::Cosine, HnswConfig::default())); + let graph_store = Arc::new(SimpleGraphStore::in_memory().unwrap()); + let vector_store = Arc::new(HnswVectorStore::new( + 384, + DistanceMetric::Cosine, + HnswConfig::default(), + )); let document_store = Arc::new(TantivyDocumentStore::in_memory().unwrap()); let tensor_store = Arc::new(InMemoryTensorStore::new()); let semantic_store = Arc::new(InMemorySemanticStore::new()); - let temporal_store: Arc> = Arc::new(InMemoryVersionStore::new()); + let temporal_store: Arc> = + Arc::new(InMemoryVersionStore::new()); + let provenance_store = Arc::new(InMemoryProvenanceStore::new()); + let spatial_store = Arc::new(InMemorySpatialStore::new()); let config = OctadConfig::default(); @@ -207,6 +224,8 @@ fn bench_octad_operations(c: &mut Criterion) { tensor_store, semantic_store, temporal_store, + provenance_store, + spatial_store, ); group.bench_function("create_octad", |b| { @@ -250,9 +269,8 @@ fn bench_octad_operations(c: &mut Criterion) { group.bench_function("get_octad", |b| { let id = octad_ids[0].clone(); - b.to_async(&rt).iter(|| async { - black_box(store.get(&id).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(store.get(&id).await.unwrap()) }); }); group.finish(); @@ -278,15 +296,13 @@ fn bench_drift_detection(c: &mut Criterion) { vec!["entity-bench".to_string()], ) .await - .unwrap() + .unwrap(), ) }); }); group.bench_function("health_check", |b| { - b.iter(|| { - black_box(detector.health_check().unwrap()) - }); + b.iter(|| black_box(detector.health_check().unwrap())); }); group.finish(); @@ -300,12 +316,19 @@ fn bench_cross_modal_query(c: &mut Criterion) { let rt = Runtime::new().unwrap(); let mut group = c.benchmark_group("cross_modal"); - let graph_store = Arc::new(OxiGraphStore::in_memory().unwrap()); - let vector_store = Arc::new(HnswVectorStore::new(384, DistanceMetric::Cosine, HnswConfig::default())); + let graph_store = Arc::new(SimpleGraphStore::in_memory().unwrap()); + let vector_store = Arc::new(HnswVectorStore::new( + 384, + DistanceMetric::Cosine, + HnswConfig::default(), + )); let document_store = Arc::new(TantivyDocumentStore::in_memory().unwrap()); let tensor_store = Arc::new(InMemoryTensorStore::new()); let semantic_store = Arc::new(InMemorySemanticStore::new()); - let temporal_store: Arc> = Arc::new(InMemoryVersionStore::new()); + let temporal_store: Arc> = + Arc::new(InMemoryVersionStore::new()); + let provenance_store = Arc::new(InMemoryProvenanceStore::new()); + let spatial_store = Arc::new(InMemorySpatialStore::new()); let config = OctadConfig::default(); @@ -317,6 +340,8 @@ fn bench_cross_modal_query(c: &mut Criterion) { tensor_store, semantic_store, temporal_store, + provenance_store, + spatial_store, ); // Create 1000 octads with multiple modalities @@ -349,15 +374,13 @@ fn bench_cross_modal_query(c: &mut Criterion) { group.bench_function("vector_similarity_search", |b| { let query = vec![0.5f32; 384]; - b.to_async(&rt).iter(|| async { - black_box(store.search_similar(&query, 10).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(store.search_similar(&query, 10).await.unwrap()) }); }); group.bench_function("fulltext_search", |b| { - b.to_async(&rt).iter(|| async { - black_box(store.search_text("machine learning", 10).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(store.search_text("machine learning", 10).await.unwrap()) }); }); group.finish(); @@ -376,7 +399,8 @@ fn bench_tensor_operations(c: &mut Criterion) { b.to_async(&rt).iter(|| async { let data: Vec = (0..4096).map(|i| (i as f64) * 0.001).collect(); let tensor = Tensor::new("bench-tensor", vec![64, 64], data).unwrap(); - black_box(store.put(&tensor).await.unwrap()) + store.put(&tensor).await.unwrap(); + black_box(()) }); }); @@ -391,9 +415,8 @@ fn bench_tensor_operations(c: &mut Criterion) { }); group.bench_function("store_get", |b| { - b.to_async(&rt).iter(|| async { - black_box(get_store.get("tensor-50").await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(get_store.get("tensor-50").await.unwrap()) }); }); // Reduce benchmark: sum along axis 0 of a 64x64 tensor @@ -406,7 +429,12 @@ fn bench_tensor_operations(c: &mut Criterion) { group.bench_function("reduce_sum_axis0", |b| { b.to_async(&rt).iter(|| async { - black_box(reduce_store.reduce("reduce-tensor", 0, ReduceOp::Sum).await.unwrap()) + black_box( + reduce_store + .reduce("reduce-tensor", 0, ReduceOp::Sum) + .await + .unwrap(), + ) }); }); @@ -430,7 +458,8 @@ fn bench_semantic_operations(c: &mut Criterion) { let typ = SemanticType::new(&iri, "BenchType"); let store_ref = &store; async move { - black_box(store_ref.register_type(&typ).await.unwrap()) + store_ref.register_type(&typ).await.unwrap(); + black_box(()) } }); }); @@ -449,7 +478,12 @@ fn bench_semantic_operations(c: &mut Criterion) { group.bench_function("get_type", |b| { b.to_async(&rt).iter(|| async { - black_box(type_store.get_type("https://example.org/Type50").await.unwrap()) + black_box( + type_store + .get_type("https://example.org/Type50") + .await + .unwrap(), + ) }); }); @@ -463,7 +497,7 @@ fn bench_semantic_operations(c: &mut Criterion) { vec![1, 2, 3, 4, 5, 6, 7, 8], ); let cbor = black_box(proof.to_cbor().unwrap()); - black_box(store.store_proof(&proof).await.unwrap()); + store.store_proof(&proof).await.unwrap(); cbor }); }); @@ -483,7 +517,12 @@ fn bench_semantic_operations(c: &mut Criterion) { group.bench_function("proof_verify", |b| { b.to_async(&rt).iter(|| async { - black_box(verify_store.verify_proofs("entity:verify-bench is-a Document").await.unwrap()) + black_box( + verify_store + .verify_proofs("entity:verify-bench is-a Document") + .await + .unwrap(), + ) }); }); @@ -507,7 +546,12 @@ fn bench_temporal_operations(c: &mut Criterion) { let data = format!("version data {}", counter); let store_ref = &store; async move { - black_box(store_ref.append(&entity, data, "bench-author", Some("bench commit")).await.unwrap()) + black_box( + store_ref + .append(&entity, data, "bench-author", Some("bench commit")) + .await + .unwrap(), + ) } }); }); @@ -517,7 +561,12 @@ fn bench_temporal_operations(c: &mut Criterion) { rt.block_on(async { for v in 0..100 { version_store - .append("bench-entity", format!("data v{}", v), "bench-author", Some(&format!("commit {}", v))) + .append( + "bench-entity", + format!("data v{}", v), + "bench-author", + Some(&format!("commit {}", v)), + ) .await .unwrap(); } @@ -530,15 +579,13 @@ fn bench_temporal_operations(c: &mut Criterion) { }); group.bench_function("version_get_latest", |b| { - b.to_async(&rt).iter(|| async { - black_box(version_store.latest("bench-entity").await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(version_store.latest("bench-entity").await.unwrap()) }); }); group.bench_function("history_10", |b| { - b.to_async(&rt).iter(|| async { - black_box(version_store.history("bench-entity", 10).await.unwrap()) - }); + b.to_async(&rt) + .iter(|| async { black_box(version_store.history("bench-entity", 10).await.unwrap()) }); }); group.bench_function("history_100", |b| { @@ -560,46 +607,21 @@ criterion_group!( bench_document_search ); -criterion_group!( - vector_benches, - bench_vector_insert, - bench_vector_search -); +criterion_group!(vector_benches, bench_vector_insert, bench_vector_search); -criterion_group!( - graph_benches, - bench_graph_operations -); +criterion_group!(graph_benches, bench_graph_operations); -criterion_group!( - octad_benches, - bench_octad_operations -); +criterion_group!(octad_benches, bench_octad_operations); -criterion_group!( - drift_benches, - bench_drift_detection -); +criterion_group!(drift_benches, bench_drift_detection); -criterion_group!( - cross_modal_benches, - bench_cross_modal_query -); +criterion_group!(cross_modal_benches, bench_cross_modal_query); -criterion_group!( - tensor_benches, - bench_tensor_operations -); +criterion_group!(tensor_benches, bench_tensor_operations); -criterion_group!( - semantic_benches, - bench_semantic_operations -); +criterion_group!(semantic_benches, bench_semantic_operations); -criterion_group!( - temporal_benches, - bench_temporal_operations -); +criterion_group!(temporal_benches, bench_temporal_operations); criterion_main!( document_benches, diff --git a/elixir-orchestration/config/dev.exs b/elixir-orchestration/config/dev.exs index 8fa0c31..af7b583 100644 --- a/elixir-orchestration/config/dev.exs +++ b/elixir-orchestration/config/dev.exs @@ -6,5 +6,4 @@ import Config config :verisim, rust_core_url: "http://localhost:8080/api/v1" -config :logger, :console, - level: :debug +config :logger, :console, level: :debug diff --git a/elixir-orchestration/config/prod.exs b/elixir-orchestration/config/prod.exs index c221061..25cfe8c 100644 --- a/elixir-orchestration/config/prod.exs +++ b/elixir-orchestration/config/prod.exs @@ -7,5 +7,4 @@ config :verisim, rust_core_url: System.get_env("VERISIM_RUST_CORE_URL") || "https://verisim-core:8080/api/v1", rust_core_timeout: 60_000 -config :logger, :console, - level: :info +config :logger, :console, level: :info diff --git a/elixir-orchestration/config/test.exs b/elixir-orchestration/config/test.exs index e414a87..1a4cd85 100644 --- a/elixir-orchestration/config/test.exs +++ b/elixir-orchestration/config/test.exs @@ -8,5 +8,4 @@ config :verisim, rust_core_timeout: 5_000, orch_api_port: 0 -config :logger, :console, - level: :warning +config :logger, :console, level: :warning diff --git a/elixir-orchestration/lib/verisim/api/router.ex b/elixir-orchestration/lib/verisim/api/router.ex index 173284d..c46b501 100644 --- a/elixir-orchestration/lib/verisim/api/router.ex +++ b/elixir-orchestration/lib/verisim/api/router.ex @@ -34,10 +34,10 @@ defmodule VeriSim.Api.Router do use Plug.Router - plug :match - plug Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason - plug :set_cors_headers - plug :dispatch + plug(:match) + plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason) + plug(:set_cors_headers) + plug(:dispatch) # ── Health ──────────────────────────────────────────────────────────── @@ -58,6 +58,7 @@ defmodule VeriSim.Api.Router do get "/telemetry" do if VeriSim.Telemetry.Collector.enabled?() do json_string = VeriSim.Telemetry.Reporter.report_json() + conn |> put_resp_content_type("application/json") |> send_resp(200, json_string) @@ -65,7 +66,8 @@ defmodule VeriSim.Api.Router do json_response(conn, 200, %{ telemetry_enabled: false, message: "Telemetry collection is disabled. Enable with VERISIM_TELEMETRY=true.", - privacy_notice: "When enabled, only aggregate counters are collected. No PII, no query content." + privacy_notice: + "When enabled, only aggregate counters are collected. No PII, no query content." }) end end diff --git a/elixir-orchestration/lib/verisim/application.ex b/elixir-orchestration/lib/verisim/application.ex index 41adde1..5542b04 100644 --- a/elixir-orchestration/lib/verisim/application.ex +++ b/elixir-orchestration/lib/verisim/application.ex @@ -20,10 +20,7 @@ defmodule VeriSim.Application do # Dynamic supervisor for entity servers {DynamicSupervisor, - name: VeriSim.EntitySupervisor, - strategy: :one_for_one, - max_restarts: 100, - max_seconds: 60}, + name: VeriSim.EntitySupervisor, strategy: :one_for_one, max_restarts: 100, max_seconds: 60}, # Drift monitor VeriSim.DriftMonitor, @@ -39,8 +36,8 @@ defmodule VeriSim.Application do # KRaft consensus node (single-node bootstrap by default) {VeriSim.Consensus.KRaftNode, - node_id: Application.get_env(:verisim, :kraft_node_id, "local"), - peers: Application.get_env(:verisim, :kraft_peers, [])}, + node_id: Application.get_env(:verisim, :kraft_node_id, "local"), + peers: Application.get_env(:verisim, :kraft_peers, [])}, # Federation resolver VeriSim.Federation.Resolver, diff --git a/elixir-orchestration/lib/verisim/consensus/kraft_node.ex b/elixir-orchestration/lib/verisim/consensus/kraft_node.ex index 2909650..a42214c 100644 --- a/elixir-orchestration/lib/verisim/consensus/kraft_node.ex +++ b/elixir-orchestration/lib/verisim/consensus/kraft_node.ex @@ -301,9 +301,7 @@ defmodule VeriSim.Consensus.KRaftNode do defp start_election(state) do new_term = state.current_term + 1 - Logger.info( - "KRaft: node #{state.node_id} starting election for term #{new_term}" - ) + Logger.info("KRaft: node #{state.node_id} starting election for term #{new_term}") state = %{ state @@ -404,9 +402,7 @@ defmodule VeriSim.Consensus.KRaftNode do end defp become_leader(state) do - Logger.info( - "KRaft: node #{state.node_id} became leader for term #{state.current_term}" - ) + Logger.info("KRaft: node #{state.node_id} became leader for term #{state.current_term}") last_log_index = length(state.log) + 1 @@ -624,7 +620,12 @@ defmodule VeriSim.Consensus.KRaftNode do "(#{state.last_applied + 1}..#{state.commit_index})" ) - state = %{state | last_applied: state.commit_index, registry: new_registry, peers: new_peers} + state = %{ + state + | last_applied: state.commit_index, + registry: new_registry, + peers: new_peers + } # Update leader tracking structures when peers change state = @@ -641,7 +642,8 @@ defmodule VeriSim.Consensus.KRaftNode do end) # Remove entries for removed peers - removed = MapSet.difference(MapSet.new(Map.keys(state.next_index)), MapSet.new(new_peers)) + removed = + MapSet.difference(MapSet.new(Map.keys(state.next_index)), MapSet.new(new_peers)) new_next_index = Map.drop(new_next_index, MapSet.to_list(removed)) new_match_index = Map.drop(new_match_index, MapSet.to_list(removed)) @@ -692,9 +694,7 @@ defmodule VeriSim.Consensus.KRaftNode do last_entry.term ) - Logger.info( - "KRaft: node #{state.node_id} saved snapshot at index #{state.last_applied}" - ) + Logger.info("KRaft: node #{state.node_id} saved snapshot at index #{state.last_applied}") end end diff --git a/elixir-orchestration/lib/verisim/consensus/kraft_transport.ex b/elixir-orchestration/lib/verisim/consensus/kraft_transport.ex index c72f9ae..8f7f903 100644 --- a/elixir-orchestration/lib/verisim/consensus/kraft_transport.ex +++ b/elixir-orchestration/lib/verisim/consensus/kraft_transport.ex @@ -116,9 +116,7 @@ defmodule VeriSim.Consensus.KRaftTransport do send(reply_to, {:append_entries_response, peer_id, response}) {:error, reason} -> - Logger.debug( - "KRaft transport: append_entries to #{peer_id} failed: #{inspect(reason)}" - ) + Logger.debug("KRaft transport: append_entries to #{peer_id} failed: #{inspect(reason)}") end end) end diff --git a/elixir-orchestration/lib/verisim/consensus/kraft_wal.ex b/elixir-orchestration/lib/verisim/consensus/kraft_wal.ex index 83afad6..e43fa18 100644 --- a/elixir-orchestration/lib/verisim/consensus/kraft_wal.ex +++ b/elixir-orchestration/lib/verisim/consensus/kraft_wal.ex @@ -54,6 +54,7 @@ defmodule VeriSim.Consensus.KRaftWAL do {:error, reason} -> {:error, {:wal_init_failed, reason}} end end + def init(nil), do: :ok @doc """ @@ -74,11 +75,13 @@ defmodule VeriSim.Consensus.KRaftWAL do case File.write(tmp_path, Jason.encode!(state)) do :ok -> File.rename(tmp_path, path) + {:error, reason} -> Logger.error("KRaft WAL: failed to persist state: #{inspect(reason)}") {:error, reason} end end + def persist_state(nil, _term, _voted_for), do: :ok @doc """ @@ -89,12 +92,15 @@ defmodule VeriSim.Consensus.KRaftWAL do json_line = Jason.encode!(serialize_entry(entry)) <> "\n" case File.write(path, json_line, [:append, :sync]) do - :ok -> :ok + :ok -> + :ok + {:error, reason} -> Logger.error("KRaft WAL: failed to append entry: #{inspect(reason)}") {:error, reason} end end + def append_entry(nil, _entry), do: :ok @doc """ @@ -105,18 +111,23 @@ defmodule VeriSim.Consensus.KRaftWAL do :ok else path = Path.join(wal_path, @wal_file) - lines = Enum.map_join(entries, fn entry -> - Jason.encode!(serialize_entry(entry)) <> "\n" - end) + + lines = + Enum.map_join(entries, fn entry -> + Jason.encode!(serialize_entry(entry)) <> "\n" + end) case File.write(path, lines, [:append, :sync]) do - :ok -> :ok + :ok -> + :ok + {:error, reason} -> Logger.error("KRaft WAL: failed to append entries: #{inspect(reason)}") {:error, reason} end end end + def append_entries(nil, _entries), do: :ok @doc """ @@ -124,7 +135,8 @@ defmodule VeriSim.Consensus.KRaftWAL do After saving, truncates the WAL to remove entries at or before the snapshot index. """ - def save_snapshot(wal_path, registry, last_included_index, last_included_term) when is_binary(wal_path) do + def save_snapshot(wal_path, registry, last_included_index, last_included_term) + when is_binary(wal_path) do snapshot = %{ "registry" => serialize_registry(registry), "last_included_index" => last_included_index, @@ -140,11 +152,13 @@ defmodule VeriSim.Consensus.KRaftWAL do File.rename(tmp_path, path) # Truncate WAL to only contain entries after the snapshot truncate_wal(wal_path, last_included_index) + {:error, reason} -> Logger.error("KRaft WAL: failed to save snapshot: #{inspect(reason)}") {:error, reason} end end + def save_snapshot(nil, _registry, _index, _term), do: :ok @doc """ @@ -166,18 +180,20 @@ defmodule VeriSim.Consensus.KRaftWAL do {registry, snap_index, snap_term} = recover_snapshot(wal_path) log = recover_log(wal_path, snap_index) - {:ok, %{ - current_term: state[:current_term] || 0, - voted_for: state[:voted_for], - log: log, - registry: registry, - snapshot_index: snap_index, - snapshot_term: snap_term - }} + {:ok, + %{ + current_term: state[:current_term] || 0, + voted_for: state[:voted_for], + log: log, + registry: registry, + snapshot_index: snap_index, + snapshot_term: snap_term + }} else {:ok, nil} end end + def recover(nil), do: {:ok, nil} # --------------------------------------------------------------------------- @@ -192,9 +208,11 @@ defmodule VeriSim.Consensus.KRaftWAL do case Jason.decode(data) do {:ok, %{"current_term" => term, "voted_for" => voted_for}} -> %{current_term: term, voted_for: voted_for} + _ -> %{} end + {:error, _} -> %{} end @@ -208,9 +226,11 @@ defmodule VeriSim.Consensus.KRaftWAL do case Jason.decode(data) do {:ok, %{"registry" => reg, "last_included_index" => idx, "last_included_term" => term}} -> {deserialize_registry(reg), idx, term} + _ -> {nil, 0, 0} end + {:error, _} -> {nil, 0, 0} end @@ -228,11 +248,13 @@ defmodule VeriSim.Consensus.KRaftWAL do {:ok, entry_map} -> entry = deserialize_entry(entry_map) if entry.index > after_index, do: [entry], else: [] + _ -> Logger.warning("KRaft WAL: skipping corrupt line: #{String.slice(line, 0, 100)}") [] end end) + {:error, _} -> [] end @@ -250,7 +272,8 @@ defmodule VeriSim.Consensus.KRaftWAL do case File.read(path) do {:ok, data} -> - remaining = data + remaining = + data |> String.split("\n", trim: true) |> Enum.filter(fn line -> case Jason.decode(line) do @@ -263,9 +286,11 @@ defmodule VeriSim.Consensus.KRaftWAL do remaining = if remaining != "", do: remaining <> "\n", else: "" File.write(path, remaining, [:sync]) - {:error, _} -> :ok + {:error, _} -> + :ok end end + def truncate_after(nil, _index), do: :ok # --------------------------------------------------------------------------- @@ -277,7 +302,8 @@ defmodule VeriSim.Consensus.KRaftWAL do case File.read(path) do {:ok, data} -> - remaining = data + remaining = + data |> String.split("\n", trim: true) |> Enum.filter(fn line -> case Jason.decode(line) do @@ -290,7 +316,8 @@ defmodule VeriSim.Consensus.KRaftWAL do remaining = if remaining != "", do: remaining <> "\n", else: "" File.write(path, remaining) - {:error, _} -> :ok + {:error, _} -> + :ok end end @@ -308,24 +335,32 @@ defmodule VeriSim.Consensus.KRaftWAL do end defp serialize_command(:noop), do: %{"type" => "noop"} + defp serialize_command({:register_store, store_id, endpoint, modalities}) do - %{"type" => "register_store", "store_id" => store_id, - "endpoint" => endpoint, "modalities" => modalities} + %{ + "type" => "register_store", + "store_id" => store_id, + "endpoint" => endpoint, + "modalities" => modalities + } end + defp serialize_command({:unregister_store, store_id}) do %{"type" => "unregister_store", "store_id" => store_id} end + defp serialize_command({:map_octad, octad_id, locations}) do - %{"type" => "map_octad", "octad_id" => octad_id, - "locations" => locations} + %{"type" => "map_octad", "octad_id" => octad_id, "locations" => locations} end + defp serialize_command({:unmap_octad, octad_id}) do %{"type" => "unmap_octad", "octad_id" => octad_id} end + defp serialize_command({:update_trust, store_id, new_trust}) do - %{"type" => "update_trust", "store_id" => store_id, - "new_trust" => new_trust} + %{"type" => "update_trust", "store_id" => store_id, "new_trust" => new_trust} end + defp serialize_command(other) do %{"type" => "unknown", "data" => inspect(other)} end @@ -340,65 +375,80 @@ defmodule VeriSim.Consensus.KRaftWAL do end defp deserialize_command(%{"type" => "noop"}), do: :noop + defp deserialize_command(%{"type" => "register_store"} = cmd) do {:register_store, cmd["store_id"], cmd["endpoint"], cmd["modalities"]} end + defp deserialize_command(%{"type" => "unregister_store"} = cmd) do {:unregister_store, cmd["store_id"]} end + defp deserialize_command(%{"type" => "map_octad"} = cmd) do {:map_octad, cmd["octad_id"], cmd["locations"]} end + defp deserialize_command(%{"type" => "unmap_octad"} = cmd) do {:unmap_octad, cmd["octad_id"]} end + defp deserialize_command(%{"type" => "update_trust"} = cmd) do {:update_trust, cmd["store_id"], cmd["new_trust"]} end + defp deserialize_command(_), do: :noop defp serialize_registry(registry) do %{ - "stores" => Map.new(registry[:stores] || %{}, fn {k, v} -> - {k, %{ - "store_id" => v[:store_id] || k, - "endpoint" => v[:endpoint], - "modalities" => v[:modalities], - "trust_level" => v[:trust_level] - }} - end), - "mappings" => Map.new(registry[:mappings] || %{}, fn {k, v} -> - {k, %{ - "octad_id" => v[:octad_id] || k, - "locations" => v[:locations], - "primary_store" => v[:primary_store] - }} - end) + "stores" => + Map.new(registry[:stores] || %{}, fn {k, v} -> + {k, + %{ + "store_id" => v[:store_id] || k, + "endpoint" => v[:endpoint], + "modalities" => v[:modalities], + "trust_level" => v[:trust_level] + }} + end), + "mappings" => + Map.new(registry[:mappings] || %{}, fn {k, v} -> + {k, + %{ + "octad_id" => v[:octad_id] || k, + "locations" => v[:locations], + "primary_store" => v[:primary_store] + }} + end) } end defp deserialize_registry(nil), do: nil + defp deserialize_registry(map) do %{ - stores: Map.new(map["stores"] || %{}, fn {k, v} -> - {k, %{ - store_id: v["store_id"] || k, - endpoint: v["endpoint"], - modalities: v["modalities"] || [], - trust_level: v["trust_level"] || 1.0, - last_seen: nil, - response_time_ms: nil - }} - end), - mappings: Map.new(map["mappings"] || %{}, fn {k, v} -> - {k, %{ - octad_id: v["octad_id"] || k, - locations: v["locations"], - primary_store: v["primary_store"], - created: nil, - modified: nil - }} - end), + stores: + Map.new(map["stores"] || %{}, fn {k, v} -> + {k, + %{ + store_id: v["store_id"] || k, + endpoint: v["endpoint"], + modalities: v["modalities"] || [], + trust_level: v["trust_level"] || 1.0, + last_seen: nil, + response_time_ms: nil + }} + end), + mappings: + Map.new(map["mappings"] || %{}, fn {k, v} -> + {k, + %{ + octad_id: v["octad_id"] || k, + locations: v["locations"], + primary_store: v["primary_store"], + created: nil, + modified: nil + }} + end), config: %{ min_trust_level: 0.5, max_store_downtime_ms: 300_000, diff --git a/elixir-orchestration/lib/verisim/drift/drift_monitor.ex b/elixir-orchestration/lib/verisim/drift/drift_monitor.ex index c23f3b2..63cd867 100644 --- a/elixir-orchestration/lib/verisim/drift/drift_monitor.ex +++ b/elixir-orchestration/lib/verisim/drift/drift_monitor.ex @@ -135,10 +135,7 @@ defmodule VeriSim.DriftMonitor do new_drift_scores = Map.update(state.drift_scores, drift_type, [score], &[score | Enum.take(&1, 99)]) - new_state = %{state | - entity_drift: new_entity_drift, - drift_scores: new_drift_scores - } + new_state = %{state | entity_drift: new_entity_drift, drift_scores: new_drift_scores} # Check if normalization is needed new_state = maybe_trigger_normalization(new_state, entity_id, score, drift_type) @@ -168,6 +165,7 @@ defmodule VeriSim.DriftMonitor do pending_normalizations: MapSet.size(state.pending_normalizations), last_sweep: state.last_sweep } + {:reply, status, state} end @@ -197,10 +195,7 @@ defmodule VeriSim.DriftMonitor do state.entity_drift end - {:noreply, %{state | - pending_normalizations: new_pending, - entity_drift: new_entity_drift - }} + {:noreply, %{state | pending_normalizations: new_pending, entity_drift: new_entity_drift}} end # Private Functions @@ -213,7 +208,8 @@ defmodule VeriSim.DriftMonitor do Logger.debug("Performing drift sweep across #{map_size(state.entity_drift)} entities") # Check each entity that has reported drift - new_state = state.entity_drift + new_state = + state.entity_drift |> Enum.reduce(state, fn {entity_id, drift_scores}, acc -> # Check each drift type for this entity Enum.reduce(drift_scores, acc, fn {drift_type, score}, inner_acc -> @@ -230,12 +226,24 @@ defmodule VeriSim.DriftMonitor do Enum.reduce(rust_metrics, new_state.drift_scores, fn metric, acc -> drift_type = case metric["drift_type"] do - "SemanticVectorDrift" -> :semantic_vector - "GraphDocumentDrift" -> :graph_document - "TemporalConsistencyDrift" -> :temporal_consistency - "TensorDrift" -> :tensor - "SchemaDrift" -> :schema - "QualityDrift" -> :quality + "SemanticVectorDrift" -> + :semantic_vector + + "GraphDocumentDrift" -> + :graph_document + + "TemporalConsistencyDrift" -> + :temporal_consistency + + "TensorDrift" -> + :tensor + + "SchemaDrift" -> + :schema + + "QualityDrift" -> + :quality + other -> Logger.debug("Unknown drift type from Rust core: #{inspect(other)}") nil @@ -257,7 +265,10 @@ defmodule VeriSim.DriftMonitor do new_state {:error, reason} -> - Logger.warning("DriftMonitor: failed to fetch Rust core drift status: #{inspect(reason)}") + Logger.warning( + "DriftMonitor: failed to fetch Rust core drift status: #{inspect(reason)}" + ) + new_state end @@ -285,10 +296,12 @@ defmodule VeriSim.DriftMonitor do if MapSet.size(state.pending_normalizations) < state.config.max_concurrent_normalizations do # Start async normalization Task.start(fn -> - result = case EntityServer.normalize(entity_id) do - :ok -> :success - _ -> :failure - end + result = + case EntityServer.normalize(entity_id) do + :ok -> :success + _ -> :failure + end + send(__MODULE__, {:normalization_complete, entity_id, result}) end) diff --git a/elixir-orchestration/lib/verisim/entity/entity_server.ex b/elixir-orchestration/lib/verisim/entity/entity_server.ex index 4324e3e..551573b 100644 --- a/elixir-orchestration/lib/verisim/entity/entity_server.ex +++ b/elixir-orchestration/lib/verisim/entity/entity_server.ex @@ -183,7 +183,9 @@ defmodule VeriSim.EntityServer do Logger.warning("High drift detected for #{state.id}: #{score}") DriftMonitor.report_drift(state.id, score) end + %{state | drift_score: score} + {:error, _} -> state end @@ -196,9 +198,7 @@ defmodule VeriSim.EntityServer do def handle_info({:normalization_complete, result, modalities}, state) do new_status = if result == :success, do: :active, else: :stale - Logger.info( - "Normalization #{result} for #{state.id}, modalities: #{inspect(modalities)}" - ) + Logger.info("Normalization #{result} for #{state.id}, modalities: #{inspect(modalities)}") new_state = %{state | status: new_status} @@ -230,8 +230,10 @@ defmodule VeriSim.EntityServer do Enum.reduce(changes, state, fn {:modality, modality, value}, acc -> put_in(acc, [:modalities, modality], value) + {key, value}, acc when is_atom(key) -> Map.put(acc, key, value) + _, acc -> acc end) diff --git a/elixir-orchestration/lib/verisim/federation/adapters/arangodb.ex b/elixir-orchestration/lib/verisim/federation/adapters/arangodb.ex index c86684d..d345e68 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/arangodb.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/arangodb.ex @@ -84,9 +84,7 @@ defmodule VeriSim.Federation.Adapters.ArangoDB do end rescue e -> - Logger.warning( - "ArangoDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("ArangoDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end diff --git a/elixir-orchestration/lib/verisim/federation/adapters/duckdb.ex b/elixir-orchestration/lib/verisim/federation/adapters/duckdb.ex index 4799c09..65a1075 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/duckdb.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/duckdb.ex @@ -86,9 +86,7 @@ defmodule VeriSim.Federation.Adapters.DuckDB do end rescue e -> - Logger.warning( - "DuckDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("DuckDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end @@ -202,13 +200,14 @@ defmodule VeriSim.Federation.Adapters.DuckDB do LIMIT $5 """ - {sql, %{ - "$1" => bounds[:min_lon] || bounds["min_lon"] || 0.0, - "$2" => bounds[:min_lat] || bounds["min_lat"] || 0.0, - "$3" => bounds[:max_lon] || bounds["max_lon"] || 0.0, - "$4" => bounds[:max_lat] || bounds["max_lat"] || 0.0, - "$5" => limit - }} + {sql, + %{ + "$1" => bounds[:min_lon] || bounds["min_lon"] || 0.0, + "$2" => bounds[:min_lat] || bounds["min_lat"] || 0.0, + "$3" => bounds[:max_lon] || bounds["max_lon"] || 0.0, + "$4" => bounds[:max_lat] || bounds["max_lat"] || 0.0, + "$5" => limit + }} :graph in modalities && Map.has_key?(query_params, :graph_pattern) -> # Recursive CTE for graph traversal @@ -247,11 +246,12 @@ defmodule VeriSim.Federation.Adapters.DuckDB do LIMIT $3 """ - {sql, %{ - "$1" => range[:start] || range["start"] || "", - "$2" => range[:end] || range["end"] || "", - "$3" => limit - }} + {sql, + %{ + "$1" => range[:start] || range["start"] || "", + "$2" => range[:end] || range["end"] || "", + "$3" => limit + }} :tensor in modalities && Map.has_key?(query_params, :vector_query) -> # DuckDB array operations for tensor similarity diff --git a/elixir-orchestration/lib/verisim/federation/adapters/elasticsearch.ex b/elixir-orchestration/lib/verisim/federation/adapters/elasticsearch.ex index de35714..bae3452 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/elasticsearch.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/elasticsearch.ex @@ -292,9 +292,7 @@ defmodule VeriSim.Federation.Adapters.Elasticsearch do error_type = get_in(body, ["error", "type"]) || "unknown" error_reason = get_in(body, ["error", "reason"]) || "HTTP #{status}" - Logger.warning( - "Elasticsearch adapter: search failed: #{error_type} — #{error_reason}" - ) + Logger.warning("Elasticsearch adapter: search failed: #{error_type} — #{error_reason}") {:error, {:es_error, status, error_type, error_reason}} diff --git a/elixir-orchestration/lib/verisim/federation/adapters/influxdb.ex b/elixir-orchestration/lib/verisim/federation/adapters/influxdb.ex index ccde85c..6224561 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/influxdb.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/influxdb.ex @@ -96,9 +96,7 @@ defmodule VeriSim.Federation.Adapters.InfluxDB do end rescue e -> - Logger.warning( - "InfluxDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("InfluxDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end diff --git a/elixir-orchestration/lib/verisim/federation/adapters/mongodb.ex b/elixir-orchestration/lib/verisim/federation/adapters/mongodb.ex index c556d23..68cebbc 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/mongodb.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/mongodb.ex @@ -93,9 +93,7 @@ defmodule VeriSim.Federation.Adapters.MongoDB do end rescue e -> - Logger.warning( - "MongoDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("MongoDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end diff --git a/elixir-orchestration/lib/verisim/federation/adapters/neo4j.ex b/elixir-orchestration/lib/verisim/federation/adapters/neo4j.ex index ce7b473..cc75b58 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/neo4j.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/neo4j.ex @@ -89,9 +89,7 @@ defmodule VeriSim.Federation.Adapters.Neo4j do end rescue e -> - Logger.warning( - "Neo4j adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("Neo4j adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end diff --git a/elixir-orchestration/lib/verisim/federation/adapters/postgresql.ex b/elixir-orchestration/lib/verisim/federation/adapters/postgresql.ex index 0160796..03982ff 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/postgresql.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/postgresql.ex @@ -210,13 +210,14 @@ defmodule VeriSim.Federation.Adapters.PostgreSQL do LIMIT $5 """ - {sql, %{ - "$1" => bounds[:min_lon] || bounds["min_lon"] || 0.0, - "$2" => bounds[:min_lat] || bounds["min_lat"] || 0.0, - "$3" => bounds[:max_lon] || bounds["max_lon"] || 0.0, - "$4" => bounds[:max_lat] || bounds["max_lat"] || 0.0, - "$5" => limit - }} + {sql, + %{ + "$1" => bounds[:min_lon] || bounds["min_lon"] || 0.0, + "$2" => bounds[:min_lat] || bounds["min_lat"] || 0.0, + "$3" => bounds[:max_lon] || bounds["max_lon"] || 0.0, + "$4" => bounds[:max_lat] || bounds["max_lat"] || 0.0, + "$5" => limit + }} :graph in modalities && Map.has_key?(query_params, :graph_pattern) -> # Recursive CTE for graph traversal @@ -255,11 +256,12 @@ defmodule VeriSim.Federation.Adapters.PostgreSQL do LIMIT $3 """ - {sql, %{ - "$1" => range[:start] || range["start"] || "", - "$2" => range[:end] || range["end"] || "", - "$3" => limit - }} + {sql, + %{ + "$1" => range[:start] || range["start"] || "", + "$2" => range[:end] || range["end"] || "", + "$3" => limit + }} :provenance in modalities -> audit_table = Map.get(config, :audit_table, "#{schema}.audit_log") diff --git a/elixir-orchestration/lib/verisim/federation/adapters/redis.ex b/elixir-orchestration/lib/verisim/federation/adapters/redis.ex index 053a5eb..b201d88 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/redis.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/redis.ex @@ -97,9 +97,7 @@ defmodule VeriSim.Federation.Adapters.Redis do end rescue e -> - Logger.warning( - "Redis adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("Redis adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end @@ -246,7 +244,14 @@ defmodule VeriSim.Federation.Adapters.Redis do end_ts = range[:end] || range["end"] || "+" %{ - "command" => ["TS.RANGE", ts_key, to_string(start_ts), to_string(end_ts), "COUNT", to_string(limit)] + "command" => [ + "TS.RANGE", + ts_key, + to_string(start_ts), + to_string(end_ts), + "COUNT", + to_string(limit) + ] } :provenance in modalities -> diff --git a/elixir-orchestration/lib/verisim/federation/adapters/sqlite.ex b/elixir-orchestration/lib/verisim/federation/adapters/sqlite.ex index 308de06..6113f55 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/sqlite.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/sqlite.ex @@ -91,9 +91,7 @@ defmodule VeriSim.Federation.Adapters.SQLite do end rescue e -> - Logger.warning( - "SQLite adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("SQLite adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end @@ -229,11 +227,12 @@ defmodule VeriSim.Federation.Adapters.SQLite do LIMIT $3 """ - {sql, %{ - "$1" => range[:start] || range["start"] || "", - "$2" => range[:end] || range["end"] || "", - "$3" => limit - }} + {sql, + %{ + "$1" => range[:start] || range["start"] || "", + "$2" => range[:end] || range["end"] || "", + "$3" => limit + }} :semantic in modalities && Map.has_key?(query_params, :filters) -> # JSON1 extraction for structured metadata queries diff --git a/elixir-orchestration/lib/verisim/federation/adapters/surrealdb.ex b/elixir-orchestration/lib/verisim/federation/adapters/surrealdb.ex index 2c56d49..f94e0a3 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/surrealdb.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/surrealdb.ex @@ -87,9 +87,7 @@ defmodule VeriSim.Federation.Adapters.SurrealDB do end rescue e -> - Logger.warning( - "SurrealDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("SurrealDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end @@ -169,7 +167,9 @@ defmodule VeriSim.Federation.Adapters.SurrealDB do # Build OR conditions across searchable fields conditions = search_fields - |> Enum.map(fn field -> "string::contains(string::lowercase(#{field}), string::lowercase('#{text}'))" end) + |> Enum.map(fn field -> + "string::contains(string::lowercase(#{field}), string::lowercase('#{text}'))" + end) |> Enum.join(" OR ") """ diff --git a/elixir-orchestration/lib/verisim/federation/adapters/vector_db.ex b/elixir-orchestration/lib/verisim/federation/adapters/vector_db.ex index 161c809..2a776d6 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/vector_db.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/vector_db.ex @@ -106,9 +106,7 @@ defmodule VeriSim.Federation.Adapters.VectorDB do end rescue e -> - Logger.warning( - "VectorDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("VectorDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end @@ -469,8 +467,7 @@ defmodule VeriSim.Federation.Adapters.VectorDB do case Req.post(url, json: body, headers: headers, receive_timeout: timeout) do {:ok, %Req.Response{status: 200, body: resp}} -> - results = - get_in(resp, ["data", "Get", collection]) || [] + results = get_in(resp, ["data", "Get", collection]) || [] {:ok, results} diff --git a/elixir-orchestration/lib/verisim/federation/adapters/verisimdb.ex b/elixir-orchestration/lib/verisim/federation/adapters/verisimdb.ex index 401bb7b..21713f7 100644 --- a/elixir-orchestration/lib/verisim/federation/adapters/verisimdb.ex +++ b/elixir-orchestration/lib/verisim/federation/adapters/verisimdb.ex @@ -77,9 +77,7 @@ defmodule VeriSim.Federation.Adapters.VeriSimDB do end rescue e -> - Logger.warning( - "VeriSimDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}" - ) + Logger.warning("VeriSimDB adapter: exception querying #{peer_info.store_id}: #{inspect(e)}") {:error, {:exception, e}} end diff --git a/elixir-orchestration/lib/verisim/federation/resolver.ex b/elixir-orchestration/lib/verisim/federation/resolver.ex index ed2f7c0..d10e6cf 100644 --- a/elixir-orchestration/lib/verisim/federation/resolver.ex +++ b/elixir-orchestration/lib/verisim/federation/resolver.ex @@ -274,9 +274,16 @@ defmodule VeriSim.Federation.Resolver do # Add optional modality-specific query parameters query_params = maybe_add_param(query_params, :text_query, Keyword.get(opts, :text_query)) query_params = maybe_add_param(query_params, :vector_query, Keyword.get(opts, :vector_query)) - query_params = maybe_add_param(query_params, :graph_pattern, Keyword.get(opts, :graph_pattern)) - query_params = maybe_add_param(query_params, :spatial_bounds, Keyword.get(opts, :spatial_bounds)) - query_params = maybe_add_param(query_params, :temporal_range, Keyword.get(opts, :temporal_range)) + + query_params = + maybe_add_param(query_params, :graph_pattern, Keyword.get(opts, :graph_pattern)) + + query_params = + maybe_add_param(query_params, :spatial_bounds, Keyword.get(opts, :spatial_bounds)) + + query_params = + maybe_add_param(query_params, :temporal_range, Keyword.get(opts, :temporal_range)) + query_params = maybe_add_param(query_params, :filters, Keyword.get(opts, :filters)) # Resolve pattern to matching stores @@ -334,11 +341,13 @@ defmodule VeriSim.Federation.Resolver do case peer.adapter_module.health_check(peer_info) do {:ok, response_time} -> - {id, %{peer | - last_seen: DateTime.utc_now(), - response_time_ms: response_time, - trust_level: min(peer.trust_level + 0.05, 1.0) - }} + {id, + %{ + peer + | last_seen: DateTime.utc_now(), + response_time_ms: response_time, + trust_level: min(peer.trust_level + 0.05, 1.0) + }} {:error, reason} -> Logger.debug( @@ -346,9 +355,7 @@ defmodule VeriSim.Federation.Resolver do "#{inspect(reason)}" ) - {id, %{peer | - trust_level: max(peer.trust_level - 0.1, 0.0) - }} + {id, %{peer | trust_level: max(peer.trust_level - 0.1, 0.0)}} end end) |> Map.new() @@ -434,9 +441,7 @@ defmodule VeriSim.Federation.Resolver do Logger.info("Federation: repair triggered on #{peer.store_id}") {:ok, %Req.Response{status: status}} -> - Logger.warning( - "Federation: repair request to #{peer.store_id} returned #{status}" - ) + Logger.warning("Federation: repair request to #{peer.store_id} returned #{status}") {:error, reason} -> Logger.warning( diff --git a/elixir-orchestration/lib/verisim/health_checker.ex b/elixir-orchestration/lib/verisim/health_checker.ex index a81342f..1ca56cd 100644 --- a/elixir-orchestration/lib/verisim/health_checker.ex +++ b/elixir-orchestration/lib/verisim/health_checker.ex @@ -53,12 +53,13 @@ defmodule VeriSim.HealthChecker do registry_status = check_entity_registry() ets_status = check_ets_cache() - new_state = %{state | - rust_core: rust_status, - entity_registry: registry_status, - ets_cache: ets_status, - last_checked: DateTime.utc_now(), - check_count: state.check_count + 1 + new_state = %{ + state + | rust_core: rust_status, + entity_registry: registry_status, + ets_cache: ets_status, + last_checked: DateTime.utc_now(), + check_count: state.check_count + 1 } # Emit telemetry events diff --git a/elixir-orchestration/lib/verisim/hypatia/dispatch_bridge.ex b/elixir-orchestration/lib/verisim/hypatia/dispatch_bridge.ex index 1c46684..d28a587 100644 --- a/elixir-orchestration/lib/verisim/hypatia/dispatch_bridge.ex +++ b/elixir-orchestration/lib/verisim/hypatia/dispatch_bridge.ex @@ -150,33 +150,32 @@ defmodule VeriSim.Hypatia.DispatchBridge do and outcome statistics. """ def summarize(data_path) when is_binary(data_path) do - pending = case read_pending(data_path) do - {:ok, p} -> p - _ -> [] - end + pending = + case read_pending(data_path) do + {:ok, p} -> p + _ -> [] + end - dispatched = case read_all_dispatch_logs(data_path) do - {:ok, d} -> d - _ -> [] - end + dispatched = + case read_all_dispatch_logs(data_path) do + {:ok, d} -> d + _ -> [] + end - outcomes = case read_outcomes(data_path) do - {:ok, o} -> o - _ -> [] - end + outcomes = + case read_outcomes(data_path) do + {:ok, o} -> o + _ -> [] + end %{ pending_count: length(pending), dispatched_count: length(dispatched), outcome_count: length(outcomes), - by_strategy: group_by_field(dispatched, "strategy"), by_repo: group_by_field(dispatched, "repo") |> top_n(20), - outcome_success_rate: outcome_success_rate(outcomes), - pending_by_strategy: group_by_field(pending, "strategy"), - repos_with_pending: pending |> Enum.map(& &1["repo"]) @@ -199,10 +198,11 @@ defmodule VeriSim.Hypatia.DispatchBridge do Returns a list of `{repo, drift_direction, delta}` tuples. """ def feedback_to_drift(data_path) when is_binary(data_path) do - outcomes = case read_outcomes(data_path) do - {:ok, o} -> o - _ -> [] - end + outcomes = + case read_outcomes(data_path) do + {:ok, o} -> o + _ -> [] + end # Group outcomes by repo repo_outcomes = @@ -215,11 +215,12 @@ defmodule VeriSim.Hypatia.DispatchBridge do successful = Enum.count(repo_ocs, &(&1["status"] == "success")) total = length(repo_ocs) - drift = cond do - successful == total -> :improving - successful >= total / 2 -> :stable - true -> :regressing - end + drift = + cond do + successful == total -> :improving + successful >= total / 2 -> :stable + true -> :regressing + end {repo, drift, %{successful: successful, total: total}} end) diff --git a/elixir-orchestration/lib/verisim/hypatia/pattern_query.ex b/elixir-orchestration/lib/verisim/hypatia/pattern_query.ex index 3f4f496..856de42 100644 --- a/elixir-orchestration/lib/verisim/hypatia/pattern_query.ex +++ b/elixir-orchestration/lib/verisim/hypatia/pattern_query.ex @@ -47,7 +47,8 @@ defmodule VeriSim.Hypatia.PatternQuery do %{ total_scans: length(scans), total_weak_points: length(all_weak_points), - repos_scanned: scans |> Enum.map(&get_in(&1, [:metadata, :repo_name])) |> Enum.uniq() |> length(), + repos_scanned: + scans |> Enum.map(&get_in(&1, [:metadata, :repo_name])) |> Enum.uniq() |> length(), severity_distribution: severity_distribution(all_weak_points), top_categories: top_categories(all_weak_points, 10), average_weak_points_per_repo: safe_avg(length(all_weak_points), length(scans)), @@ -189,7 +190,8 @@ defmodule VeriSim.Hypatia.PatternQuery do |> extract_all_weak_points() |> Enum.group_by(& &1["location"]) |> Map.new(fn {location, items} -> - {location || "unknown", %{count: length(items), categories: Enum.map(items, & &1["category"]) |> Enum.uniq()}} + {location || "unknown", + %{count: length(items), categories: Enum.map(items, & &1["category"]) |> Enum.uniq()}} end) |> Enum.sort_by(fn {_loc, data} -> data.count end, :desc) |> Enum.take(50) diff --git a/elixir-orchestration/lib/verisim/hypatia/scan_ingester.ex b/elixir-orchestration/lib/verisim/hypatia/scan_ingester.ex index ce4f201..86d6745 100644 --- a/elixir-orchestration/lib/verisim/hypatia/scan_ingester.ex +++ b/elixir-orchestration/lib/verisim/hypatia/scan_ingester.ex @@ -128,7 +128,10 @@ defmodule VeriSim.Hypatia.ScanIngester do end) successful = Enum.count(results, fn {_, result} -> match?({:ok, _}, result) end) - Logger.info("Hypatia: ingested #{successful}/#{length(results)} scan files from #{dir_path}") + + Logger.info( + "Hypatia: ingested #{successful}/#{length(results)} scan files from #{dir_path}" + ) {:ok, results} diff --git a/elixir-orchestration/lib/verisim/nif_bridge.ex b/elixir-orchestration/lib/verisim/nif_bridge.ex index d574606..1038c12 100644 --- a/elixir-orchestration/lib/verisim/nif_bridge.ex +++ b/elixir-orchestration/lib/verisim/nif_bridge.ex @@ -40,9 +40,17 @@ defmodule VeriSim.NifBridge do |> Path.join("native/libverisim_nif") case :erlang.load_nif(String.to_charlist(nif_path), 0) do - :ok -> :ok - {:error, {:load_failed, _}} -> :ok # NIF not available — stubs will be used - {:error, {:reload, _}} -> :ok # Already loaded + :ok -> + :ok + + # NIF not available — stubs will be used + {:error, {:load_failed, _}} -> + :ok + + # Already loaded + {:error, {:reload, _}} -> + :ok + {:error, reason} -> require Logger Logger.debug("VeriSim.NifBridge: NIF not loaded (#{inspect(reason)})") diff --git a/elixir-orchestration/lib/verisim/query/query_router.ex b/elixir-orchestration/lib/verisim/query/query_router.ex index 178341b..0c547bb 100644 --- a/elixir-orchestration/lib/verisim/query/query_router.ex +++ b/elixir-orchestration/lib/verisim/query/query_router.ex @@ -66,6 +66,7 @@ defmodule VeriSim.QueryRouter do avg_latency_ms: 0.0, total_latency_ms: 0 } + {:ok, state} end @@ -90,6 +91,7 @@ defmodule VeriSim.QueryRouter do queries_by_type: state.query_by_type, avg_latency_ms: state.avg_latency_ms } + {:reply, stats, state} end @@ -176,11 +178,12 @@ defmodule VeriSim.QueryRouter do new_by_type = Map.update(state.query_by_type, type, 1, &(&1 + 1)) - %{state | - query_count: new_count, - query_by_type: new_by_type, - avg_latency_ms: new_avg, - total_latency_ms: new_total + %{ + state + | query_count: new_count, + query_by_type: new_by_type, + avg_latency_ms: new_avg, + total_latency_ms: new_total } end end diff --git a/elixir-orchestration/lib/verisim/query/vql_bridge.ex b/elixir-orchestration/lib/verisim/query/vql_bridge.ex index a243027..d5f46ec 100644 --- a/elixir-orchestration/lib/verisim/query/vql_bridge.ex +++ b/elixir-orchestration/lib/verisim/query/vql_bridge.ex @@ -146,7 +146,10 @@ defmodule VeriSim.Query.VQLBridge do {:ok, %{state | port: port}} {:error, reason} -> - Logger.warning("VQLBridge: parser process unavailable (#{reason}), falling back to built-in parser") + Logger.warning( + "VQLBridge: parser process unavailable (#{reason}), falling back to built-in parser" + ) + {:ok, state} end end @@ -161,11 +164,13 @@ defmodule VeriSim.Query.VQLBridge do @impl true def handle_call({:typecheck, ast}, from, state) do id = state.next_id - message = Jason.encode!(%{ - "id" => id, - "action" => "typecheck", - "ast" => ast - }) + + message = + Jason.encode!(%{ + "id" => id, + "action" => "typecheck", + "ast" => ast + }) send_to_port(state.port, message) pending = Map.put(state.pending, id, from) @@ -174,8 +179,13 @@ defmodule VeriSim.Query.VQLBridge do @impl true def handle_call({action, query_string}, _from, %{port: nil} = state) - when action in [:parse, :parse_slipstream, :parse_dependent, - :parse_mutation, :parse_statement] do + when action in [ + :parse, + :parse_slipstream, + :parse_dependent, + :parse_mutation, + :parse_statement + ] do # Fallback: no external parser available, use built-in Elixir parser result = builtin_parse(query_string, action) {:reply, result, state} @@ -183,14 +193,21 @@ defmodule VeriSim.Query.VQLBridge do @impl true def handle_call({action, query_string}, from, state) - when action in [:parse, :parse_slipstream, :parse_dependent, - :parse_mutation, :parse_statement] do + when action in [ + :parse, + :parse_slipstream, + :parse_dependent, + :parse_mutation, + :parse_statement + ] do id = state.next_id - message = Jason.encode!(%{ - "id" => id, - "action" => Atom.to_string(action), - "query" => query_string - }) + + message = + Jason.encode!(%{ + "id" => id, + "action" => Atom.to_string(action), + "query" => query_string + }) # Send length-prefixed message to port send_to_port(state.port, message) @@ -204,7 +221,9 @@ defmodule VeriSim.Query.VQLBridge do case Jason.decode(IO.iodata_to_binary(data)) do {:ok, %{"id" => id, "ok" => ast}} -> case Map.pop(state.pending, id) do - {nil, _} -> {:noreply, state} + {nil, _} -> + {:noreply, state} + {from, pending} -> GenServer.reply(from, {:ok, atomize_keys(ast)}) {:noreply, %{state | pending: pending}} @@ -212,7 +231,9 @@ defmodule VeriSim.Query.VQLBridge do {:ok, %{"id" => id, "error" => reason}} -> case Map.pop(state.pending, id) do - {nil, _} -> {:noreply, state} + {nil, _} -> + {:noreply, state} + {from, pending} -> GenServer.reply(from, {:error, reason}) {:noreply, %{state | pending: pending}} @@ -257,10 +278,12 @@ defmodule VeriSim.Query.VQLBridge do if runtime && File.exists?(script) do try do - port = Port.open( - {:spawn_executable, runtime}, - [:binary, :exit_status, {:args, [script]}, {:line, 1_048_576}] - ) + port = + Port.open( + {:spawn_executable, runtime}, + [:binary, :exit_status, {:args, [script]}, {:line, 1_048_576}] + ) + {:ok, port} rescue e -> {:error, Exception.message(e)} @@ -292,6 +315,7 @@ defmodule VeriSim.Query.VQLBridge do Map.new(map, fn {key, value} when is_binary(key) -> {String.to_existing_atom(key), atomize_keys(value)} + {key, value} -> {key, atomize_keys(value)} end) @@ -319,11 +343,13 @@ defmodule VeriSim.Query.VQLBridge do :parse_statement -> with {:ok, tokens} <- tokenize(query_string) do first = tokens |> List.first() |> to_string() |> String.upcase() + case first do cmd when cmd in ["INSERT", "UPDATE", "DELETE"] -> with {:ok, mutation} <- parse_mutation_tokens(tokens) do {:ok, %{TAG: "Mutation", _0: mutation}} end + _ -> with {:ok, ast} <- parse_tokens(tokens) do {:ok, %{TAG: "Query", _0: ast}} @@ -336,9 +362,15 @@ defmodule VeriSim.Query.VQLBridge do {:ok, ast} <- parse_tokens(tokens) do case action do :parse_slipstream -> - if ast[:proof], do: {:error, "Slipstream queries cannot have PROOF clause"}, else: {:ok, ast} + if ast[:proof], + do: {:error, "Slipstream queries cannot have PROOF clause"}, + else: {:ok, ast} + :parse_dependent -> - if ast[:proof], do: {:ok, ast}, else: {:error, "Dependent-type queries require PROOF clause"} + if ast[:proof], + do: {:ok, ast}, + else: {:error, "Dependent-type queries require PROOF clause"} + :parse -> {:ok, ast} end @@ -366,19 +398,20 @@ defmodule VeriSim.Query.VQLBridge do {:ok, order_by, rest} <- parse_order_by(rest), {:ok, limit, rest} <- parse_limit(rest), {:ok, offset, _rest} <- parse_offset(rest) do - {:ok, %{ - modalities: modalities, - projections: projections, - aggregates: aggregates, - source: source, - where: where_clause, - groupBy: group_by, - having: having, - proof: proof, - orderBy: order_by, - limit: limit, - offset: offset - }} + {:ok, + %{ + modalities: modalities, + projections: projections, + aggregates: aggregates, + source: source, + where: where_clause, + groupBy: group_by, + having: having, + proof: proof, + orderBy: order_by, + limit: limit, + offset: offset + }} end end @@ -389,20 +422,37 @@ defmodule VeriSim.Query.VQLBridge do defp parse_select(_), do: {:error, "Expected SELECT"} - defp take_modalities(["GRAPH" | rest], acc), do: take_modalities(strip_comma(rest), [:graph | acc]) - defp take_modalities(["VECTOR" | rest], acc), do: take_modalities(strip_comma(rest), [:vector | acc]) - defp take_modalities(["TENSOR" | rest], acc), do: take_modalities(strip_comma(rest), [:tensor | acc]) - defp take_modalities(["SEMANTIC" | rest], acc), do: take_modalities(strip_comma(rest), [:semantic | acc]) - defp take_modalities(["DOCUMENT" | rest], acc), do: take_modalities(strip_comma(rest), [:document | acc]) - defp take_modalities(["TEMPORAL" | rest], acc), do: take_modalities(strip_comma(rest), [:temporal | acc]) - defp take_modalities(["PROVENANCE" | rest], acc), do: take_modalities(strip_comma(rest), [:provenance | acc]) - defp take_modalities(["SPATIAL" | rest], acc), do: take_modalities(strip_comma(rest), [:spatial | acc]) + defp take_modalities(["GRAPH" | rest], acc), + do: take_modalities(strip_comma(rest), [:graph | acc]) + + defp take_modalities(["VECTOR" | rest], acc), + do: take_modalities(strip_comma(rest), [:vector | acc]) + + defp take_modalities(["TENSOR" | rest], acc), + do: take_modalities(strip_comma(rest), [:tensor | acc]) + + defp take_modalities(["SEMANTIC" | rest], acc), + do: take_modalities(strip_comma(rest), [:semantic | acc]) + + defp take_modalities(["DOCUMENT" | rest], acc), + do: take_modalities(strip_comma(rest), [:document | acc]) + + defp take_modalities(["TEMPORAL" | rest], acc), + do: take_modalities(strip_comma(rest), [:temporal | acc]) + + defp take_modalities(["PROVENANCE" | rest], acc), + do: take_modalities(strip_comma(rest), [:provenance | acc]) + + defp take_modalities(["SPATIAL" | rest], acc), + do: take_modalities(strip_comma(rest), [:spatial | acc]) + defp take_modalities(["*" | rest], acc), do: take_modalities(strip_comma(rest), [:all | acc]) defp take_modalities(rest, acc), do: {Enum.reverse(acc), rest} defp strip_comma(["," <> token | rest]) when token != "" do [token | rest] end + defp strip_comma(["," | rest]), do: rest defp strip_comma(rest), do: rest @@ -422,13 +472,15 @@ defmodule VeriSim.Query.VQLBridge do defp parse_from(_), do: {:error, "Expected FROM clause"} defp parse_drift_policy(["WITH", "DRIFT", policy | rest]) do - drift = case String.upcase(policy) do - "STRICT" -> :strict - "REPAIR" -> :repair - "TOLERATE" -> :tolerate - "LATEST" -> :latest - _ -> nil - end + drift = + case String.upcase(policy) do + "STRICT" -> :strict + "REPAIR" -> :repair + "TOLERATE" -> :tolerate + "LATEST" -> :latest + _ -> nil + end + {drift, rest} end @@ -436,15 +488,17 @@ defmodule VeriSim.Query.VQLBridge do defp parse_where(["WHERE" | rest]) do # Simplified: collect everything until PROOF, LIMIT, OFFSET, or end - {condition_tokens, rest} = Enum.split_while(rest, fn token -> - token not in ["PROOF", "LIMIT", "OFFSET"] - end) - - condition = if condition_tokens == [] do - nil - else - %{raw: Enum.join(condition_tokens, " ")} - end + {condition_tokens, rest} = + Enum.split_while(rest, fn token -> + token not in ["PROOF", "LIMIT", "OFFSET"] + end) + + condition = + if condition_tokens == [] do + nil + else + %{raw: Enum.join(condition_tokens, " ")} + end {:ok, condition, rest} end @@ -452,9 +506,10 @@ defmodule VeriSim.Query.VQLBridge do defp parse_where(rest), do: {:ok, nil, rest} defp parse_proof(["PROOF" | rest]) do - {proof_tokens, rest} = Enum.split_while(rest, fn token -> - token not in ["LIMIT", "OFFSET"] - end) + {proof_tokens, rest} = + Enum.split_while(rest, fn token -> + token not in ["LIMIT", "OFFSET"] + end) raw = Enum.join(proof_tokens, " ") @@ -462,11 +517,12 @@ defmodule VeriSim.Query.VQLBridge do # "EXISTENCE(a) AND PROVENANCE(b)" → [%{raw: "EXISTENCE(a)"}, %{raw: "PROVENANCE(b)"}] specs = VeriSim.Query.VQLTypeChecker.parse_proof_specs(%{raw: raw}) - proof = case specs do - [] -> %{raw: raw} - [single] -> single - multiple -> multiple - end + proof = + case specs do + [] -> %{raw: raw} + [single] -> single + multiple -> multiple + end {:ok, proof, rest} end @@ -504,11 +560,20 @@ defmodule VeriSim.Query.VQLBridge do # Safe atom conversion using allowlist — prevents atom table exhaustion @safe_atoms %{ - "graph" => :graph, "vector" => :vector, "tensor" => :tensor, - "semantic" => :semantic, "document" => :document, "temporal" => :temporal, - "provenance" => :provenance, "spatial" => :spatial, + "graph" => :graph, + "vector" => :vector, + "tensor" => :tensor, + "semantic" => :semantic, + "document" => :document, + "temporal" => :temporal, + "provenance" => :provenance, + "spatial" => :spatial, "all" => :all, - "count" => :count, "sum" => :sum, "avg" => :avg, "min" => :min, "max" => :max + "count" => :count, + "sum" => :sum, + "avg" => :avg, + "min" => :min, + "max" => :max } defp safe_to_atom(str) when is_binary(str) do @@ -541,6 +606,7 @@ defmodule VeriSim.Query.VQLBridge do mod_atom = safe_to_atom(mod) mods = if mod_atom in mods, do: mods, else: [mod_atom | mods] take_select_items(strip_comma(rest), mods, projs, [agg | aggs]) + _ -> {{Enum.reverse(mods), nilify(projs), nilify(aggs)}, tokens} end @@ -557,12 +623,15 @@ defmodule VeriSim.Query.VQLBridge do _ -> # Try as bare modality up = String.upcase(String.replace(token, ",", "")) + cond do up in @modality_names -> mod_atom = safe_to_atom(up) take_select_items(strip_comma(rest), [mod_atom | mods], projs, aggs) + up == "*" -> take_select_items(strip_comma(rest), [:all | mods], projs, aggs) + true -> {{Enum.reverse(mods), nilify(projs), nilify(aggs)}, tokens} end @@ -576,16 +645,22 @@ defmodule VeriSim.Query.VQLBridge do defp parse_aggregate_arg(["(" <> rest_token | rest]) do # Handle "(MODALITY.field)" — may be split across tokens inner = String.trim_trailing(rest_token, ")") + case String.split(inner, ".", parts: 2) do [mod, field] when mod in @modality_names -> - rest = case rest do - [")" | r] -> r - _ -> rest - end + rest = + case rest do + [")" | r] -> r + _ -> rest + end + {:ok, mod, field, rest} - _ -> :error + + _ -> + :error end end + defp parse_aggregate_arg(_), do: :error defp nilify([]), do: nil @@ -601,10 +676,12 @@ defmodule VeriSim.Query.VQLBridge do defp take_field_refs([token | rest], acc) do clean = String.replace(token, ",", "") + case String.split(clean, ".", parts: 2) do [mod_str, field] when mod_str in @modality_names -> ref = %{modality: safe_to_atom(mod_str), field: field} take_field_refs(strip_comma(rest), [ref | acc]) + _ -> {Enum.reverse(acc), [token | rest]} end @@ -614,15 +691,17 @@ defmodule VeriSim.Query.VQLBridge do # HAVING parser (collects tokens until ORDER/PROOF/LIMIT/OFFSET/end) defp parse_having(["HAVING" | rest]) do - {condition_tokens, rest} = Enum.split_while(rest, fn token -> - String.upcase(token) not in ["ORDER", "PROOF", "LIMIT", "OFFSET"] - end) - - condition = if condition_tokens == [] do - nil - else - %{raw: Enum.join(condition_tokens, " ")} - end + {condition_tokens, rest} = + Enum.split_while(rest, fn token -> + String.upcase(token) not in ["ORDER", "PROOF", "LIMIT", "OFFSET"] + end) + + condition = + if condition_tokens == [] do + nil + else + %{raw: Enum.join(condition_tokens, " ")} + end {:ok, condition, rest} end @@ -639,20 +718,23 @@ defmodule VeriSim.Query.VQLBridge do defp take_order_items([token | rest], acc) do clean = String.replace(token, ",", "") + case String.split(clean, ".", parts: 2) do [mod_str, field] when mod_str in @modality_names -> - {direction, rest} = case rest do - ["ASC" | r] -> {:asc, strip_comma(r)} - ["DESC" | r] -> {:desc, strip_comma(r)} - ["ASC," <> _ | _] -> {:asc, strip_comma(rest)} - ["DESC," <> _ | _] -> {:desc, strip_comma(rest)} - _ -> {:asc, strip_comma(rest)} - end + {direction, rest} = + case rest do + ["ASC" | r] -> {:asc, strip_comma(r)} + ["DESC" | r] -> {:desc, strip_comma(r)} + ["ASC," <> _ | _] -> {:asc, strip_comma(rest)} + ["DESC," <> _ | _] -> {:desc, strip_comma(rest)} + _ -> {:asc, strip_comma(rest)} + end item = %{ field: %{modality: safe_to_atom(mod_str), field: field}, direction: direction } + take_order_items(rest, [item | acc]) _ -> @@ -669,31 +751,37 @@ defmodule VeriSim.Query.VQLBridge do defp parse_mutation_tokens(["INSERT", "HEXAD", "WITH" | rest]) do {modality_data, rest} = take_modality_data(rest, []) {:ok, proof, _rest} = parse_proof(rest) - {:ok, %{ - TAG: "Insert", - modalities: modality_data, - proof: proof - }} + + {:ok, + %{ + TAG: "Insert", + modalities: modality_data, + proof: proof + }} end defp parse_mutation_tokens(["UPDATE", "HEXAD", uuid, "SET" | rest]) do {sets, rest} = take_set_assignments(rest, []) {:ok, proof, _rest} = parse_proof(rest) - {:ok, %{ - TAG: "Update", - octadId: uuid, - sets: sets, - proof: proof - }} + + {:ok, + %{ + TAG: "Update", + octadId: uuid, + sets: sets, + proof: proof + }} end defp parse_mutation_tokens(["DELETE", "HEXAD", uuid | rest]) do {:ok, proof, _rest} = parse_proof(rest) - {:ok, %{ - TAG: "Delete", - octadId: uuid, - proof: proof - }} + + {:ok, + %{ + TAG: "Delete", + octadId: uuid, + proof: proof + }} end defp parse_mutation_tokens(_), do: {:error, "Expected INSERT, UPDATE, or DELETE"} @@ -707,15 +795,18 @@ defmodule VeriSim.Query.VQLBridge do {inner_tokens, rest3} = collect_until_close_paren([inner_start | rest2], []) data = %{modality: safe_to_atom(mod), raw: Enum.join(inner_tokens, " ")} take_modality_data(strip_comma(rest3), [data | acc]) + _ -> {Enum.reverse(acc), tokens} end + _ -> {Enum.reverse(acc), tokens} end end defp collect_until_close_paren([], acc), do: {Enum.reverse(acc), []} + defp collect_until_close_paren([token | rest], acc) do if String.ends_with?(token, ")") do cleaned = String.trim_trailing(token, ")") @@ -731,6 +822,7 @@ defmodule VeriSim.Query.VQLBridge do [field, "=", value | rest] -> assignment = %{field: field, value: value} take_set_assignments(strip_comma(rest), [assignment | acc]) + _ -> {Enum.reverse(acc), tokens} end diff --git a/elixir-orchestration/lib/verisim/query/vql_executor.ex b/elixir-orchestration/lib/verisim/query/vql_executor.ex index 52d32a1..f7fb187 100644 --- a/elixir-orchestration/lib/verisim/query/vql_executor.ex +++ b/elixir-orchestration/lib/verisim/query/vql_executor.ex @@ -113,6 +113,7 @@ defmodule VeriSim.Query.VQLExecutor do statement_type: statement_type, modalities: modalities }) + {:error, _} -> Telemetry.emit_query_exception(%{statement_type: statement_type}) end @@ -123,6 +124,7 @@ defmodule VeriSim.Query.VQLExecutor do # Classify a query string into a statement type for telemetry (no content captured). defp classify_statement_type(query_string) do upper = String.upcase(String.trim(query_string)) + cond do String.starts_with?(upper, "SELECT") -> "SELECT" String.starts_with?(upper, "INSERT") -> "INSERT" @@ -139,6 +141,7 @@ defmodule VeriSim.Query.VQLExecutor do # Extract modality names from a query string for telemetry (names only, no content). defp extract_modalities_from_string(query_string) do upper = String.upcase(query_string) + ~w(GRAPH VECTOR TENSOR SEMANTIC DOCUMENT TEMPORAL PROVENANCE SPATIAL) |> Enum.filter(&String.contains?(upper, &1)) |> Enum.map(&String.downcase/1) @@ -163,18 +166,50 @@ defmodule VeriSim.Query.VQLExecutor do if proof_specs do # VQL-DT path: type-check → execute → verify proofs → bundle certificate - execute_dt_query(query_ast, proof_specs, modalities, source, where_clause, - limit, offset, order_by, group_by, aggregates, projections, timeout) + execute_dt_query( + query_ast, + proof_specs, + modalities, + source, + where_clause, + limit, + offset, + order_by, + group_by, + aggregates, + projections, + timeout + ) else # Slipstream path: no proofs, no type checking - execute_slipstream_query(modalities, source, where_clause, - limit, offset, order_by, group_by, aggregates, projections, timeout) + execute_slipstream_query( + modalities, + source, + where_clause, + limit, + offset, + order_by, + group_by, + aggregates, + projections, + timeout + ) end end # Slipstream execution — fast path, no proofs - defp execute_slipstream_query(modalities, source, where_clause, - limit, offset, order_by, group_by, aggregates, projections, timeout) do + defp execute_slipstream_query( + modalities, + source, + where_clause, + limit, + offset, + order_by, + group_by, + aggregates, + projections, + timeout + ) do {pushdown_conditions, cross_modal_conditions} = classify_conditions(where_clause) result = execute_by_source(source, modalities, pushdown_conditions, limit, offset, timeout) @@ -188,13 +223,26 @@ defmodule VeriSim.Query.VQLExecutor do |> maybe_project_columns(projections) |> then(&{:ok, &1}) - error -> error + error -> + error end end # VQL-DT execution — type check, execute, verify proofs, bundle certificate - defp execute_dt_query(query_ast, proof_specs, modalities, source, where_clause, - limit, offset, order_by, group_by, aggregates, projections, timeout) do + defp execute_dt_query( + query_ast, + proof_specs, + modalities, + source, + where_clause, + limit, + offset, + order_by, + group_by, + aggregates, + projections, + timeout + ) do alias VeriSim.Query.{VQLBridge, VQLTypeChecker} # Step 1: Type-check the query to get proof obligations and composition strategy. @@ -202,36 +250,41 @@ defmodule VeriSim.Query.VQLExecutor do # 1. ReScript bidirectional type checker (VQLBridge.typecheck — full formal system) # 2. Elixir-native type checker (VQLTypeChecker — validates types, generates obligations) # 3. Bare AST extraction (last resort — no validation, just structuring) - type_info = case VQLBridge.typecheck(query_ast) do - {:ok, info} -> - info + type_info = + case VQLBridge.typecheck(query_ast) do + {:ok, info} -> + info + + {:error, :type_checker_unavailable} -> + # ReScript subprocess not running. Use the Elixir-native type checker + # which validates proof types, modality compatibility, and composition. + Logger.info( + "VQL-DT: Using Elixir-native type checker (ReScript subprocess unavailable)" + ) + + case VQLTypeChecker.typecheck(query_ast) do + {:ok, info} -> + info - {:error, :type_checker_unavailable} -> - # ReScript subprocess not running. Use the Elixir-native type checker - # which validates proof types, modality compatibility, and composition. - Logger.info("VQL-DT: Using Elixir-native type checker (ReScript subprocess unavailable)") - - case VQLTypeChecker.typecheck(query_ast) do - {:ok, info} -> - info - - {:error, reason} -> - # Native type checker rejected the query — this is a real type error. - Logger.error("VQL-DT: Type checking failed: #{inspect(reason)}") - nil - end + {:error, reason} -> + # Native type checker rejected the query — this is a real type error. + Logger.error("VQL-DT: Type checking failed: #{inspect(reason)}") + nil + end - {:error, reason} -> - Logger.error("VQL-DT: Type checking failed: #{inspect(reason)}") - nil - end + {:error, reason} -> + Logger.error("VQL-DT: Type checking failed: #{inspect(reason)}") + nil + end if is_nil(type_info) do {:error, {:type_check_failed, "VQL-DT query type checking failed"}} else # Step 2: Execute the query (get data) {pushdown_conditions, cross_modal_conditions} = classify_conditions(where_clause) - data_result = execute_by_source(source, modalities, pushdown_conditions, limit, offset, timeout) + + data_result = + execute_by_source(source, modalities, pushdown_conditions, limit, offset, timeout) case data_result do {:ok, rows} -> @@ -243,7 +296,9 @@ defmodule VeriSim.Query.VQLExecutor do |> maybe_project_columns(projections) # Step 3: Verify all proof obligations - obligations = type_info[:proof_obligations] || type_info["proof_obligations"] || proof_specs + obligations = + type_info[:proof_obligations] || type_info["proof_obligations"] || proof_specs + proof_result = verify_multi_proof(query_ast, obligations) case proof_result do @@ -253,7 +308,10 @@ defmodule VeriSim.Query.VQLExecutor do # verifier (e.g., hash commitments, Merkle proofs, entity existence # confirmations) so downstream consumers can independently verify. query_text = Map.get(query_ast, :raw, "") || "" - composition = type_info[:composition_strategy] || type_info["composition_strategy"] || :conjunction + + composition = + type_info[:composition_strategy] || type_info["composition_strategy"] || + :conjunction # Step 4b: Generate independently verifiable certificates for # each proof obligation using VQLProofCertificate. Each artifact @@ -264,6 +322,7 @@ defmodule VeriSim.Query.VQLExecutor do |> Enum.zip(List.wrap(artifacts)) |> Enum.map(fn {obligation, artifact} -> witness = if is_map(artifact), do: artifact, else: %{raw: artifact} + case VQLProofCertificate.generate_certificate(obligation, witness) do {:ok, cert} -> cert {:error, _} -> nil @@ -278,17 +337,20 @@ defmodule VeriSim.Query.VQLExecutor do obligations: obligations, composition: composition, verified_at: DateTime.utc_now(), - query_hash: :crypto.hash(:sha256, to_string(query_text)) |> Base.encode16(case: :lower), + query_hash: + :crypto.hash(:sha256, to_string(query_text)) |> Base.encode16(case: :lower), verifiable_certificates: verifiable_certificates } } + {:ok, proved_result} {:error, reason} -> {:error, {:proof_verification_failed, reason}} end - error -> error + error -> + error end end end @@ -300,7 +362,15 @@ defmodule VeriSim.Query.VQLExecutor do execute_octad_query(entity_id, modalities, pushdown_conditions, limit, offset, timeout) {:federation, pattern, drift_policy} -> - execute_federation_query(pattern, drift_policy, modalities, pushdown_conditions, limit, offset, timeout) + execute_federation_query( + pattern, + drift_policy, + modalities, + pushdown_conditions, + limit, + offset, + timeout + ) {:store, store_id} -> execute_store_query(store_id, modalities, pushdown_conditions, limit, offset, timeout) @@ -316,31 +386,46 @@ defmodule VeriSim.Query.VQLExecutor do defp classify_conditions(nil), do: {nil, []} defp classify_conditions(%{raw: _} = condition), do: {condition, []} + defp classify_conditions(condition) when is_map(condition) do case condition do - %{TAG: "CrossModalFieldCompare"} -> {nil, [condition]} - %{TAG: "ModalityDrift"} -> {nil, [condition]} - %{TAG: "ModalityExists"} -> {nil, [condition]} - %{TAG: "ModalityNotExists"} -> {nil, [condition]} - %{TAG: "ModalityConsistency"} -> {nil, [condition]} + %{TAG: "CrossModalFieldCompare"} -> + {nil, [condition]} + + %{TAG: "ModalityDrift"} -> + {nil, [condition]} + + %{TAG: "ModalityExists"} -> + {nil, [condition]} + + %{TAG: "ModalityNotExists"} -> + {nil, [condition]} + + %{TAG: "ModalityConsistency"} -> + {nil, [condition]} + %{TAG: "And", _0: left, _1: right} -> {push_l, cross_l} = classify_conditions(left) {push_r, cross_r} = classify_conditions(right) pushdown = combine_pushdown(push_l, push_r, :and) {pushdown, cross_l ++ cross_r} + %{TAG: "Or", _0: left, _1: right} -> {push_l, cross_l} = classify_conditions(left) {push_r, cross_r} = classify_conditions(right) pushdown = combine_pushdown(push_l, push_r, :or) {pushdown, cross_l ++ cross_r} + %{TAG: "Not", _0: inner} -> {push, cross} = classify_conditions(inner) {push, cross} + _ -> # Simple condition: pushdown {condition, []} end end + defp classify_conditions(condition), do: {condition, []} defp combine_pushdown(nil, nil, _op), do: nil @@ -354,6 +439,7 @@ defmodule VeriSim.Query.VQLExecutor do # =========================================================================== defp maybe_evaluate_cross_modal(rows, []), do: rows + defp maybe_evaluate_cross_modal(rows, cross_modal_conditions) do Enum.filter(rows, fn octad -> Enum.all?(cross_modal_conditions, fn condition -> @@ -364,8 +450,7 @@ defmodule VeriSim.Query.VQLExecutor do defp evaluate_cross_modal(octad, condition) do case condition do - %{TAG: "CrossModalFieldCompare", - _0: mod1, _1: field1, _2: op, _3: mod2, _4: field2} -> + %{TAG: "CrossModalFieldCompare", _0: mod1, _1: field1, _2: op, _3: mod2, _4: field2} -> val1 = get_modality_field(octad, mod1, field1) val2 = get_modality_field(octad, mod2, field2) compare_values_with_op(val1, op, val2) @@ -383,7 +468,8 @@ defmodule VeriSim.Query.VQLExecutor do %{TAG: "ModalityConsistency", _0: mod1, _1: mod2, _2: metric} -> compute_consistency(octad, mod1, mod2, metric) > 0.0 - _ -> true + _ -> + true end end @@ -395,6 +481,7 @@ defmodule VeriSim.Query.VQLExecutor do defp has_modality_data?(octad, modality) do mod_str = modality_to_string(modality) + case Map.get(octad, mod_str) do nil -> false data when data == %{} -> false @@ -410,8 +497,12 @@ defmodule VeriSim.Query.VQLExecutor do mod2_str = modality_to_string(mod2) case {Map.get(octad, mod1_str), Map.get(octad, mod2_str)} do - {nil, _} -> 1.0 # Missing modality = maximum drift - {_, nil} -> 1.0 + # Missing modality = maximum drift + {nil, _} -> + 1.0 + + {_, nil} -> + 1.0 {data1, data2} -> # Try to get drift from the Rust drift detector via octad ID @@ -440,6 +531,7 @@ defmodule VeriSim.Query.VQLExecutor do true -> [] end end + defp extract_embedding_from_modality(data) when is_list(data), do: data defp extract_embedding_from_modality(_), do: [] @@ -455,11 +547,14 @@ defmodule VeriSim.Query.VQLExecutor do end) end - defp compute_cosine_distance([], _), do: 0.5 # Unknown = moderate drift + # Unknown = moderate drift + defp compute_cosine_distance([], _), do: 0.5 defp compute_cosine_distance(_, []), do: 0.5 + defp compute_cosine_distance(vec1, vec2) do # Cosine distance: 1 - cosine_similarity. Range: [0.0, 2.0], normalized to [0.0, 1.0]. - {dot, mag1, mag2} = Enum.zip(vec1, vec2) + {dot, mag1, mag2} = + Enum.zip(vec1, vec2) |> Enum.reduce({0.0, 0.0, 0.0}, fn {a, b}, {d, m1, m2} -> {d + a * b, m1 + a * a, m2 + b * b} end) @@ -471,7 +566,8 @@ defmodule VeriSim.Query.VQLExecutor do # Clamp and normalize to [0.0, 1.0] min(max(1.0 - similarity, 0.0), 1.0) else - 1.0 # Zero vectors = max drift + # Zero vectors = max drift + 1.0 end end @@ -485,8 +581,11 @@ defmodule VeriSim.Query.VQLExecutor do data2 = Map.get(octad, mod2_str) case {data1, data2} do - {nil, _} -> 0.0 - {_, nil} -> 0.0 + {nil, _} -> + 0.0 + + {_, nil} -> + 0.0 {d1, d2} -> vec1 = extract_embedding_from_modality(d1) @@ -506,15 +605,18 @@ defmodule VeriSim.Query.VQLExecutor do jaccard_similarity(d1, d2) _ -> - cosine_similarity(vec1, vec2) # Default to cosine + # Default to cosine + cosine_similarity(vec1, vec2) end end end defp cosine_similarity([], _), do: 0.0 defp cosine_similarity(_, []), do: 0.0 + defp cosine_similarity(vec1, vec2) do - {dot, mag1, mag2} = Enum.zip(vec1, vec2) + {dot, mag1, mag2} = + Enum.zip(vec1, vec2) |> Enum.reduce({0.0, 0.0, 0.0}, fn {a, b}, {d, m1, m2} -> {d + a * b, m1 + a * a, m2 + b * b} end) @@ -525,8 +627,10 @@ defmodule VeriSim.Query.VQLExecutor do defp euclidean_similarity([], _), do: 0.0 defp euclidean_similarity(_, []), do: 0.0 + defp euclidean_similarity(vec1, vec2) do - dist = Enum.zip(vec1, vec2) + dist = + Enum.zip(vec1, vec2) |> Enum.reduce(0.0, fn {a, b}, acc -> acc + (a - b) * (a - b) end) |> :math.sqrt() @@ -536,6 +640,7 @@ defmodule VeriSim.Query.VQLExecutor do defp dot_product_similarity([], _), do: 0.0 defp dot_product_similarity(_, []), do: 0.0 + defp dot_product_similarity(vec1, vec2) do dot = Enum.zip(vec1, vec2) |> Enum.reduce(0.0, fn {a, b}, acc -> acc + a * b end) # Normalize to [0.0, 1.0] using sigmoid @@ -550,6 +655,7 @@ defmodule VeriSim.Query.VQLExecutor do union = MapSet.union(keys1, keys2) |> MapSet.size() if union > 0, do: intersection / union, else: 0.0 end + defp jaccard_similarity(_, _), do: 0.0 defp compare_values_with_op(val1, op, val2) when is_number(val1) and is_number(val2) do @@ -569,6 +675,7 @@ defmodule VeriSim.Query.VQLExecutor do _ -> false end end + defp compare_values_with_op(val1, op, val2) when is_binary(val1) and is_binary(val2) do case op do "==" -> val1 == val2 @@ -578,6 +685,7 @@ defmodule VeriSim.Query.VQLExecutor do _ -> false end end + defp compare_values_with_op(_val1, _op, _val2), do: false defp modality_to_string(mod) when is_binary(mod), do: String.downcase(mod) @@ -614,13 +722,17 @@ defmodule VeriSim.Query.VQLExecutor do {:error, {:write_proof_failed, reason}} {:ok, _artifacts} -> - field_updates = Enum.map(sets, fn {field_ref, value} -> - {field_ref, value} - end) + field_updates = + Enum.map(sets, fn {field_ref, value} -> + {field_ref, value} + end) case RustClient.update_octad(octad_id, field_updates) do - {:ok, _} -> {:ok, %{octad_id: octad_id, operation: :update, fields_updated: length(sets)}} - {:error, reason} -> {:error, {:update_failed, reason}} + {:ok, _} -> + {:ok, %{octad_id: octad_id, operation: :update, fields_updated: length(sets)}} + + {:error, reason} -> + {:error, {:update_failed, reason}} end end rescue @@ -651,9 +763,10 @@ defmodule VeriSim.Query.VQLExecutor do defp verify_multi_proof(_query_ast, proof_specs) when is_list(proof_specs) do # Verify each proof in the composition, collecting proof artifacts. # Each verify_single_proof/1 returns {:ok, artifact} or {:error, reason}. - results = Enum.map(proof_specs, fn spec -> - verify_single_proof(spec) - end) + results = + Enum.map(proof_specs, fn spec -> + verify_single_proof(spec) + end) case Enum.find(results, &match?({:error, _}, &1)) do nil -> @@ -665,12 +778,16 @@ defmodule VeriSim.Query.VQLExecutor do error end end + defp verify_multi_proof(_query_ast, nil), do: {:ok, []} + defp verify_multi_proof(_query_ast, proof_specs) do # Non-list proof specs are a safety violation — they must not silently pass. # This catch-all previously returned :ok, which meant malformed proof specs # (e.g., a bare map or atom) would bypass verification entirely. - {:error, {:invalid_proof_specs, "Expected a list of proof specifications, got: #{inspect(proof_specs)}"}} + {:error, + {:invalid_proof_specs, + "Expected a list of proof specifications, got: #{inspect(proof_specs)}"}} end defp verify_single_proof(proof_spec) do @@ -688,10 +805,17 @@ defmodule VeriSim.Query.VQLExecutor do if entity_id do case RustClient.get_octad(entity_id) do {:ok, octad} -> - {:ok, %{type: :existence, entity_id: entity_id, verified: true, - status: Map.get(octad, "status", %{})}} + {:ok, + %{ + type: :existence, + entity_id: entity_id, + verified: true, + status: Map.get(octad, "status", %{}) + }} + {:error, :not_found} -> {:error, {:existence_failed, "Entity '#{entity_id}' does not exist"}} + {:error, reason} -> {:error, {:existence_check_failed, reason}} end @@ -718,16 +842,22 @@ defmodule VeriSim.Query.VQLExecutor do case RustClient.get("/auth/check/#{entity_id}") do {:ok, %{status: 200, body: %{"authorized" => true}}} -> {:ok, %{type: :access, entity_id: entity_id, authorized: true}} + {:ok, %{status: 200, body: %{"authorized" => false}}} -> {:error, {:access_denied, "Not authorized to access '#{entity_id}'"}} + {:ok, %{status: 403}} -> {:error, {:access_denied, "Not authorized to access '#{entity_id}'"}} + {:error, reason} -> {:error, {:access_check_failed, reason}} end else # No specific entity — log a warning but allow for global queries. - Logger.warning("VQL-DT: Access proof without entity ID — global query, skipping entity-level check") + Logger.warning( + "VQL-DT: Access proof without entity ID — global query, skipping entity-level check" + ) + {:ok, %{type: :access, entity_id: nil, authorized: true, scope: :global}} end @@ -741,12 +871,20 @@ defmodule VeriSim.Query.VQLExecutor do privacy_level: "public" }) do {:ok, %{status: 200, body: %{"success" => true} = body}} -> - {:ok, %{type: :integrity, contract: contract_name, verified: true, - proof_data: Map.get(body, "proof")}} + {:ok, + %{ + type: :integrity, + contract: contract_name, + verified: true, + proof_data: Map.get(body, "proof") + }} + {:ok, %{status: 200, body: %{"success" => false, "error" => reason}}} -> {:error, {:integrity_failed, reason}} + {:ok, %{status: 200, body: %{"error" => reason}}} -> {:error, {:integrity_failed, reason}} + {:error, reason} -> {:error, {:integrity_check_failed, reason}} end @@ -762,8 +900,13 @@ defmodule VeriSim.Query.VQLExecutor do if entity_id do case RustClient.verify_provenance(entity_id) do {:ok, %{"has_provenance" => true, "chain_valid" => true} = body} -> - {:ok, %{type: :provenance, entity_id: entity_id, chain_valid: true, - chain_length: Map.get(body, "chain_length", 0)}} + {:ok, + %{ + type: :provenance, + entity_id: entity_id, + chain_valid: true, + chain_length: Map.get(body, "chain_length", 0) + }} {:ok, %{"has_provenance" => true, "chain_valid" => false}} -> {:error, {:provenance_chain_broken, entity_id}} @@ -791,11 +934,19 @@ defmodule VeriSim.Query.VQLExecutor do case RustClient.get_drift_score(entity_id) do {:ok, score} when is_number(score) -> threshold = Map.get(proof_spec, :threshold, 0.3) + if score <= threshold do - {:ok, %{type: :consistency, entity_id: entity_id, drift_score: score, - threshold: threshold, consistent: true}} + {:ok, + %{ + type: :consistency, + entity_id: entity_id, + drift_score: score, + threshold: threshold, + consistent: true + }} else - {:error, {:consistency_failed, + {:error, + {:consistency_failed, "Entity '#{entity_id}' drift score #{score} exceeds threshold #{threshold}"}} end @@ -817,33 +968,46 @@ defmodule VeriSim.Query.VQLExecutor do {:ok, octad} -> max_age_ms = Map.get(proof_spec, :max_age_ms, 3_600_000) temporal = Map.get(octad, "temporal", %{}) - last_modified = Map.get(temporal, "last_modified") || - Map.get(temporal, "updated_at") || - Map.get(octad, "updated_at") + + last_modified = + Map.get(temporal, "last_modified") || + Map.get(temporal, "updated_at") || + Map.get(octad, "updated_at") if last_modified do - age_ms = case DateTime.from_iso8601(to_string(last_modified)) do - {:ok, dt, _} -> - DateTime.diff(DateTime.utc_now(), dt, :millisecond) - _ -> - # If timestamp is a unix epoch, convert - if is_number(last_modified) do - now_ms = System.system_time(:millisecond) - now_ms - trunc(last_modified) - else - max_age_ms + 1 # Unknown format — treat as stale - end - end + age_ms = + case DateTime.from_iso8601(to_string(last_modified)) do + {:ok, dt, _} -> + DateTime.diff(DateTime.utc_now(), dt, :millisecond) + + _ -> + # If timestamp is a unix epoch, convert + if is_number(last_modified) do + now_ms = System.system_time(:millisecond) + now_ms - trunc(last_modified) + else + # Unknown format — treat as stale + max_age_ms + 1 + end + end if age_ms <= max_age_ms do - {:ok, %{type: :freshness, entity_id: entity_id, age_ms: age_ms, - max_age_ms: max_age_ms, fresh: true}} + {:ok, + %{ + type: :freshness, + entity_id: entity_id, + age_ms: age_ms, + max_age_ms: max_age_ms, + fresh: true + }} else - {:error, {:freshness_expired, + {:error, + {:freshness_expired, "Entity '#{entity_id}' is #{age_ms}ms old, exceeds max age #{max_age_ms}ms"}} end else - {:error, {:no_temporal_data, + {:error, + {:no_temporal_data, "Entity '#{entity_id}' has no temporal/timestamp data for freshness check"}} end @@ -868,10 +1032,17 @@ defmodule VeriSim.Query.VQLExecutor do privacy_level: Map.get(proof_spec, :privacy_level, "public") }) do {:ok, %{status: 200, body: %{"success" => true} = body}} -> - {:ok, %{type: :custom, circuit: contract_name, verified: true, - proof_data: Map.get(body, "proof")}} + {:ok, + %{ + type: :custom, + circuit: contract_name, + verified: true, + proof_data: Map.get(body, "proof") + }} + {:ok, %{status: 200, body: %{"error" => reason}}} -> {:error, {:custom_proof_failed, reason}} + {:error, reason} -> {:error, {:custom_proof_failed, reason}} end @@ -894,10 +1065,17 @@ defmodule VeriSim.Query.VQLExecutor do case RustClient.post("/proofs/generate", request) do {:ok, %{status: 200, body: %{"success" => true} = body}} -> - {:ok, %{type: :zkp, verified: true, privacy_level: privacy_level, - proof_data: Map.get(body, "proof")}} + {:ok, + %{ + type: :zkp, + verified: true, + privacy_level: privacy_level, + proof_data: Map.get(body, "proof") + }} + {:ok, %{status: 200, body: %{"error" => reason}}} -> {:error, {:zkp_failed, reason}} + {:error, reason} -> {:error, {:zkp_unavailable, reason}} end @@ -905,10 +1083,11 @@ defmodule VeriSim.Query.VQLExecutor do :proven -> # Proven proofs verify against certificates from the proven library claim = Map.get(proof_spec, :claim, "") + case RustClient.post("/proofs/generate", %{claim: claim, privacy_level: "public"}) do {:ok, %{status: 200, body: %{"success" => true} = body}} -> - {:ok, %{type: :proven, verified: true, - proof_data: Map.get(body, "proof")}} + {:ok, %{type: :proven, verified: true, proof_data: Map.get(body, "proof")}} + _ -> {:error, {:proven_unavailable, "proven certificate verification failed"}} end @@ -931,6 +1110,7 @@ defmodule VeriSim.Query.VQLExecutor do defp extract_proof_type(%{proofType: type}), do: normalize_proof_type(type) defp extract_proof_type(%{TAG: tag}), do: normalize_proof_type(tag) + defp extract_proof_type(%{raw: raw}) when is_binary(raw) do # Raw proof strings look like "EXISTENCE(entity-001)" or "EXISTENCE entity-001". # Extract just the proof type name (before any parens or whitespace). @@ -939,6 +1119,7 @@ defmodule VeriSim.Query.VQLExecutor do |> List.first() |> normalize_proof_type() end + defp extract_proof_type(_), do: :unknown defp normalize_proof_type("EXISTENCE"), do: :existence @@ -954,6 +1135,7 @@ defmodule VeriSim.Query.VQLExecutor do defp normalize_proof_type("SANCTIFY"), do: :sanctify defp normalize_proof_type(%{TAG: tag}), do: normalize_proof_type(tag) defp normalize_proof_type(atom) when is_atom(atom), do: atom + defp normalize_proof_type(str) when is_binary(str) do try do String.downcase(str) |> String.to_existing_atom() @@ -961,10 +1143,12 @@ defmodule VeriSim.Query.VQLExecutor do ArgumentError -> :unknown end end + defp normalize_proof_type(_), do: :unknown defp extract_contract_name(%{contractName: name}), do: name defp extract_contract_name(%{contract: name}), do: name + defp extract_contract_name(%{raw: raw}) when is_binary(raw) do # Extract contract name from raw proof spec: "INTEGRITY(my_contract)" case Regex.run(~r/\(([^)]+)\)/, raw) do @@ -972,6 +1156,7 @@ defmodule VeriSim.Query.VQLExecutor do _ -> nil end end + defp extract_contract_name(_), do: nil defp validate_contract_exists(contract_name) do @@ -981,10 +1166,13 @@ defmodule VeriSim.Query.VQLExecutor do case RustClient.search_text("contract:#{contract_name}", 1) do {:ok, results} when is_list(results) and length(results) > 0 -> {:ok, %{type: :contract_verified, contract: contract_name, verified: true}} + {:ok, %{"results" => [_ | _]}} -> {:ok, %{type: :contract_verified, contract: contract_name, verified: true}} + {:ok, _} -> {:error, {:contract_not_found, contract_name}} + {:error, reason} -> {:error, {:contract_verification_failed, reason}} end @@ -1020,7 +1208,15 @@ defmodule VeriSim.Query.VQLExecutor do end end - defp execute_federation_query(pattern, drift_policy, modalities, where_clause, limit, offset, timeout) do + defp execute_federation_query( + pattern, + drift_policy, + modalities, + where_clause, + limit, + offset, + timeout + ) do Logger.info("Federation query: pattern=#{inspect(pattern)}, drift=#{inspect(drift_policy)}") # Delegate to Rust federation API which handles peer discovery and fan-out @@ -1034,15 +1230,18 @@ defmodule VeriSim.Query.VQLExecutor do } # Add query parameters if WHERE clause exists - federation_params = if where_clause do - text = case where_clause do - %{raw: raw} -> raw - _ -> nil + federation_params = + if where_clause do + text = + case where_clause do + %{raw: raw} -> raw + _ -> nil + end + + if text, do: Map.put(federation_params, :text_query, text), else: federation_params + else + federation_params end - if text, do: Map.put(federation_params, :text_query, text), else: federation_params - else - federation_params - end case RustClient.post("/federation/query", federation_params) do {:ok, %{status: 200, body: body}} when is_list(body) -> @@ -1117,48 +1316,79 @@ defmodule VeriSim.Query.VQLExecutor do search_limit = limit || 20 # Search stored queries by text similarity - result = if text_query != "" do - case RustClient.post("/search/text", %{q: "type:vql_query #{text_query}", limit: search_limit}) do - {:ok, %{status: 200, body: %{"results" => results}}} -> {:ok, results} - {:ok, %{status: 200, body: body}} when is_list(body) -> {:ok, body} - {:ok, %{status: 200, body: body}} when is_map(body) -> {:ok, Map.get(body, "results", [])} - {:error, reason} -> {:error, {:reflect_query_failed, reason}} - _ -> {:ok, []} - end - else - # No text filter — list all stored queries - case RustClient.post("/search/text", %{q: "type:vql_query", limit: search_limit}) do - {:ok, %{status: 200, body: %{"results" => results}}} -> {:ok, results} - {:ok, %{status: 200, body: body}} when is_list(body) -> {:ok, body} - {:ok, %{status: 200, body: body}} when is_map(body) -> {:ok, Map.get(body, "results", [])} - {:error, reason} -> {:error, {:reflect_query_failed, reason}} - _ -> {:ok, []} + result = + if text_query != "" do + case RustClient.post("/search/text", %{ + q: "type:vql_query #{text_query}", + limit: search_limit + }) do + {:ok, %{status: 200, body: %{"results" => results}}} -> + {:ok, results} + + {:ok, %{status: 200, body: body}} when is_list(body) -> + {:ok, body} + + {:ok, %{status: 200, body: body}} when is_map(body) -> + {:ok, Map.get(body, "results", [])} + + {:error, reason} -> + {:error, {:reflect_query_failed, reason}} + + _ -> + {:ok, []} + end + else + # No text filter — list all stored queries + case RustClient.post("/search/text", %{q: "type:vql_query", limit: search_limit}) do + {:ok, %{status: 200, body: %{"results" => results}}} -> + {:ok, results} + + {:ok, %{status: 200, body: body}} when is_list(body) -> + {:ok, body} + + {:ok, %{status: 200, body: body}} when is_map(body) -> + {:ok, Map.get(body, "results", [])} + + {:error, reason} -> + {:error, {:reflect_query_failed, reason}} + + _ -> + {:ok, []} + end end - end # Enrich results with query-specific metadata case result do {:ok, rows} -> - enriched = Enum.map(rows, fn row -> - row - |> Map.put("_source", "reflect") - |> Map.put("_type", "stored_query") - |> maybe_filter_modalities(modalities) - end) + enriched = + Enum.map(rows, fn row -> + row + |> Map.put("_source", "reflect") + |> Map.put("_type", "stored_query") + |> maybe_filter_modalities(modalities) + end) + {:ok, enriched} - error -> error + error -> + error end end defp maybe_filter_modalities(row, [:all]), do: row + defp maybe_filter_modalities(row, modalities) do mod_strings = Enum.map(modalities, &to_string/1) base_keys = ["id", "_source", "_type", "status"] keep_keys = base_keys ++ mod_strings - Map.take(row, keep_keys ++ Map.keys(row) |> Enum.filter(fn k -> - Enum.any?(mod_strings, &String.starts_with?(k, &1)) - end)) + + Map.take( + row, + (keep_keys ++ Map.keys(row)) + |> Enum.filter(fn k -> + Enum.any?(mod_strings, &String.starts_with?(k, &1)) + end) + ) end defp filter_octad(octad, modalities, _where_clause) do @@ -1172,6 +1402,7 @@ defmodule VeriSim.Query.VQLExecutor do defp paginate_results(results, nil, nil), do: results defp paginate_results(results, limit, nil), do: Enum.take(results, limit) defp paginate_results(results, nil, offset), do: Enum.drop(results, offset) + defp paginate_results(results, limit, offset) do results |> Enum.drop(offset) @@ -1193,90 +1424,158 @@ defmodule VeriSim.Query.VQLExecutor do # AST-walking condition detectors: check TAG values for modality-specific conditions defp has_fulltext_condition?(nil), do: false + defp has_fulltext_condition?(%{raw: raw}) when is_binary(raw) do upper = String.upcase(raw) + String.contains?(upper, "FULLTEXT") or String.contains?(upper, "CONTAINS") or - String.contains?(upper, "MATCHES") + String.contains?(upper, "MATCHES") end - defp has_fulltext_condition?(%{TAG: tag}) when tag in ["FulltextContains", "FulltextMatches", "DocumentCondition"], do: true - defp has_fulltext_condition?(%{TAG: "And", _0: left, _1: right}), do: has_fulltext_condition?(left) or has_fulltext_condition?(right) - defp has_fulltext_condition?(%{TAG: "Or", _0: left, _1: right}), do: has_fulltext_condition?(left) or has_fulltext_condition?(right) + + defp has_fulltext_condition?(%{TAG: tag}) + when tag in ["FulltextContains", "FulltextMatches", "DocumentCondition"], + do: true + + defp has_fulltext_condition?(%{TAG: "And", _0: left, _1: right}), + do: has_fulltext_condition?(left) or has_fulltext_condition?(right) + + defp has_fulltext_condition?(%{TAG: "Or", _0: left, _1: right}), + do: has_fulltext_condition?(left) or has_fulltext_condition?(right) + defp has_fulltext_condition?(%{TAG: "Not", _0: inner}), do: has_fulltext_condition?(inner) defp has_fulltext_condition?(_), do: false defp has_vector_condition?(nil), do: false + defp has_vector_condition?(%{raw: raw}) when is_binary(raw) do upper = String.upcase(raw) String.contains?(upper, "SIMILAR") or String.contains?(upper, "NEAREST") end - defp has_vector_condition?(%{TAG: tag}) when tag in ["VectorSimilar", "VectorNearest", "VectorCondition"], do: true - defp has_vector_condition?(%{TAG: "And", _0: left, _1: right}), do: has_vector_condition?(left) or has_vector_condition?(right) - defp has_vector_condition?(%{TAG: "Or", _0: left, _1: right}), do: has_vector_condition?(left) or has_vector_condition?(right) + + defp has_vector_condition?(%{TAG: tag}) + when tag in ["VectorSimilar", "VectorNearest", "VectorCondition"], + do: true + + defp has_vector_condition?(%{TAG: "And", _0: left, _1: right}), + do: has_vector_condition?(left) or has_vector_condition?(right) + + defp has_vector_condition?(%{TAG: "Or", _0: left, _1: right}), + do: has_vector_condition?(left) or has_vector_condition?(right) + defp has_vector_condition?(%{TAG: "Not", _0: inner}), do: has_vector_condition?(inner) defp has_vector_condition?(_), do: false defp has_graph_pattern?(nil), do: false + defp has_graph_pattern?(%{raw: raw}) when is_binary(raw) do # Graph patterns use SPARQL-like syntax with arrow edges String.contains?(raw, "->") or String.contains?(raw, "-[") end - defp has_graph_pattern?(%{TAG: tag}) when tag in ["SparqlPattern", "PathPattern", "GraphCondition"], do: true - defp has_graph_pattern?(%{TAG: "And", _0: left, _1: right}), do: has_graph_pattern?(left) or has_graph_pattern?(right) - defp has_graph_pattern?(%{TAG: "Or", _0: left, _1: right}), do: has_graph_pattern?(left) or has_graph_pattern?(right) + + defp has_graph_pattern?(%{TAG: tag}) + when tag in ["SparqlPattern", "PathPattern", "GraphCondition"], + do: true + + defp has_graph_pattern?(%{TAG: "And", _0: left, _1: right}), + do: has_graph_pattern?(left) or has_graph_pattern?(right) + + defp has_graph_pattern?(%{TAG: "Or", _0: left, _1: right}), + do: has_graph_pattern?(left) or has_graph_pattern?(right) + defp has_graph_pattern?(%{TAG: "Not", _0: inner}), do: has_graph_pattern?(inner) defp has_graph_pattern?(_), do: false # Provenance condition detection: actor, origin, chain_valid, event_type queries defp has_provenance_condition?(nil), do: false + defp has_provenance_condition?(%{raw: raw}) when is_binary(raw) do upper = String.upcase(raw) + String.contains?(upper, "PROVENANCE.") or String.contains?(upper, "CHAIN_VALID") or - String.contains?(upper, "CHAIN_LENGTH") - end - defp has_provenance_condition?(%{TAG: tag}) when tag in [ - "ProvenanceActor", "ProvenanceOrigin", "ProvenanceChainValid", - "ProvenanceEventType", "ProvenanceCondition" - ], do: true - defp has_provenance_condition?(%{TAG: "And", _0: left, _1: right}), do: has_provenance_condition?(left) or has_provenance_condition?(right) - defp has_provenance_condition?(%{TAG: "Or", _0: left, _1: right}), do: has_provenance_condition?(left) or has_provenance_condition?(right) + String.contains?(upper, "CHAIN_LENGTH") + end + + defp has_provenance_condition?(%{TAG: tag}) + when tag in [ + "ProvenanceActor", + "ProvenanceOrigin", + "ProvenanceChainValid", + "ProvenanceEventType", + "ProvenanceCondition" + ], + do: true + + defp has_provenance_condition?(%{TAG: "And", _0: left, _1: right}), + do: has_provenance_condition?(left) or has_provenance_condition?(right) + + defp has_provenance_condition?(%{TAG: "Or", _0: left, _1: right}), + do: has_provenance_condition?(left) or has_provenance_condition?(right) + defp has_provenance_condition?(%{TAG: "Not", _0: inner}), do: has_provenance_condition?(inner) - defp has_provenance_condition?(%{modality: mod}) when mod in [:provenance, "PROVENANCE", "provenance"], do: true + + defp has_provenance_condition?(%{modality: mod}) + when mod in [:provenance, "PROVENANCE", "provenance"], + do: true + defp has_provenance_condition?(_), do: false # Spatial condition detection: radius, bounding box, nearest queries defp has_spatial_condition?(nil), do: false + defp has_spatial_condition?(%{raw: raw}) when is_binary(raw) do upper = String.upcase(raw) + String.contains?(upper, "WITHIN RADIUS") or String.contains?(upper, "WITHIN BOUNDS") or - String.contains?(upper, "SPATIAL.") or String.contains?(upper, "NEAREST") - end - defp has_spatial_condition?(%{TAG: tag}) when tag in [ - "SpatialRadius", "SpatialBounds", "SpatialNearest", - "SpatialCondition", "WithinRadius", "WithinBounds" - ], do: true - defp has_spatial_condition?(%{TAG: "And", _0: left, _1: right}), do: has_spatial_condition?(left) or has_spatial_condition?(right) - defp has_spatial_condition?(%{TAG: "Or", _0: left, _1: right}), do: has_spatial_condition?(left) or has_spatial_condition?(right) + String.contains?(upper, "SPATIAL.") or String.contains?(upper, "NEAREST") + end + + defp has_spatial_condition?(%{TAG: tag}) + when tag in [ + "SpatialRadius", + "SpatialBounds", + "SpatialNearest", + "SpatialCondition", + "WithinRadius", + "WithinBounds" + ], + do: true + + defp has_spatial_condition?(%{TAG: "And", _0: left, _1: right}), + do: has_spatial_condition?(left) or has_spatial_condition?(right) + + defp has_spatial_condition?(%{TAG: "Or", _0: left, _1: right}), + do: has_spatial_condition?(left) or has_spatial_condition?(right) + defp has_spatial_condition?(%{TAG: "Not", _0: inner}), do: has_spatial_condition?(inner) - defp has_spatial_condition?(%{modality: mod}) when mod in [:spatial, "SPATIAL", "spatial"], do: true + + defp has_spatial_condition?(%{modality: mod}) when mod in [:spatial, "SPATIAL", "spatial"], + do: true + defp has_spatial_condition?(_), do: false # AST-walking query extractors: pull actual values from parsed conditions defp extract_text_query(nil), do: "" + defp extract_text_query(%{raw: raw}) when is_binary(raw) do # Extract text between quotes from raw WHERE clause: FULLTEXT CONTAINS 'search terms' case Regex.run(~r/'([^']*)'/, raw) do - [_, text] -> text + [_, text] -> + text + _ -> # Try without quotes: FULLTEXT CONTAINS keyword case Regex.run(~r/(?:CONTAINS|MATCHES)\s+(.+?)(?:\s+AND|\s+OR|\s*$)/i, raw) do [_, text] -> String.trim(text) - _ -> raw # Use the raw clause as search text + # Use the raw clause as search text + _ -> raw end end end + defp extract_text_query(%{TAG: "FulltextContains", _0: text}), do: text defp extract_text_query(%{TAG: "FulltextMatches", _0: pattern}), do: pattern + defp extract_text_query(%{TAG: "And", _0: left, _1: right}) do case {has_fulltext_condition?(left), has_fulltext_condition?(right)} do {true, _} -> extract_text_query(left) @@ -1284,42 +1583,54 @@ defmodule VeriSim.Query.VQLExecutor do _ -> "" end end + defp extract_text_query(_), do: "" defp extract_vector_query(nil), do: {[], 0.9} + defp extract_vector_query(%{raw: raw}) when is_binary(raw) do # Extract vector literal [0.1, 0.2, ...] and optional WITHIN threshold - vector = case Regex.run(~r/\[([0-9.,\s-]+)\]/, raw) do - [_, nums] -> - nums - |> String.split(",") - |> Enum.map(&String.trim/1) - |> Enum.flat_map(fn s -> - case Float.parse(s) do - {f, _} -> [f] - :error -> [] + vector = + case Regex.run(~r/\[([0-9.,\s-]+)\]/, raw) do + [_, nums] -> + nums + |> String.split(",") + |> Enum.map(&String.trim/1) + |> Enum.flat_map(fn s -> + case Float.parse(s) do + {f, _} -> [f] + :error -> [] + end + end) + + _ -> + [] + end + + threshold = + case Regex.run(~r/WITHIN\s+([0-9.]+)/i, raw) do + [_, t] -> + case Float.parse(t) do + {f, _} -> f + :error -> 0.9 end - end) - _ -> [] - end - threshold = case Regex.run(~r/WITHIN\s+([0-9.]+)/i, raw) do - [_, t] -> - case Float.parse(t) do - {f, _} -> f - :error -> 0.9 - end - _ -> 0.9 - end + _ -> + 0.9 + end {vector, threshold} end + defp extract_vector_query(%{TAG: "VectorSimilar", _0: _field, _1: vector, _2: threshold}) do {vector, threshold || 0.9} end + defp extract_vector_query(%{TAG: "VectorNearest", _0: _field, _1: k}) do - {[], k} # k-nearest doesn't have a vector, uses the field's own embedding + # k-nearest doesn't have a vector, uses the field's own embedding + {[], k} end + defp extract_vector_query(%{TAG: "And", _0: left, _1: right}) do case {has_vector_condition?(left), has_vector_condition?(right)} do {true, _} -> extract_vector_query(left) @@ -1327,19 +1638,24 @@ defmodule VeriSim.Query.VQLExecutor do _ -> {[], 0.9} end end + defp extract_vector_query(_), do: {[], 0.9} defp extract_graph_query(nil), do: %{} + defp extract_graph_query(%{raw: raw}) when is_binary(raw) do # Parse simple SPARQL-like patterns from raw WHERE clause %{raw_pattern: raw} end + defp extract_graph_query(%{TAG: "SparqlPattern", _0: node1, _1: edge, _2: node2}) do %{subject: node1, predicate: edge, object: node2} end + defp extract_graph_query(%{TAG: "PathPattern", _0: start, _1: edge, _2: finish}) do %{start: start, edge: edge, finish: finish, traversal: true} end + defp extract_graph_query(%{TAG: "And", _0: left, _1: right}) do case {has_graph_pattern?(left), has_graph_pattern?(right)} do {true, _} -> extract_graph_query(left) @@ -1347,6 +1663,7 @@ defmodule VeriSim.Query.VQLExecutor do _ -> %{} end end + defp extract_graph_query(_), do: %{} # --------------------------------------------------------------------------- @@ -1354,46 +1671,59 @@ defmodule VeriSim.Query.VQLExecutor do # --------------------------------------------------------------------------- defp extract_provenance_query(nil), do: %{} + defp extract_provenance_query(%{raw: raw}) when is_binary(raw) do params = %{} # Extract actor filter: PROVENANCE.actor = 'someone' - params = case Regex.run(~r/(?:PROVENANCE\.)?actor\s*=\s*'([^']+)'/i, raw) do - [_, actor] -> Map.put(params, :actor, actor) - _ -> params - end + params = + case Regex.run(~r/(?:PROVENANCE\.)?actor\s*=\s*'([^']+)'/i, raw) do + [_, actor] -> Map.put(params, :actor, actor) + _ -> params + end # Extract origin filter: PROVENANCE.origin = 'source' - params = case Regex.run(~r/(?:PROVENANCE\.)?origin\s*=\s*'([^']+)'/i, raw) do - [_, origin] -> Map.put(params, :origin, origin) - _ -> params - end + params = + case Regex.run(~r/(?:PROVENANCE\.)?origin\s*=\s*'([^']+)'/i, raw) do + [_, origin] -> Map.put(params, :origin, origin) + _ -> params + end # Extract chain_valid filter: PROVENANCE.chain_valid = true - params = case Regex.run(~r/(?:PROVENANCE\.)?chain_valid\s*=\s*(true|false)/i, raw) do - [_, val] -> Map.put(params, :chain_valid, String.downcase(val) == "true") - _ -> params - end + params = + case Regex.run(~r/(?:PROVENANCE\.)?chain_valid\s*=\s*(true|false)/i, raw) do + [_, val] -> Map.put(params, :chain_valid, String.downcase(val) == "true") + _ -> params + end # Extract event_type filter: PROVENANCE.event_type = 'Modified' - params = case Regex.run(~r/(?:PROVENANCE\.)?event_type\s*=\s*'([^']+)'/i, raw) do - [_, et] -> Map.put(params, :event_type, et) - _ -> params - end + params = + case Regex.run(~r/(?:PROVENANCE\.)?event_type\s*=\s*'([^']+)'/i, raw) do + [_, et] -> Map.put(params, :event_type, et) + _ -> params + end params end + defp extract_provenance_query(%{TAG: "ProvenanceActor", _0: actor}), do: %{actor: actor} defp extract_provenance_query(%{TAG: "ProvenanceOrigin", _0: origin}), do: %{origin: origin} - defp extract_provenance_query(%{TAG: "ProvenanceChainValid", _0: valid}), do: %{chain_valid: valid} - defp extract_provenance_query(%{TAG: "ProvenanceEventType", _0: event_type}), do: %{event_type: event_type} + + defp extract_provenance_query(%{TAG: "ProvenanceChainValid", _0: valid}), + do: %{chain_valid: valid} + + defp extract_provenance_query(%{TAG: "ProvenanceEventType", _0: event_type}), + do: %{event_type: event_type} + defp extract_provenance_query(%{TAG: "And", _0: left, _1: right}) do Map.merge(extract_provenance_query(left), extract_provenance_query(right)) end + defp extract_provenance_query(%{modality: mod, field: field, value: value}) - when mod in [:provenance, "PROVENANCE", "provenance"] do + when mod in [:provenance, "PROVENANCE", "provenance"] do %{String.to_existing_atom(field) => value} end + defp extract_provenance_query(_), do: %{} defp execute_provenance_query(params, limit) do @@ -1427,16 +1757,23 @@ defmodule VeriSim.Query.VQLExecutor do # Verify chain integrity across all provenance chains case RustClient.get("/provenance/verify-all") do {:ok, %{status: 200, body: body}} when is_list(body) -> - filtered = if params.chain_valid do - Enum.filter(body, &(&1["chain_valid"] == true)) - else - Enum.filter(body, &(&1["chain_valid"] == false)) - end + filtered = + if params.chain_valid do + Enum.filter(body, &(&1["chain_valid"] == true)) + else + Enum.filter(body, &(&1["chain_valid"] == false)) + end + {:ok, wrap_provenance_results(filtered)} + {:ok, %{status: 200, body: body}} when is_map(body) -> {:ok, wrap_provenance_results(Map.get(body, "results", []))} - {:error, reason} -> {:error, {:provenance_query_failed, reason}} - _ -> {:ok, []} + + {:error, reason} -> + {:error, {:provenance_query_failed, reason}} + + _ -> + {:ok, []} end true -> @@ -1454,10 +1791,12 @@ defmodule VeriSim.Query.VQLExecutor do %{"provenance" => item, "_source" => "provenance"} end) end + defp wrap_provenance_results(body) when is_map(body) do results = Map.get(body, "results", [body]) wrap_provenance_results(results) end + defp wrap_provenance_results(_), do: [] # --------------------------------------------------------------------------- @@ -1465,56 +1804,86 @@ defmodule VeriSim.Query.VQLExecutor do # --------------------------------------------------------------------------- defp extract_spatial_query(nil), do: %{} + defp extract_spatial_query(%{raw: raw}) when is_binary(raw) do upper = String.upcase(raw) cond do # WITHIN RADIUS(lat, lon, radius_km) String.contains?(upper, "WITHIN RADIUS") -> - case Regex.run(~r/WITHIN\s+RADIUS\s*\(\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9.]+)\s*\)/i, raw) do + case Regex.run( + ~r/WITHIN\s+RADIUS\s*\(\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9.]+)\s*\)/i, + raw + ) do [_, lat, lon, radius] -> - %{type: :radius, + %{ + type: :radius, latitude: parse_float_safe(lat), longitude: parse_float_safe(lon), - radius_km: parse_float_safe(radius)} - _ -> %{} + radius_km: parse_float_safe(radius) + } + + _ -> + %{} end # WITHIN BOUNDS(min_lat, min_lon, max_lat, max_lon) String.contains?(upper, "WITHIN BOUNDS") -> - case Regex.run(~r/WITHIN\s+BOUNDS\s*\(\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*\)/i, raw) do + case Regex.run( + ~r/WITHIN\s+BOUNDS\s*\(\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*\)/i, + raw + ) do [_, min_lat, min_lon, max_lat, max_lon] -> - %{type: :bounds, + %{ + type: :bounds, min_lat: parse_float_safe(min_lat), min_lon: parse_float_safe(min_lon), max_lat: parse_float_safe(max_lat), - max_lon: parse_float_safe(max_lon)} - _ -> %{} + max_lon: parse_float_safe(max_lon) + } + + _ -> + %{} end # NEAREST(lat, lon, k) String.contains?(upper, "NEAREST") -> case Regex.run(~r/NEAREST\s*\(\s*([0-9.\-]+)\s*,\s*([0-9.\-]+)\s*,\s*([0-9]+)\s*\)/i, raw) do [_, lat, lon, k] -> - %{type: :nearest, + %{ + type: :nearest, latitude: parse_float_safe(lat), longitude: parse_float_safe(lon), - k: String.to_integer(k)} - _ -> %{} + k: String.to_integer(k) + } + + _ -> + %{} end - true -> %{} + true -> + %{} end end + defp extract_spatial_query(%{TAG: "SpatialRadius", _0: lat, _1: lon, _2: radius}) do %{type: :radius, latitude: lat, longitude: lon, radius_km: radius} end - defp extract_spatial_query(%{TAG: "SpatialBounds", _0: min_lat, _1: min_lon, _2: max_lat, _3: max_lon}) do + + defp extract_spatial_query(%{ + TAG: "SpatialBounds", + _0: min_lat, + _1: min_lon, + _2: max_lat, + _3: max_lon + }) do %{type: :bounds, min_lat: min_lat, min_lon: min_lon, max_lat: max_lat, max_lon: max_lon} end + defp extract_spatial_query(%{TAG: "SpatialNearest", _0: lat, _1: lon, _2: k}) do %{type: :nearest, latitude: lat, longitude: lon, k: k} end + defp extract_spatial_query(%{TAG: "And", _0: left, _1: right}) do case {has_spatial_condition?(left), has_spatial_condition?(right)} do {true, _} -> extract_spatial_query(left) @@ -1522,6 +1891,7 @@ defmodule VeriSim.Query.VQLExecutor do _ -> %{} end end + defp extract_spatial_query(_), do: %{} defp execute_spatial_query(params, limit) do @@ -1563,20 +1933,22 @@ defmodule VeriSim.Query.VQLExecutor do end _ -> - {:error, {:invalid_spatial_query, "Missing spatial query type (radius, bounds, or nearest)"}} + {:error, + {:invalid_spatial_query, "Missing spatial query type (radius, bounds, or nearest)"}} end end defp wrap_spatial_results(body) when is_list(body) do Enum.map(body, fn item -> - %{"spatial" => item, "_source" => "spatial", - "distance_km" => Map.get(item, "distance_km")} + %{"spatial" => item, "_source" => "spatial", "distance_km" => Map.get(item, "distance_km")} end) end + defp wrap_spatial_results(body) when is_map(body) do results = Map.get(body, "results", [body]) wrap_spatial_results(results) end + defp wrap_spatial_results(_), do: [] defp parse_float_safe(str) when is_binary(str) do @@ -1585,6 +1957,7 @@ defmodule VeriSim.Query.VQLExecutor do :error -> 0.0 end end + defp parse_float_safe(num) when is_number(num), do: num / 1 defp parse_float_safe(_), do: 0.0 @@ -1615,54 +1988,89 @@ defmodule VeriSim.Query.VQLExecutor do steps = [%{operation: "Parse VQL", cost_ms: 1, notes: "Already completed"} | steps] # Step 2: Type check (if proofs present) - steps = if proof_specs do - proof_count = length(proof_specs) - [%{operation: "Type check + proof verification", cost_ms: 5 * proof_count, - notes: "#{proof_count} proof obligation(s)"} | steps] - else - steps - end + steps = + if proof_specs do + proof_count = length(proof_specs) + + [ + %{ + operation: "Type check + proof verification", + cost_ms: 5 * proof_count, + notes: "#{proof_count} proof obligation(s)" + } + | steps + ] + else + steps + end # Step 3: Route to stores - {source_type, source_cost, source_notes} = case source do - {:octad, id} -> {"Octad lookup", 2, "Direct ID: #{id}"} - {:federation, pattern, _} -> {"Federation fan-out", 100, "Pattern: #{inspect(pattern)}"} - {:store, id} -> {"Store query", 15, "Store: #{id}"} - :reflect -> {"REFLECT (meta-query)", 20, "Query the query store itself"} - end + {source_type, source_cost, source_notes} = + case source do + {:octad, id} -> + {"Octad lookup", 2, "Direct ID: #{id}"} + + {:federation, pattern, _} -> + {"Federation fan-out", 100, "Pattern: #{inspect(pattern)}"} + + {:store, id} -> + {"Store query", 15, "Store: #{id}"} + + :reflect -> + {"REFLECT (meta-query)", 20, "Query the query store itself"} + end + steps = [%{operation: source_type, cost_ms: source_cost, notes: source_notes} | steps] # Step 4: Modality queries modality_cost = length(modalities) * 10 - steps = [%{operation: "Query #{length(modalities)} modality store(s)", - cost_ms: modality_cost, - modalities: Enum.map(modalities, &to_string/1)} | steps] + + steps = [ + %{ + operation: "Query #{length(modalities)} modality store(s)", + cost_ms: modality_cost, + modalities: Enum.map(modalities, &to_string/1) + } + | steps + ] # Step 5: Where clause evaluation where_cost = if where_clause, do: 5, else: 0 - steps = if where_clause do - query_type = determine_query_type(modalities, where_clause) - [%{operation: "Evaluate WHERE (#{query_type})", cost_ms: where_cost} | steps] - else - steps - end + + steps = + if where_clause do + query_type = determine_query_type(modalities, where_clause) + [%{operation: "Evaluate WHERE (#{query_type})", cost_ms: where_cost} | steps] + else + steps + end # Step 6: Cross-modal evaluation (if any) - steps = if cross_modal != [] do - [%{operation: "Cross-modal evaluation", cost_ms: 20 * length(cross_modal), - conditions: length(cross_modal), - notes: "Post-fetch filter across modalities"} | steps] - else - steps - end + steps = + if cross_modal != [] do + [ + %{ + operation: "Cross-modal evaluation", + cost_ms: 20 * length(cross_modal), + conditions: length(cross_modal), + notes: "Post-fetch filter across modalities" + } + | steps + ] + else + steps + end # Step 7: Aggregation (if GROUP BY present) - steps = if group_by do - [%{operation: "Group + Aggregate", cost_ms: 8, - group_fields: length(group_by)} | steps] - else - steps - end + steps = + if group_by do + [ + %{operation: "Group + Aggregate", cost_ms: 8, group_fields: length(group_by)} + | steps + ] + else + steps + end steps = Enum.reverse(steps) total = Enum.reduce(steps, 0, fn step, acc -> acc + Map.get(step, :cost_ms, 0) end) @@ -1684,15 +2092,18 @@ defmodule VeriSim.Query.VQLExecutor do defp maybe_group_and_aggregate(rows, nil, _aggregates), do: rows defp maybe_group_and_aggregate(rows, _group_by, nil), do: rows + defp maybe_group_and_aggregate(rows, group_by, aggregates) do - grouped = Enum.group_by(rows, fn row -> - Enum.map(group_by, fn %{modality: mod, field: field} -> - get_in(row, [to_string(mod), field]) + grouped = + Enum.group_by(rows, fn row -> + Enum.map(group_by, fn %{modality: mod, field: field} -> + get_in(row, [to_string(mod), field]) + end) end) - end) Enum.map(grouped, fn {group_key, group_rows} -> - base = group_by + base = + group_by |> Enum.zip(group_key) |> Enum.into(%{}, fn {%{modality: mod, field: field}, val} -> {"#{mod}.#{field}", val} @@ -1704,18 +2115,28 @@ defmodule VeriSim.Query.VQLExecutor do Map.put(acc, "COUNT(*)", length(group_rows)) {:aggregate_field, func, %{modality: mod, field: field}} -> - values = Enum.map(group_rows, fn row -> - get_in(row, [to_string(mod), field]) || 0 - end) - - result = case func do - :count -> length(values) - :sum -> Enum.sum(values) - :avg -> - if length(values) > 0, do: Enum.sum(values) / length(values), else: 0 - :min -> Enum.min(values, fn -> 0 end) - :max -> Enum.max(values, fn -> 0 end) - end + values = + Enum.map(group_rows, fn row -> + get_in(row, [to_string(mod), field]) || 0 + end) + + result = + case func do + :count -> + length(values) + + :sum -> + Enum.sum(values) + + :avg -> + if length(values) > 0, do: Enum.sum(values) / length(values), else: 0 + + :min -> + Enum.min(values, fn -> 0 end) + + :max -> + Enum.max(values, fn -> 0 end) + end label = "#{String.upcase(to_string(func))}(#{mod}.#{field})" Map.put(acc, label, result) @@ -1725,29 +2146,35 @@ defmodule VeriSim.Query.VQLExecutor do end defp maybe_order_by(rows, nil), do: rows + defp maybe_order_by(rows, order_items) do - Enum.sort_by(rows, fn row -> - Enum.map(order_items, fn %{field: %{modality: mod, field: field}} -> - get_in(row, [to_string(mod), field]) || get_in(row, ["#{mod}.#{field}"]) - end) - end, fn a, b -> - order_items - |> Enum.zip(Enum.zip(a, b)) - |> Enum.reduce_while(:eq, fn {item, {va, vb}}, _acc -> - cmp = compare_values(va, vb) - direction = Map.get(item, :direction, :asc) - effective = if direction == :desc, do: invert_cmp(cmp), else: cmp - case effective do - :eq -> {:cont, :eq} - :lt -> {:halt, true} - :gt -> {:halt, false} + Enum.sort_by( + rows, + fn row -> + Enum.map(order_items, fn %{field: %{modality: mod, field: field}} -> + get_in(row, [to_string(mod), field]) || get_in(row, ["#{mod}.#{field}"]) + end) + end, + fn a, b -> + order_items + |> Enum.zip(Enum.zip(a, b)) + |> Enum.reduce_while(:eq, fn {item, {va, vb}}, _acc -> + cmp = compare_values(va, vb) + direction = Map.get(item, :direction, :asc) + effective = if direction == :desc, do: invert_cmp(cmp), else: cmp + + case effective do + :eq -> {:cont, :eq} + :lt -> {:halt, true} + :gt -> {:halt, false} + end + end) + |> case do + :eq -> true + bool -> bool end - end) - |> case do - :eq -> true - bool -> bool end - end) + ) end defp compare_values(a, b) when is_number(a) and is_number(b) do @@ -1757,6 +2184,7 @@ defmodule VeriSim.Query.VQLExecutor do true -> :eq end end + defp compare_values(a, b) when is_binary(a) and is_binary(b) do cond do a < b -> :lt @@ -1764,6 +2192,7 @@ defmodule VeriSim.Query.VQLExecutor do true -> :eq end end + defp compare_values(_a, _b), do: :eq defp invert_cmp(:lt), do: :gt @@ -1771,6 +2200,7 @@ defmodule VeriSim.Query.VQLExecutor do defp invert_cmp(:eq), do: :eq defp maybe_project_columns(rows, nil), do: rows + defp maybe_project_columns(rows, projections) do Enum.map(rows, fn row -> Enum.into(projections, %{}, fn %{modality: mod, field: field} -> @@ -1785,6 +2215,7 @@ defmodule VeriSim.Query.VQLExecutor do # --------------------------------------------------------------------------- defp extract_modalities(query_ast), do: Map.get(query_ast, :modalities, [:all]) + defp extract_source(query_ast) do case Map.get(query_ast, :source, {:octad, "default"}) do %{TAG: "Reflect"} -> :reflect @@ -1793,13 +2224,15 @@ defmodule VeriSim.Query.VQLExecutor do other -> other end end + defp extract_where(query_ast), do: Map.get(query_ast, :where, nil) defp extract_proof(query_ast) do case Map.get(query_ast, :proof, nil) do nil -> nil proof when is_list(proof) -> proof - proof when is_map(proof) -> [proof] # backward compat: single proof → list + # backward compat: single proof → list + proof when is_map(proof) -> [proof] end end diff --git a/elixir-orchestration/lib/verisim/query/vql_type_checker.ex b/elixir-orchestration/lib/verisim/query/vql_type_checker.ex index eb23298..9d90cc6 100644 --- a/elixir-orchestration/lib/verisim/query/vql_type_checker.ex +++ b/elixir-orchestration/lib/verisim/query/vql_type_checker.ex @@ -131,15 +131,16 @@ defmodule VeriSim.Query.VQLTypeChecker do :ok <- validate_modality_compatibility(proof_specs, modalities), {:ok, obligations} <- generate_obligations(proof_specs), {:ok, composition} <- determine_composition(obligations) do - total_ms = Enum.reduce(obligations, 0, & &1.estimated_time_ms + &2) - - {:ok, %{ - proof_obligations: obligations, - composition_strategy: composition, - inferred_types: %{}, - total_estimated_ms: total_ms, - is_parallelizable: composition == :independent - }} + total_ms = Enum.reduce(obligations, 0, &(&1.estimated_time_ms + &2)) + + {:ok, + %{ + proof_obligations: obligations, + composition_strategy: composition, + inferred_types: %{}, + total_estimated_ms: total_ms, + is_parallelizable: composition == :independent + }} end end @@ -161,9 +162,11 @@ defmodule VeriSim.Query.VQLTypeChecker do [%{proofType: "INTEGRITY", contractName: "my_contract", raw: "INTEGRITY(my_contract)"}] """ def parse_proof_specs(nil), do: [] + def parse_proof_specs(specs) when is_list(specs) do Enum.flat_map(specs, &parse_proof_specs/1) end + def parse_proof_specs(%{raw: raw}) when is_binary(raw) do # Split on AND/OR connectors (case-insensitive), preserving each proof spec raw @@ -172,6 +175,7 @@ defmodule VeriSim.Query.VQLTypeChecker do |> Enum.reject(&(&1 == "")) |> Enum.map(fn spec_str -> {proof_type, contract_name} = parse_single_proof_spec(spec_str) + %{ proofType: proof_type, contractName: contract_name, @@ -179,6 +183,7 @@ defmodule VeriSim.Query.VQLTypeChecker do } end) end + def parse_proof_specs(%{proofType: _} = spec), do: [spec] def parse_proof_specs(%{TAG: _} = spec), do: [spec] def parse_proof_specs(_), do: [] @@ -193,17 +198,21 @@ defmodule VeriSim.Query.VQLTypeChecker do defp validate_proof_specs([]) do {:error, {:missing_proof, "VQL-DT query requires at least one PROOF specification"}} end + defp validate_proof_specs(specs) do Enum.reduce_while(specs, :ok, fn spec, _acc -> proof_type = normalize_proof_type(spec) + cond do proof_type == :unknown -> raw = Map.get(spec, :raw, Map.get(spec, :proofType, inspect(spec))) {:halt, {:error, {:unknown_proof_type, "Unknown proof type: #{raw}"}}} needs_contract?(proof_type) and not has_contract?(spec) -> - {:halt, {:error, {:missing_contract, - "#{proof_type_name(proof_type)} proof requires a contract/entity reference"}}} + {:halt, + {:error, + {:missing_contract, + "#{proof_type_name(proof_type)} proof requires a contract/entity reference"}}} true -> {:cont, :ok} @@ -224,9 +233,12 @@ defmodule VeriSim.Query.VQLTypeChecker do {:cont, :ok} else required_str = required |> Enum.map(&to_string/1) |> Enum.join(", ") - {:halt, {:error, {:modality_mismatch, - "#{proof_type_name(proof_type)} proof requires #{required_str} modality " <> - "but query only selects #{inspect(modalities)}"}}} + + {:halt, + {:error, + {:modality_mismatch, + "#{proof_type_name(proof_type)} proof requires #{required_str} modality " <> + "but query only selects #{inspect(modalities)}"}}} end end) end @@ -236,27 +248,29 @@ defmodule VeriSim.Query.VQLTypeChecker do # --------------------------------------------------------------------------- defp generate_obligations(proof_specs) do - obligations = Enum.map(proof_specs, fn spec -> - proof_type = normalize_proof_type(spec) - contract = extract_contract(spec) - - %{ - type: proof_type, - proofType: proof_type |> Atom.to_string() |> String.upcase(), - contract: contract, - contractName: contract, - witness_fields: Map.get(@proof_witness_fields, proof_type, []), - circuit: circuit_for(proof_type, spec), - estimated_time_ms: Map.get(@proof_time_estimates_ms, proof_type, 200), - required_modalities: Map.get(@proof_required_modalities, proof_type, []) - } - end) + obligations = + Enum.map(proof_specs, fn spec -> + proof_type = normalize_proof_type(spec) + contract = extract_contract(spec) + + %{ + type: proof_type, + proofType: proof_type |> Atom.to_string() |> String.upcase(), + contract: contract, + contractName: contract, + witness_fields: Map.get(@proof_witness_fields, proof_type, []), + circuit: circuit_for(proof_type, spec), + estimated_time_ms: Map.get(@proof_time_estimates_ms, proof_type, 200), + required_modalities: Map.get(@proof_required_modalities, proof_type, []) + } + end) {:ok, obligations} end defp determine_composition([]), do: {:ok, :independent} defp determine_composition([_single]), do: {:ok, :independent} + defp determine_composition(obligations) do types = Enum.map(obligations, & &1.type) |> MapSet.new() @@ -286,6 +300,7 @@ defmodule VeriSim.Query.VQLTypeChecker do case Regex.run(~r/^([A-Z_]+)\(([^)]*)\)$/, String.trim(spec_str)) do [_, proof_type, contract] -> {proof_type, String.trim(contract)} + _ -> # Try space-separated: "EXISTENCE entity-001" case String.split(String.trim(spec_str), ~r/\s+/, parts: 2) do @@ -299,16 +314,19 @@ defmodule VeriSim.Query.VQLTypeChecker do defp normalize_proof_type(%{proofType: type}), do: do_normalize(type) defp normalize_proof_type(%{TAG: tag}), do: do_normalize(tag) defp normalize_proof_type(%{type: type}) when is_atom(type), do: type + defp normalize_proof_type(%{raw: raw}) when is_binary(raw) do raw |> String.split(~r/[\s(]/, parts: 2) |> List.first() |> do_normalize() end + defp normalize_proof_type(_), do: :unknown defp do_normalize(str) when is_binary(str) do str_down = String.downcase(str) + try do atom = String.to_existing_atom(str_down) if atom in @known_proof_types, do: atom, else: :unknown @@ -316,19 +334,23 @@ defmodule VeriSim.Query.VQLTypeChecker do ArgumentError -> :unknown end end + defp do_normalize(atom) when is_atom(atom) do if atom in @known_proof_types, do: atom, else: :unknown end + defp do_normalize(_), do: :unknown defp extract_contract(%{contractName: name}) when is_binary(name) and name != "", do: name defp extract_contract(%{contract: name}) when is_binary(name) and name != "", do: name + defp extract_contract(%{raw: raw}) when is_binary(raw) do case Regex.run(~r/\(([^)]+)\)/, raw) do [_, name] -> String.trim(name) _ -> nil end end + defp extract_contract(_), do: nil defp needs_contract?(type) when type in [:integrity, :citation, :custom, :sanctify], do: true @@ -342,6 +364,7 @@ defmodule VeriSim.Query.VQLTypeChecker do defp circuit_for(:custom, spec) do extract_contract(spec) || "custom-circuit" end + defp circuit_for(type, _spec), do: Map.get(@proof_circuits, type) defp proof_type_name(type) do diff --git a/elixir-orchestration/lib/verisim/rust_client.ex b/elixir-orchestration/lib/verisim/rust_client.ex index b3c8650..db8c078 100644 --- a/elixir-orchestration/lib/verisim/rust_client.ex +++ b/elixir-orchestration/lib/verisim/rust_client.ex @@ -13,7 +13,8 @@ defmodule VeriSim.RustClient do @default_base_url "http://localhost:8080/api/v1" @default_timeout 30_000 @cache_table :verisim_rust_client_cache - @cache_ttl_ms 30_000 # 30 seconds default TTL + # 30 seconds default TTL + @cache_ttl_ms 30_000 # --------------------------------------------------------------------------- # ETS Cache — transparent read-through caching for octad lookups and searches @@ -26,6 +27,7 @@ defmodule VeriSim.RustClient do if :ets.info(@cache_table) == :undefined do :ets.new(@cache_table, [:set, :public, :named_table, read_concurrency: true]) end + :ok end @@ -36,6 +38,7 @@ defmodule VeriSim.RustClient do if :ets.info(@cache_table) != :undefined do :ets.delete_all_objects(@cache_table) end + :ok end @@ -46,6 +49,7 @@ defmodule VeriSim.RustClient do if :ets.info(@cache_table) != :undefined do :ets.delete(@cache_table, key) end + :ok end @@ -59,7 +63,9 @@ defmodule VeriSim.RustClient do :ets.delete(@cache_table, key) :miss end - _ -> :miss + + _ -> + :miss end else :miss @@ -71,6 +77,7 @@ defmodule VeriSim.RustClient do expiry = System.monotonic_time(:millisecond) + ttl_ms :ets.insert(@cache_table, {key, value, expiry}) end + value end @@ -93,8 +100,10 @@ defmodule VeriSim.RustClient do case get("/health") do {:ok, %{status: 200, body: body}} -> {:ok, body} + {:ok, %{status: status}} -> {:error, {:unhealthy, status}} + {:error, reason} -> {:error, reason} end @@ -121,15 +130,23 @@ defmodule VeriSim.RustClient do cache_key = {:octad, entity_id} case cache_get(cache_key) do - {:hit, cached} -> {:ok, cached} + {:hit, cached} -> + {:ok, cached} + :miss -> case get("/octads/#{entity_id}") do {:ok, %{status: 200, body: body}} -> cache_put(cache_key, body) {:ok, body} - {:ok, %{status: 404}} -> {:error, :not_found} - {:ok, %{status: status, body: body}} -> {:error, {status, body}} - {:error, reason} -> {:error, reason} + + {:ok, %{status: 404}} -> + {:error, :not_found} + + {:ok, %{status: status, body: body}} -> + {:error, {status, body}} + + {:error, reason} -> + {:error, reason} end end end @@ -143,9 +160,15 @@ defmodule VeriSim.RustClient do invalidate_cache({:octad, entity_id}) invalidate_cache({:drift, entity_id}) {:ok, body} - {:ok, %{status: 404}} -> {:error, :not_found} - {:ok, %{status: status, body: body}} -> {:error, {status, body}} - {:error, reason} -> {:error, reason} + + {:ok, %{status: 404}} -> + {:error, :not_found} + + {:ok, %{status: status, body: body}} -> + {:error, {status, body}} + + {:error, reason} -> + {:error, reason} end end @@ -158,13 +181,20 @@ defmodule VeriSim.RustClient do invalidate_cache({:octad, entity_id}) invalidate_cache({:drift, entity_id}) :ok + {:ok, %{status: 200}} -> invalidate_cache({:octad, entity_id}) invalidate_cache({:drift, entity_id}) :ok - {:ok, %{status: 404}} -> {:error, :not_found} - {:ok, %{status: status, body: body}} -> {:error, {status, body}} - {:error, reason} -> {:error, reason} + + {:ok, %{status: 404}} -> + {:error, :not_found} + + {:ok, %{status: status, body: body}} -> + {:error, {status, body}} + + {:error, reason} -> + {:error, reason} end end @@ -213,15 +243,24 @@ defmodule VeriSim.RustClient do cache_key = {:drift, entity_id} case cache_get(cache_key) do - {:hit, cached} -> {:ok, cached} + {:hit, cached} -> + {:ok, cached} + :miss -> case get("/drift/entity/#{entity_id}") do {:ok, %{status: 200, body: %{"score" => score}}} -> - cache_put(cache_key, score, 10_000) # Short TTL — drift changes frequently + # Short TTL — drift changes frequently + cache_put(cache_key, score, 10_000) {:ok, score} - {:ok, %{status: 404}} -> {:ok, 0.0} - {:ok, %{status: status, body: body}} -> {:error, {status, body}} - {:error, reason} -> {:error, reason} + + {:ok, %{status: 404}} -> + {:ok, 0.0} + + {:ok, %{status: status, body: body}} -> + {:error, {status, body}} + + {:error, reason} -> + {:error, reason} end end end @@ -382,10 +421,10 @@ defmodule VeriSim.RustClient do url = base_url() <> path case Req.get(url, - params: params, - receive_timeout: timeout(), - decode_body: true - ) do + params: params, + receive_timeout: timeout(), + decode_body: true + ) do {:ok, resp} -> validate_json_response(resp) {:error, reason} -> {:error, reason} end @@ -397,10 +436,10 @@ defmodule VeriSim.RustClient do url = base_url() <> path case Req.post(url, - json: body, - receive_timeout: timeout(), - decode_body: true - ) do + json: body, + receive_timeout: timeout(), + decode_body: true + ) do {:ok, resp} -> validate_json_response(resp) {:error, reason} -> {:error, reason} end @@ -412,10 +451,10 @@ defmodule VeriSim.RustClient do url = base_url() <> path case Req.put(url, - json: body, - receive_timeout: timeout(), - decode_body: true - ) do + json: body, + receive_timeout: timeout(), + decode_body: true + ) do {:ok, resp} -> validate_json_response(resp) {:error, reason} -> {:error, reason} end @@ -427,9 +466,9 @@ defmodule VeriSim.RustClient do url = base_url() <> path case Req.delete(url, - receive_timeout: timeout(), - decode_body: true - ) do + receive_timeout: timeout(), + decode_body: true + ) do {:ok, resp} -> validate_json_response(resp) {:error, reason} -> {:error, reason} end @@ -448,7 +487,10 @@ defmodule VeriSim.RustClient do defp validate_json_response(%{status: status, body: body}) when is_binary(body) and status >= 200 and status < 300 do - Logger.warning("Rust core returned non-JSON response (status #{status}): #{String.slice(body, 0, 120)}...") + Logger.warning( + "Rust core returned non-JSON response (status #{status}): #{String.slice(body, 0, 120)}..." + ) + {:error, {:non_json_response, status, String.slice(body, 0, 500)}} end diff --git a/elixir-orchestration/lib/verisim/schema/schema_registry.ex b/elixir-orchestration/lib/verisim/schema/schema_registry.ex index 1dfaec0..960879f 100644 --- a/elixir-orchestration/lib/verisim/schema/schema_registry.ex +++ b/elixir-orchestration/lib/verisim/schema/schema_registry.ex @@ -159,7 +159,11 @@ defmodule VeriSim.SchemaRegistry do label: "Document", supertypes: ["verisim:Entity"], constraints: [ - %{name: "title_required", kind: {:required, "title"}, message: "Documents must have a title"} + %{ + name: "title_required", + kind: {:required, "title"}, + message: "Documents must have a title" + } ] }, %{ @@ -197,6 +201,7 @@ defmodule VeriSim.SchemaRegistry do defp validate_constraint(entity, %{kind: {:required, property}, message: msg}) do properties = Map.get(entity, :properties, %{}) + if Map.has_key?(properties, property) do :ok else @@ -206,8 +211,11 @@ defmodule VeriSim.SchemaRegistry do defp validate_constraint(entity, %{kind: {:pattern, property, pattern}, message: msg}) do properties = Map.get(entity, :properties, %{}) + case Map.get(properties, property) do - nil -> :ok + nil -> + :ok + value -> if Regex.match?(~r/#{pattern}/, value) do :ok @@ -219,15 +227,20 @@ defmodule VeriSim.SchemaRegistry do defp validate_constraint(entity, %{kind: {:range, property, min, max}, message: msg}) do properties = Map.get(entity, :properties, %{}) + case Map.get(properties, property) do - nil -> :ok + nil -> + :ok + value when is_number(value) -> if (is_nil(min) or value >= min) and (is_nil(max) or value <= max) do :ok else {:error, msg} end - _ -> :ok + + _ -> + :ok end end @@ -241,10 +254,13 @@ defmodule VeriSim.SchemaRegistry do defp compute_hierarchy(iri, types, visited) do if iri in visited do - [] # Prevent cycles + # Prevent cycles + [] else case Map.get(types, iri) do - nil -> [] + nil -> + [] + type_def -> supertypes = type_def.supertypes || [] [iri | Enum.flat_map(supertypes, &compute_hierarchy(&1, types, [iri | visited]))] diff --git a/elixir-orchestration/lib/verisim/telemetry.ex b/elixir-orchestration/lib/verisim/telemetry.ex index d0543ae..43acdbb 100644 --- a/elixir-orchestration/lib/verisim/telemetry.ex +++ b/elixir-orchestration/lib/verisim/telemetry.ex @@ -211,13 +211,17 @@ defmodule VeriSim.Telemetry do def measure_drift_status do # Get drift status from monitor case GenServer.whereis(VeriSim.DriftMonitor) do - nil -> :ok + nil -> + :ok + _pid -> case VeriSim.DriftMonitor.status() do %{overall_health: health} -> score = health_to_score(health) :telemetry.execute([:verisim, :drift], %{score: score}, %{}) - _ -> :ok + + _ -> + :ok end end end diff --git a/elixir-orchestration/lib/verisim/telemetry/collector.ex b/elixir-orchestration/lib/verisim/telemetry/collector.ex index f774775..3ed5af0 100644 --- a/elixir-orchestration/lib/verisim/telemetry/collector.ex +++ b/elixir-orchestration/lib/verisim/telemetry/collector.ex @@ -142,17 +142,23 @@ defmodule VeriSim.Telemetry.Collector do case :ets.lookup(@table, min_key) do [{^min_key, current}] when value < current / 1000 -> :ets.insert(@table, {min_key, trunc(value * 1000)}) + [] -> :ets.insert(@table, {min_key, trunc(value * 1000)}) - _ -> :ok + + _ -> + :ok end case :ets.lookup(@table, max_key) do [{^max_key, current}] when value > current / 1000 -> :ets.insert(@table, {max_key, trunc(value * 1000)}) + [] -> :ets.insert(@table, {max_key, trunc(value * 1000)}) - _ -> :ok + + _ -> + :ok end {:noreply, state} diff --git a/elixir-orchestration/lib/verisim/telemetry/reporter.ex b/elixir-orchestration/lib/verisim/telemetry/reporter.ex index 33f5e08..d9f3d8b 100644 --- a/elixir-orchestration/lib/verisim/telemetry/reporter.ex +++ b/elixir-orchestration/lib/verisim/telemetry/reporter.ex @@ -54,7 +54,7 @@ defmodule VeriSim.Telemetry.Reporter do telemetry_enabled: Collector.enabled?(), privacy_notice: "This report contains aggregate metrics only. " <> - "No query content, entity data, or PII is included." + "No query content, entity data, or PII is included." }, modality_heatmap: modality_heatmap(snapshot), query_patterns: query_patterns(snapshot), diff --git a/elixir-orchestration/lib/verisim/transport.ex b/elixir-orchestration/lib/verisim/transport.ex index 0b1eb9f..e604c98 100644 --- a/elixir-orchestration/lib/verisim/transport.ex +++ b/elixir-orchestration/lib/verisim/transport.ex @@ -85,6 +85,7 @@ defmodule VeriSim.Transport do def create_octad(input) do if use_nif?() do json = Jason.encode!(input) + case NifBridge.create_octad(json) do result when is_binary(result) -> {:ok, Jason.decode!(result)} {:error, reason} -> {:error, reason} @@ -142,6 +143,7 @@ defmodule VeriSim.Transport do def search_vector(vector, k \\ 10) do if use_nif?() do embedding_json = Jason.encode!(vector) + case NifBridge.search_vector(embedding_json, k) do result when is_binary(result) -> {:ok, Jason.decode!(result)} {:error, reason} -> {:error, reason} diff --git a/elixir-orchestration/test/integration_test.exs b/elixir-orchestration/test/integration_test.exs index 2d37198..dfb97f4 100644 --- a/elixir-orchestration/test/integration_test.exs +++ b/elixir-orchestration/test/integration_test.exs @@ -235,9 +235,10 @@ defmodule VeriSim.IntegrationTest do assert state.version == 0 # Update entity - {:ok, new_state} = EntityServer.update(entity_id, [ - {:modality, :document, true} - ]) + {:ok, new_state} = + EntityServer.update(entity_id, [ + {:modality, :document, true} + ]) assert new_state.modalities.document == true end diff --git a/elixir-orchestration/test/support/vql_test_helpers.ex b/elixir-orchestration/test/support/vql_test_helpers.ex index 5584587..f6ba292 100644 --- a/elixir-orchestration/test/support/vql_test_helpers.ex +++ b/elixir-orchestration/test/support/vql_test_helpers.ex @@ -52,8 +52,11 @@ defmodule VeriSim.Test.VQLTestHelpers do """ def parse_statement!(query_string) do case VQLBridge.parse_statement(query_string) do - {:ok, ast} -> ast - {:error, reason} -> raise "VQL statement parse failed: #{inspect(reason)}\n Query: #{query_string}" + {:ok, ast} -> + ast + + {:error, reason} -> + raise "VQL statement parse failed: #{inspect(reason)}\n Query: #{query_string}" end end @@ -117,10 +120,15 @@ defmodule VeriSim.Test.VQLTestHelpers do {:error, reason} when is_atom(reason) -> :rust_unavailable - {:error, {tag, _}} when tag in [ - :connection_refused, :econnrefused, :connect_timeout, - :timeout, :req_error, :mint_error - ] -> + {:error, {tag, _}} + when tag in [ + :connection_refused, + :econnrefused, + :connect_timeout, + :timeout, + :req_error, + :mint_error + ] -> :rust_unavailable {:unavailable, _} -> @@ -159,9 +167,11 @@ defmodule VeriSim.Test.VQLTestHelpers do @doc "Build a SELECT query for multiple modalities from a octad." def multi_modality_query(modalities, entity_id \\ "test-entity-001") do - projection = modalities + projection = + modalities |> Enum.map(fn m -> "#{m |> to_string() |> String.upcase()}.*" end) |> Enum.join(", ") + "SELECT #{projection} FROM HEXAD '#{entity_id}'" end @@ -173,6 +183,7 @@ defmodule VeriSim.Test.VQLTestHelpers do @doc "Build a federation query." def federation_query(pattern \\ "/*", drift_policy \\ nil) do base = "SELECT * FROM FEDERATION #{pattern}" + if drift_policy do "#{base} WITH DRIFT #{drift_policy |> to_string() |> String.upcase()}" else @@ -264,9 +275,15 @@ defmodule VeriSim.Test.VQLTestHelpers do source = ast[:source] || ast["source"] case {expected_type, source} do - {:octad, {:octad, _id}} -> :ok - {:federation, {:federation, _, _}} -> :ok - {:store, {:store, _id}} -> :ok + {:octad, {:octad, _id}} -> + :ok + + {:federation, {:federation, _, _}} -> + :ok + + {:store, {:store, _id}} -> + :ok + _ -> raise ExUnit.AssertionError, message: "Source type mismatch", @@ -278,6 +295,7 @@ defmodule VeriSim.Test.VQLTestHelpers do @doc "Assert that an AST has a WHERE clause present." def assert_has_where(ast) do where = ast[:where] || ast["where"] + unless where do raise ExUnit.AssertionError, message: "Expected WHERE clause to be present, got nil" @@ -287,6 +305,7 @@ defmodule VeriSim.Test.VQLTestHelpers do @doc "Assert that an AST has a PROOF clause present." def assert_has_proof(ast) do proof = ast[:proof] || ast["proof"] + unless proof do raise ExUnit.AssertionError, message: "Expected PROOF clause to be present, got nil" @@ -296,6 +315,7 @@ defmodule VeriSim.Test.VQLTestHelpers do @doc "Assert that an AST has LIMIT set." def assert_limit(ast, expected_limit) do limit = ast[:limit] || ast["limit"] + unless limit == expected_limit do raise ExUnit.AssertionError, message: "LIMIT mismatch", @@ -307,6 +327,7 @@ defmodule VeriSim.Test.VQLTestHelpers do @doc "Assert that an AST has OFFSET set." def assert_offset(ast, expected_offset) do offset = ast[:offset] || ast["offset"] + unless offset == expected_offset do raise ExUnit.AssertionError, message: "OFFSET mismatch", diff --git a/elixir-orchestration/test/verisim/api/router_test.exs b/elixir-orchestration/test/verisim/api/router_test.exs index d985ed8..497f498 100644 --- a/elixir-orchestration/test/verisim/api/router_test.exs +++ b/elixir-orchestration/test/verisim/api/router_test.exs @@ -176,8 +176,22 @@ defmodule VeriSim.Api.RouterTest do endpoints = [ {"/telemetry/modality-heatmap", ["counts", "percentages", "total_modality_queries"]}, {"/telemetry/query-patterns", ["total_queries", "error_count", "error_rate", "by_type"]}, - {"/telemetry/drift", ["drift_detected_count", "normalise_attempts", "normalise_success_count", "normalise_success_rate", "modality_breakdown"]}, - {"/telemetry/performance", ["query_count", "avg_duration_ms", "min_duration_ms", "max_duration_ms", "total_duration_ms"]}, + {"/telemetry/drift", + [ + "drift_detected_count", + "normalise_attempts", + "normalise_success_count", + "normalise_success_rate", + "modality_breakdown" + ]}, + {"/telemetry/performance", + [ + "query_count", + "avg_duration_ms", + "min_duration_ms", + "max_duration_ms", + "total_duration_ms" + ]}, {"/telemetry/federation", ["total_federation_queries", "peer_errors"]}, {"/telemetry/proof-types", ["total_proofs", "by_type", "vql_dt_active"]}, {"/telemetry/entities", ["created", "deleted"]} @@ -193,7 +207,7 @@ defmodule VeriSim.Api.RouterTest do for key <- expected_keys do assert Map.has_key?(body, key), - "#{path} missing expected key '#{key}'. Got: #{inspect(Map.keys(body))}" + "#{path} missing expected key '#{key}'. Got: #{inspect(Map.keys(body))}" end end end @@ -209,12 +223,20 @@ defmodule VeriSim.Api.RouterTest do # Either full report or disabled message — both are valid if Map.has_key?(body, "meta") do # Full report structure matches what PanLL Model.res expects - expected_sections = ["meta", "modality_heatmap", "query_patterns", - "performance", "drift", "federation", "proof_types", "entities"] + expected_sections = [ + "meta", + "modality_heatmap", + "query_patterns", + "performance", + "drift", + "federation", + "proof_types", + "entities" + ] for section <- expected_sections do assert Map.has_key?(body, section), - "Full telemetry report missing '#{section}' section" + "Full telemetry report missing '#{section}' section" end # Meta section has privacy notice @@ -249,16 +271,24 @@ defmodule VeriSim.Api.RouterTest do for modality <- octad_modalities do assert Map.has_key?(body["counts"], modality), - "Modality heatmap missing '#{modality}' in counts" + "Modality heatmap missing '#{modality}' in counts" + assert Map.has_key?(body["percentages"], modality), - "Modality heatmap missing '#{modality}' in percentages" + "Modality heatmap missing '#{modality}' in percentages" end end test "all telemetry endpoints return CORS headers" do - endpoints = ["/telemetry", "/telemetry/modality-heatmap", "/telemetry/query-patterns", - "/telemetry/drift", "/telemetry/performance", "/telemetry/federation", - "/telemetry/proof-types", "/telemetry/entities"] + endpoints = [ + "/telemetry", + "/telemetry/modality-heatmap", + "/telemetry/query-patterns", + "/telemetry/drift", + "/telemetry/performance", + "/telemetry/federation", + "/telemetry/proof-types", + "/telemetry/entities" + ] for path <- endpoints do conn = @@ -266,7 +296,7 @@ defmodule VeriSim.Api.RouterTest do |> call() assert {"access-control-allow-origin", "*"} in conn.resp_headers, - "#{path} missing CORS header" + "#{path} missing CORS header" end end end diff --git a/elixir-orchestration/test/verisim/consensus/kraft_node_test.exs b/elixir-orchestration/test/verisim/consensus/kraft_node_test.exs index 1a3d0f8..9476471 100644 --- a/elixir-orchestration/test/verisim/consensus/kraft_node_test.exs +++ b/elixir-orchestration/test/verisim/consensus/kraft_node_test.exs @@ -12,10 +12,12 @@ defmodule VeriSim.Consensus.KRaftNodeTest do setup do # Track started nodes for cleanup nodes = [] + on_exit(fn -> # Nodes are cleaned up by ExUnit since we use start_supervised for each :ok end) + {:ok, nodes: nodes} end @@ -204,6 +206,7 @@ defmodule VeriSim.Consensus.KRaftNodeTest do # Find which one is leader leader_diag = KRaftNode.diagnostics(leader_id) + {actual_leader, actual_follower, actual_follower_pid} = if leader_diag.role == :leader do {leader_id, follower_id, follower_pid} diff --git a/elixir-orchestration/test/verisim/consensus/kraft_wal_test.exs b/elixir-orchestration/test/verisim/consensus/kraft_wal_test.exs index 9b159db..7fd57a3 100644 --- a/elixir-orchestration/test/verisim/consensus/kraft_wal_test.exs +++ b/elixir-orchestration/test/verisim/consensus/kraft_wal_test.exs @@ -104,7 +104,9 @@ defmodule VeriSim.Consensus.KRaftWALTest do [recovered_entry] = recovered.log assert recovered_entry.term == 1 assert recovered_entry.index == 1 - assert recovered_entry.command == {:register_store, "s1", "http://localhost:9000", ["graph"]} + + assert recovered_entry.command == + {:register_store, "s1", "http://localhost:9000", ["graph"]} end test "appends multiple entries sequentially", %{wal_path: wal_path} do @@ -183,6 +185,7 @@ defmodule VeriSim.Consensus.KRaftWALTest do KRaftWAL.append_entry(wal_path, entry) {:ok, recovered} = KRaftWAL.recover(wal_path) + assert hd(recovered.log).command == {:register_store, "store-1", "http://host:8080", ["graph", "vector"]} end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/clickhouse_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/clickhouse_integration_test.exs index efbda3a..f0595a8 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/clickhouse_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/clickhouse_integration_test.exs @@ -347,6 +347,8 @@ defmodule VeriSim.Federation.Adapters.ClickHouseIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("ClickHouse not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("ClickHouse not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/influxdb_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/influxdb_integration_test.exs index 9c0a773..8e3fc6d 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/influxdb_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/influxdb_integration_test.exs @@ -369,6 +369,8 @@ defmodule VeriSim.Federation.Adapters.InfluxDBIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("InfluxDB not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("InfluxDB not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/mongodb_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/mongodb_integration_test.exs index e8cae8c..7444a16 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/mongodb_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/mongodb_integration_test.exs @@ -332,7 +332,8 @@ defmodule VeriSim.Federation.Adapters.MongoDBIntegrationTest do bad_peer = %{ @peer_info - | adapter_config: Map.put(@peer_info.adapter_config, :collection, "nonexistent_collection_xyz") + | adapter_config: + Map.put(@peer_info.adapter_config, :collection, "nonexistent_collection_xyz") } query_params = %{modalities: [], limit: 10} @@ -396,6 +397,8 @@ defmodule VeriSim.Federation.Adapters.MongoDBIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("MongoDB not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("MongoDB not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/neo4j_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/neo4j_integration_test.exs index 30d9c4d..42e3369 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/neo4j_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/neo4j_integration_test.exs @@ -354,6 +354,8 @@ defmodule VeriSim.Federation.Adapters.Neo4jIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("Neo4j not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("Neo4j not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/object_storage_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/object_storage_integration_test.exs index 6c77a45..1386a3e 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/object_storage_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/object_storage_integration_test.exs @@ -376,6 +376,8 @@ defmodule VeriSim.Federation.Adapters.ObjectStorageIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("MinIO not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("MinIO not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/redis_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/redis_integration_test.exs index 7f097b3..988da33 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/redis_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/redis_integration_test.exs @@ -353,6 +353,8 @@ defmodule VeriSim.Federation.Adapters.RedisIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("Redis Stack not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("Redis Stack not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/federation/adapters/integration/surrealdb_integration_test.exs b/elixir-orchestration/test/verisim/federation/adapters/integration/surrealdb_integration_test.exs index 8b6e1b9..2c2b45a 100644 --- a/elixir-orchestration/test/verisim/federation/adapters/integration/surrealdb_integration_test.exs +++ b/elixir-orchestration/test/verisim/federation/adapters/integration/surrealdb_integration_test.exs @@ -368,6 +368,8 @@ defmodule VeriSim.Federation.Adapters.SurrealDBIntegrationTest do # Helpers # --------------------------------------------------------------------------- - defp skip_if_unavailable(%{skip: true}), do: flunk("SurrealDB not available — start test-infra stack") + defp skip_if_unavailable(%{skip: true}), + do: flunk("SurrealDB not available — start test-infra stack") + defp skip_if_unavailable(_context), do: :ok end diff --git a/elixir-orchestration/test/verisim/hypatia/dispatch_bridge_test.exs b/elixir-orchestration/test/verisim/hypatia/dispatch_bridge_test.exs index 0d267e7..01fb326 100644 --- a/elixir-orchestration/test/verisim/hypatia/dispatch_bridge_test.exs +++ b/elixir-orchestration/test/verisim/hypatia/dispatch_bridge_test.exs @@ -37,34 +37,76 @@ defmodule VeriSim.Hypatia.DispatchBridgeTest do defp sample_pending do [ - %{"repo" => "echidna", "pattern" => "PA001", "strategy" => "auto_execute", "confidence" => 0.98, - "mutation" => "mutation { createPR(repo: \"echidna\") }"}, - %{"repo" => "ambientops", "pattern" => "PA003", "strategy" => "review", "confidence" => 0.87, - "mutation" => "mutation { createPR(repo: \"ambientops\") }"}, - %{"repo" => "verisimdb", "pattern" => "PA001", "strategy" => "auto_execute", "confidence" => 0.96, - "mutation" => "mutation { createPR(repo: \"verisimdb\") }"} + %{ + "repo" => "echidna", + "pattern" => "PA001", + "strategy" => "auto_execute", + "confidence" => 0.98, + "mutation" => "mutation { createPR(repo: \"echidna\") }" + }, + %{ + "repo" => "ambientops", + "pattern" => "PA003", + "strategy" => "review", + "confidence" => 0.87, + "mutation" => "mutation { createPR(repo: \"ambientops\") }" + }, + %{ + "repo" => "verisimdb", + "pattern" => "PA001", + "strategy" => "auto_execute", + "confidence" => 0.96, + "mutation" => "mutation { createPR(repo: \"verisimdb\") }" + } ] end defp sample_dispatch_log do [ - %{"repo" => "echidna", "pattern" => "PA001", "strategy" => "auto_execute", - "status" => "dispatched", "timestamp" => "2026-02-12T10:00:00Z"}, - %{"repo" => "proven", "pattern" => "PA005", "strategy" => "report_only", - "status" => "dispatched", "timestamp" => "2026-02-12T10:01:00Z"}, - %{"repo" => "ambientops", "pattern" => "PA003", "strategy" => "review", - "status" => "dispatched", "timestamp" => "2026-02-12T10:02:00Z"} + %{ + "repo" => "echidna", + "pattern" => "PA001", + "strategy" => "auto_execute", + "status" => "dispatched", + "timestamp" => "2026-02-12T10:00:00Z" + }, + %{ + "repo" => "proven", + "pattern" => "PA005", + "strategy" => "report_only", + "status" => "dispatched", + "timestamp" => "2026-02-12T10:01:00Z" + }, + %{ + "repo" => "ambientops", + "pattern" => "PA003", + "strategy" => "review", + "status" => "dispatched", + "timestamp" => "2026-02-12T10:02:00Z" + } ] end defp sample_outcomes do [ - %{"repo" => "echidna", "pattern" => "PA001", "status" => "success", - "timestamp" => "2026-02-12T11:00:00Z"}, - %{"repo" => "proven", "pattern" => "PA005", "status" => "success", - "timestamp" => "2026-02-12T11:01:00Z"}, - %{"repo" => "ambientops", "pattern" => "PA003", "status" => "failure", - "timestamp" => "2026-02-12T11:02:00Z"} + %{ + "repo" => "echidna", + "pattern" => "PA001", + "status" => "success", + "timestamp" => "2026-02-12T11:00:00Z" + }, + %{ + "repo" => "proven", + "pattern" => "PA005", + "status" => "success", + "timestamp" => "2026-02-12T11:01:00Z" + }, + %{ + "repo" => "ambientops", + "pattern" => "PA003", + "status" => "failure", + "timestamp" => "2026-02-12T11:02:00Z" + } ] end @@ -110,6 +152,7 @@ defmodule VeriSim.Hypatia.DispatchBridgeTest do describe "read_all_dispatch_logs/1" do test "reads all dispatch logs", %{data_path: dp, dispatch_dir: dd} do write_jsonl(Path.join(dd, "dispatch-2026-02-12.jsonl"), sample_dispatch_log()) + write_jsonl(Path.join(dd, "dispatch-2026-02-13.jsonl"), [ %{"repo" => "verisimdb", "pattern" => "PA001", "strategy" => "auto_execute"} ]) diff --git a/elixir-orchestration/test/verisim/hypatia/pattern_query_test.exs b/elixir-orchestration/test/verisim/hypatia/pattern_query_test.exs index 47323b5..e98dbed 100644 --- a/elixir-orchestration/test/verisim/hypatia/pattern_query_test.exs +++ b/elixir-orchestration/test/verisim/hypatia/pattern_query_test.exs @@ -39,24 +39,78 @@ defmodule VeriSim.Hypatia.PatternQueryTest do defp test_data do [ - {"repo-alpha", "rust", [ - %{"category" => "PanicPath", "location" => "src/main.rs", "severity" => "Medium", "description" => "unwrap"}, - %{"category" => "UnsafeCode", "location" => "src/ffi.rs", "severity" => "High", "description" => "unsafe block"}, - %{"category" => "PanicPath", "location" => "src/lib.rs", "severity" => "Medium", "description" => "expect"} - ]}, - {"repo-beta", "elixir", [ - %{"category" => "PanicPath", "location" => "lib/worker.ex", "severity" => "Medium", "description" => "raise"}, - %{"category" => "InputValidation", "location" => "lib/api.ex", "severity" => "High", "description" => "unsanitized"} - ]}, - {"repo-gamma", "rust", [ - %{"category" => "PanicPath", "location" => "src/core.rs", "severity" => "Medium", "description" => "unwrap"}, - %{"category" => "PanicPath", "location" => "src/net.rs", "severity" => "High", "description" => "index panic"}, - %{"category" => "UnsafeCode", "location" => "src/sys.rs", "severity" => "High", "description" => "transmute"} - ]}, - {"repo-delta", "javascript", [ - %{"category" => "InputValidation", "location" => "src/handler.js", "severity" => "High", "description" => "XSS"}, - %{"category" => "PanicPath", "location" => "src/index.js", "severity" => "Low", "description" => "throw"} - ]} + {"repo-alpha", "rust", + [ + %{ + "category" => "PanicPath", + "location" => "src/main.rs", + "severity" => "Medium", + "description" => "unwrap" + }, + %{ + "category" => "UnsafeCode", + "location" => "src/ffi.rs", + "severity" => "High", + "description" => "unsafe block" + }, + %{ + "category" => "PanicPath", + "location" => "src/lib.rs", + "severity" => "Medium", + "description" => "expect" + } + ]}, + {"repo-beta", "elixir", + [ + %{ + "category" => "PanicPath", + "location" => "lib/worker.ex", + "severity" => "Medium", + "description" => "raise" + }, + %{ + "category" => "InputValidation", + "location" => "lib/api.ex", + "severity" => "High", + "description" => "unsanitized" + } + ]}, + {"repo-gamma", "rust", + [ + %{ + "category" => "PanicPath", + "location" => "src/core.rs", + "severity" => "Medium", + "description" => "unwrap" + }, + %{ + "category" => "PanicPath", + "location" => "src/net.rs", + "severity" => "High", + "description" => "index panic" + }, + %{ + "category" => "UnsafeCode", + "location" => "src/sys.rs", + "severity" => "High", + "description" => "transmute" + } + ]}, + {"repo-delta", "javascript", + [ + %{ + "category" => "InputValidation", + "location" => "src/handler.js", + "severity" => "High", + "description" => "XSS" + }, + %{ + "category" => "PanicPath", + "location" => "src/index.js", + "severity" => "Low", + "description" => "throw" + } + ]} ] end @@ -70,7 +124,8 @@ defmodule VeriSim.Hypatia.PatternQueryTest do assert health.total_scans == 4 assert health.repos_scanned == 4 - assert health.total_weak_points == 0 # Weak points are in document body, not directly accessible + # Weak points are in document body, not directly accessible + assert health.total_weak_points == 0 end end diff --git a/elixir-orchestration/test/verisim/query/vql_crossmodal_test.exs b/elixir-orchestration/test/verisim/query/vql_crossmodal_test.exs index 44c60cf..bdd393d 100644 --- a/elixir-orchestration/test/verisim/query/vql_crossmodal_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_crossmodal_test.exs @@ -161,7 +161,9 @@ defmodule VeriSim.Query.VQLCrossModalTest do describe "ModalityConsistency with cosine metric" do test "CONSISTENT condition with COSINE metric parses correctly" do - query = "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(VECTOR, SEMANTIC) USING COSINE > 0.8" + query = + "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(VECTOR, SEMANTIC) USING COSINE > 0.8" + ast = H.parse!(query) H.assert_has_where(ast) @@ -180,7 +182,9 @@ defmodule VeriSim.Query.VQLCrossModalTest do end test "cosine consistency query executes without crashing" do - query = "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(VECTOR, SEMANTIC) USING COSINE > 0.8" + query = + "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(VECTOR, SEMANTIC) USING COSINE > 0.8" + result = H.execute_safely(query) assert elem(result, 0) in [:ok, :error, :unavailable] end @@ -192,7 +196,9 @@ defmodule VeriSim.Query.VQLCrossModalTest do describe "ModalityConsistency with jaccard metric" do test "CONSISTENT condition with JACCARD metric parses correctly" do - query = "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(GRAPH, DOCUMENT) USING JACCARD > 0.5" + query = + "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(GRAPH, DOCUMENT) USING JACCARD > 0.5" + ast = H.parse!(query) H.assert_has_where(ast) @@ -211,7 +217,9 @@ defmodule VeriSim.Query.VQLCrossModalTest do end test "jaccard consistency query executes without crashing" do - query = "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(GRAPH, DOCUMENT) USING JACCARD > 0.5" + query = + "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(GRAPH, DOCUMENT) USING JACCARD > 0.5" + result = H.execute_safely(query) assert elem(result, 0) in [:ok, :error, :unavailable] end @@ -258,7 +266,9 @@ defmodule VeriSim.Query.VQLCrossModalTest do describe "compound cross-modal conditions" do test "drift AND consistency in same query parses correctly" do - query = "SELECT * FROM HEXAD 'entity-001' WHERE DRIFT(VECTOR, DOCUMENT) > 0.3 AND CONSISTENT(GRAPH, SEMANTIC) USING COSINE > 0.8" + query = + "SELECT * FROM HEXAD 'entity-001' WHERE DRIFT(VECTOR, DOCUMENT) > 0.3 AND CONSISTENT(GRAPH, SEMANTIC) USING COSINE > 0.8" + ast = H.parse!(query) H.assert_has_where(ast) diff --git a/elixir-orchestration/test/verisim/query/vql_dt_integration_test.exs b/elixir-orchestration/test/verisim/query/vql_dt_integration_test.exs index f0200f2..9d34ee7 100644 --- a/elixir-orchestration/test/verisim/query/vql_dt_integration_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_dt_integration_test.exs @@ -116,7 +116,9 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do describe "multi-proof composition" do test "two proofs combined with AND parse correctly" do - query = "SELECT * FROM HEXAD 'entity-001' PROOF EXISTENCE(entity-001) AND PROVENANCE(entity-001)" + query = + "SELECT * FROM HEXAD 'entity-001' PROOF EXISTENCE(entity-001) AND PROVENANCE(entity-001)" + ast = H.parse!(query) H.assert_has_proof(ast) @@ -127,7 +129,8 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do end test "multi-proof execution routes each proof independently" do - query = "SELECT * FROM HEXAD 'entity-001' PROOF EXISTENCE(entity-001) AND PROVENANCE(entity-001)" + query = + "SELECT * FROM HEXAD 'entity-001' PROOF EXISTENCE(entity-001) AND PROVENANCE(entity-001)" result = H.execute_safely(query) assert_proof_result_or_error(result) @@ -145,8 +148,10 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do result = H.execute_safely(query) case result do - {:error, _} -> assert true # Expected: unknown proof type fails - {:unavailable, _} -> assert true # Connection error + # Expected: unknown proof type fails + {:error, _} -> assert true + # Connection error + {:unavailable, _} -> assert true {:ok, _} -> flunk("Unknown proof type should not produce a valid result") end end @@ -157,7 +162,8 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do modalities: [:graph], source: {:octad, "entity-001"}, where: nil, - proof: [%{proofType: "INTEGRITY"}], # No contractName + # No contractName + proof: [%{proofType: "INTEGRITY"}], limit: nil, offset: nil, orderBy: nil, @@ -171,7 +177,8 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do case result do {:error, {:proof_verification_failed, {:missing_contract, _}}} -> assert true - {:error, _} -> assert true # Any error is acceptable + # Any error is acceptable + {:error, _} -> assert true {:unavailable, _} -> assert true {:ok, _} -> flunk("Integrity proof without contract should fail") end @@ -195,7 +202,7 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do # Must have a proof_certificate — NOT bare data assert Map.has_key?(proved_result, :proof_certificate) or Map.has_key?(proved_result, "proof_certificate"), - "VQL-DT result must include proof_certificate, got: #{inspect(Map.keys(proved_result))}" + "VQL-DT result must include proof_certificate, got: #{inspect(Map.keys(proved_result))}" {:error, _} -> # Proof verification error is acceptable (Rust core unavailable) @@ -221,9 +228,8 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do end test "parse_slipstream rejects queries with PROOF clause" do - result = VQLBridge.parse_slipstream( - "SELECT GRAPH.* FROM HEXAD 'abc-123' PROOF EXISTENCE(abc-123)" - ) + result = + VQLBridge.parse_slipstream("SELECT GRAPH.* FROM HEXAD 'abc-123' PROOF EXISTENCE(abc-123)") case result do {:error, msg} -> @@ -262,21 +268,26 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do {:ok, proved_result} when is_map(proved_result) -> # Verify the ProvedResult structure assert Map.has_key?(proved_result, :data) or Map.has_key?(proved_result, "data"), - "ProvedResult must have :data field" + "ProvedResult must have :data field" + assert Map.has_key?(proved_result, :proof_certificate) or Map.has_key?(proved_result, "proof_certificate"), - "ProvedResult must have :proof_certificate field" + "ProvedResult must have :proof_certificate field" cert = proved_result[:proof_certificate] || proved_result["proof_certificate"] + if cert do assert Map.has_key?(cert, :proofs) or Map.has_key?(cert, "proofs"), - "Certificate must have :proofs field" + "Certificate must have :proofs field" + assert Map.has_key?(cert, :composition) or Map.has_key?(cert, "composition"), - "Certificate must have :composition field" + "Certificate must have :composition field" + assert Map.has_key?(cert, :verified_at) or Map.has_key?(cert, "verified_at"), - "Certificate must have :verified_at field" + "Certificate must have :verified_at field" + assert Map.has_key?(cert, :query_hash) or Map.has_key?(cert, "query_hash"), - "Certificate must have :query_hash field" + "Certificate must have :query_hash field" end {:error, _} -> @@ -308,7 +319,7 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do case result do {:ok, %{proof_certificate: cert}} -> assert Map.has_key?(cert, :obligations), - "Certificate should include :obligations list" + "Certificate should include :obligations list" _ -> # Rust unavailable — skip structure check @@ -398,9 +409,9 @@ defmodule VeriSim.Query.VQLDTIntegrationTest do case result do {:ok, proved_result} when is_map(proved_result) -> # Valid ProvedResult — should have proof_certificate + # Or it might be a plain data result if the proof silently passed (bug!) assert Map.has_key?(proved_result, :proof_certificate) or Map.has_key?(proved_result, "proof_certificate") or - # Or it might be a plain data result if the proof silently passed (bug!) Map.has_key?(proved_result, :data) or Map.has_key?(proved_result, "data") diff --git a/elixir-orchestration/test/verisim/query/vql_dt_test.exs b/elixir-orchestration/test/verisim/query/vql_dt_test.exs index ac0a9c8..4e3e04d 100644 --- a/elixir-orchestration/test/verisim/query/vql_dt_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_dt_test.exs @@ -51,9 +51,7 @@ defmodule VeriSim.Query.VQLDTTest do test "parse_statement handles PROOF clause in AST" do # The built-in parser should handle PROOF clauses result = - VQLBridge.parse_statement( - "SELECT GRAPH.* FROM HEXAD 'abc-123' PROOF EXISTENCE(abc-123)" - ) + VQLBridge.parse_statement("SELECT GRAPH.* FROM HEXAD 'abc-123' PROOF EXISTENCE(abc-123)") case result do {:ok, ast} -> diff --git a/elixir-orchestration/test/verisim/query/vql_e2e_test.exs b/elixir-orchestration/test/verisim/query/vql_e2e_test.exs index bd7e187..ebfef63 100644 --- a/elixir-orchestration/test/verisim/query/vql_e2e_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_e2e_test.exs @@ -63,8 +63,17 @@ defmodule VeriSim.Query.VQLE2ETest do """ ast = H.parse!(query) - H.assert_modalities(ast, [:graph, :vector, :tensor, :semantic, - :document, :temporal, :provenance, :spatial]) + + H.assert_modalities(ast, [ + :graph, + :vector, + :tensor, + :semantic, + :document, + :temporal, + :provenance, + :spatial + ]) end end @@ -102,7 +111,9 @@ defmodule VeriSim.Query.VQLE2ETest do end test "WITHIN RADIUS spatial condition is parsed" do - query = "SELECT SPATIAL.* FROM HEXAD 'entity-001' WHERE WITHIN RADIUS(51.5074, -0.1278, 5000)" + query = + "SELECT SPATIAL.* FROM HEXAD 'entity-001' WHERE WITHIN RADIUS(51.5074, -0.1278, 5000)" + ast = H.parse!(query) H.assert_has_where(ast) @@ -117,7 +128,9 @@ defmodule VeriSim.Query.VQLE2ETest do end test "CONSISTENT cross-modal condition with COSINE metric" do - query = "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(VECTOR, SEMANTIC) USING COSINE > 0.8" + query = + "SELECT * FROM HEXAD 'entity-001' WHERE CONSISTENT(VECTOR, SEMANTIC) USING COSINE > 0.8" + ast = H.parse!(query) H.assert_has_where(ast) @@ -145,11 +158,12 @@ defmodule VeriSim.Query.VQLE2ETest do test "#{@pt_upper} proof: typecheck → certificate → verify" do # Build a VQL-DT query with this proof type - contract = if @pt in [:integrity, :citation, :custom, :sanctify] do - "(my_contract)" - else - "(entity-001)" - end + contract = + if @pt in [:integrity, :citation, :custom, :sanctify] do + "(my_contract)" + else + "(entity-001)" + end query = "SELECT GRAPH.* FROM HEXAD 'entity-001' PROOF #{@pt_upper}#{contract}" ast = H.parse!(query) @@ -172,7 +186,8 @@ defmodule VeriSim.Query.VQLE2ETest do # Certificate structure assert cert.type == @pt assert is_binary(cert.hash) - assert byte_size(cert.hash) == 32 # SHA-256 + # SHA-256 + assert byte_size(cert.hash) == 32 # Verify round-trip assert :ok == VQLProofCertificate.verify_certificate(cert) @@ -186,7 +201,9 @@ defmodule VeriSim.Query.VQLE2ETest do end test "multi-proof AND composition: typecheck produces multiple obligations" do - query = "SELECT * FROM HEXAD 'entity-001' PROOF EXISTENCE(entity-001) AND PROVENANCE(entity-001)" + query = + "SELECT * FROM HEXAD 'entity-001' PROOF EXISTENCE(entity-001) AND PROVENANCE(entity-001)" + ast = H.parse!(query) H.assert_has_proof(ast) @@ -196,9 +213,10 @@ defmodule VeriSim.Query.VQLE2ETest do assert length(obligations) >= 2 # Generate batch certificates - witnesses = Enum.map(obligations, fn obl -> - {obl, build_mock_witness(obl[:type])} - end) + witnesses = + Enum.map(obligations, fn obl -> + {obl, build_mock_witness(obl[:type])} + end) {:ok, certs} = VQLProofCertificate.generate_batch(witnesses) assert length(certs) >= 2 @@ -262,6 +280,7 @@ defmodule VeriSim.Query.VQLE2ETest do SELECT * FROM HEXAD 'entity-001' WHERE DOCUMENT.severity > 5 AND PROVENANCE EXISTS """ + ast = H.parse!(query) H.assert_has_where(ast) end @@ -282,6 +301,7 @@ defmodule VeriSim.Query.VQLE2ETest do test "unknown proof type is rejected by type checker" do query = "SELECT GRAPH.* FROM HEXAD 'entity-001' PROOF TELEPORT(entity-001)" + case VQLBridge.parse(query) do {:ok, ast} -> result = VQLTypeChecker.typecheck(ast) @@ -295,6 +315,7 @@ defmodule VeriSim.Query.VQLE2ETest do test "INTEGRITY proof without semantic modality is rejected" do query = "SELECT GRAPH.* FROM HEXAD 'entity-001' PROOF INTEGRITY(my_contract)" + case VQLBridge.parse(query) do {:ok, ast} -> case VQLTypeChecker.typecheck(ast) do @@ -308,12 +329,14 @@ defmodule VeriSim.Query.VQLE2ETest do :ok end - {:error, _} -> :ok + {:error, _} -> + :ok end end test "PROVENANCE proof without provenance modality is rejected" do query = "SELECT GRAPH.* FROM HEXAD 'entity-001' PROOF PROVENANCE(entity-001)" + case VQLBridge.parse(query) do {:ok, ast} -> case VQLTypeChecker.typecheck(ast) do @@ -321,10 +344,12 @@ defmodule VeriSim.Query.VQLE2ETest do reason_str = inspect(reason) assert reason_str =~ "provenance" or reason_str =~ "modality" - {:ok, _} -> :ok + {:ok, _} -> + :ok end - {:error, _} -> :ok + {:error, _} -> + :ok end end @@ -335,7 +360,8 @@ defmodule VeriSim.Query.VQLE2ETest do case H.assert_ok_or_rust_unavailable(result) do :rust_unavailable -> :ok {:error_from_rust, _} -> :ok - :ok_result -> :ok # May return empty results + # May return empty results + :ok_result -> :ok end end end @@ -394,10 +420,11 @@ defmodule VeriSim.Query.VQLE2ETest do ast = %{ modalities: [:graph, :vector, :document, :provenance], source: {:octad, "entity-001"}, - where: H.and_condition( - H.modality_exists(:provenance), - H.modality_drift(:vector, :document, 0.5) - ), + where: + H.and_condition( + H.modality_exists(:provenance), + H.modality_drift(:vector, :document, 0.5) + ), limit: nil, offset: nil, order_by: nil, diff --git a/elixir-orchestration/test/verisim/query/vql_proof_certificate_test.exs b/elixir-orchestration/test/verisim/query/vql_proof_certificate_test.exs index 66d8d48..e691823 100644 --- a/elixir-orchestration/test/verisim/query/vql_proof_certificate_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_proof_certificate_test.exs @@ -69,24 +69,31 @@ defmodule VeriSim.Query.VQLProofCertificateTest do describe "generate_certificate/2" do test "produces a certificate with all required fields" do - {:ok, cert} = VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) + {:ok, cert} = + VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) assert cert.type == :existence assert cert.obligation == existence_obligation() assert cert.witness == existence_witness() assert %DateTime{} = cert.timestamp assert is_binary(cert.hash) - assert byte_size(cert.hash) == 32 # SHA-256 = 32 bytes + # SHA-256 = 32 bytes + assert byte_size(cert.hash) == 32 end test "rejects obligation without :type field" do bad_obligation = %{proofType: "EXISTENCE", contract: "x"} - assert {:error, {:invalid_obligation, _}} = VQLProofCertificate.generate_certificate(bad_obligation, %{}) + + assert {:error, {:invalid_obligation, _}} = + VQLProofCertificate.generate_certificate(bad_obligation, %{}) end test "rejects non-map arguments" do - assert {:error, {:invalid_arguments, _}} = VQLProofCertificate.generate_certificate("not a map", %{}) - assert {:error, {:invalid_arguments, _}} = VQLProofCertificate.generate_certificate(%{type: :existence}, "not a map") + assert {:error, {:invalid_arguments, _}} = + VQLProofCertificate.generate_certificate("not a map", %{}) + + assert {:error, {:invalid_arguments, _}} = + VQLProofCertificate.generate_certificate(%{type: :existence}, "not a map") end end @@ -96,19 +103,23 @@ defmodule VeriSim.Query.VQLProofCertificateTest do describe "verify_certificate/1" do test "valid certificate passes verification" do - {:ok, cert} = VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) + {:ok, cert} = + VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) + assert :ok = VQLProofCertificate.verify_certificate(cert) end test "tampered obligation causes hash mismatch" do - {:ok, cert} = VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) + {:ok, cert} = + VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) tampered = %{cert | obligation: %{cert.obligation | type: :integrity}} assert {:error, :invalid_hash} = VQLProofCertificate.verify_certificate(tampered) end test "tampered witness causes hash mismatch" do - {:ok, cert} = VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) + {:ok, cert} = + VQLProofCertificate.generate_certificate(existence_obligation(), existence_witness()) tampered = %{cert | witness: %{"octad_id" => "evil-entity"}} assert {:error, :invalid_hash} = VQLProofCertificate.verify_certificate(tampered) @@ -117,7 +128,9 @@ defmodule VeriSim.Query.VQLProofCertificateTest do test "malformed certificate returns error" do assert {:error, :malformed_certificate} = VQLProofCertificate.verify_certificate(%{}) assert {:error, :malformed_certificate} = VQLProofCertificate.verify_certificate(nil) - assert {:error, :malformed_certificate} = VQLProofCertificate.verify_certificate("not a cert") + + assert {:error, :malformed_certificate} = + VQLProofCertificate.verify_certificate("not a cert") end end diff --git a/elixir-orchestration/test/verisim/query/vql_property_test.exs b/elixir-orchestration/test/verisim/query/vql_property_test.exs index ec1a234..6312606 100644 --- a/elixir-orchestration/test/verisim/query/vql_property_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_property_test.exs @@ -19,7 +19,9 @@ defmodule VeriSim.Query.VQLPropertyTest do # --------------------------------------------------------------------------- defp valid_proof_type do - member_of(~w(existence integrity consistency provenance freshness access citation custom zkp proven sanctify)) + member_of( + ~w(existence integrity consistency provenance freshness access citation custom zkp proven sanctify) + ) end defp valid_modality do @@ -32,15 +34,19 @@ defmodule VeriSim.Query.VQLPropertyTest do end defp proof_spec_raw do - gen all proof_type <- valid_proof_type(), - contract <- valid_contract_name() do + gen all( + proof_type <- valid_proof_type(), + contract <- valid_contract_name() + ) do %{raw: "#{String.upcase(proof_type)}(#{contract})"} end end defp valid_query_ast do - gen all modalities <- list_of(valid_modality(), min_length: 1, max_length: 4), - proof_specs <- list_of(proof_spec_raw(), min_length: 1, max_length: 3) do + gen all( + modalities <- list_of(valid_modality(), min_length: 1, max_length: 4), + proof_specs <- list_of(proof_spec_raw(), min_length: 1, max_length: 3) + ) do %{ modalities: Enum.uniq(modalities), proof: proof_specs @@ -53,14 +59,14 @@ defmodule VeriSim.Query.VQLPropertyTest do # --------------------------------------------------------------------------- property "type checker never crashes on valid proof specs" do - check all query_ast <- valid_query_ast() do + check all(query_ast <- valid_query_ast()) do result = VQLTypeChecker.typecheck(query_ast) assert match?({:ok, _}, result) or match?({:error, _}, result) end end property "type checker output has required fields when successful" do - check all query_ast <- valid_query_ast() do + check all(query_ast <- valid_query_ast()) do case VQLTypeChecker.typecheck(query_ast) do {:ok, info} -> assert is_list(info.proof_obligations) @@ -77,8 +83,10 @@ defmodule VeriSim.Query.VQLPropertyTest do end property "parse_proof_specs round-trips without data loss" do - check all proof_type <- valid_proof_type(), - contract <- valid_contract_name() do + check all( + proof_type <- valid_proof_type(), + contract <- valid_contract_name() + ) do raw = "#{String.upcase(proof_type)}(#{contract})" specs = VQLTypeChecker.parse_proof_specs(%{raw: raw}) @@ -90,8 +98,10 @@ defmodule VeriSim.Query.VQLPropertyTest do end property "multi-proof specs split correctly on AND/OR" do - check all types <- list_of(valid_proof_type(), min_length: 2, max_length: 4), - contracts <- list_of(valid_contract_name(), length: length(types)) do + check all( + types <- list_of(valid_proof_type(), min_length: 2, max_length: 4), + contracts <- list_of(valid_contract_name(), length: length(types)) + ) do parts = Enum.zip(types, contracts) |> Enum.map(fn {t, c} -> "#{String.upcase(t)}(#{c})" end) raw = Enum.join(parts, " AND ") @@ -103,7 +113,7 @@ defmodule VeriSim.Query.VQLPropertyTest do property "unknown proof types always rejected" do # Prefix with "XBOGUS" to guarantee the generated string never collides # with a known proof type (existence, integrity, consistency, etc.) - check all suffix <- string(:alphanumeric, min_length: 3, max_length: 10) do + check all(suffix <- string(:alphanumeric, min_length: 3, max_length: 10)) do bad_type = "XBOGUS#{suffix}" query_ast = %{ diff --git a/elixir-orchestration/test/verisim/query/vql_test.exs b/elixir-orchestration/test/verisim/query/vql_test.exs index 93a2e43..e1c2b7c 100644 --- a/elixir-orchestration/test/verisim/query/vql_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_test.exs @@ -57,8 +57,7 @@ defmodule VeriSim.Query.VQLTest do end test "parses multi-modality query" do - {:ok, ast} = - VQLBridge.parse("SELECT GRAPH.*, VECTOR.*, DOCUMENT.* FROM HEXAD 'abc-123'") + {:ok, ast} = VQLBridge.parse("SELECT GRAPH.*, VECTOR.*, DOCUMENT.* FROM HEXAD 'abc-123'") assert is_map(ast) end @@ -70,36 +69,26 @@ defmodule VeriSim.Query.VQLTest do test "parses query with WHERE clause" do {:ok, ast} = - VQLBridge.parse( - "SELECT DOCUMENT.* FROM HEXAD 'abc-123' WHERE DOCUMENT.title = 'Test'" - ) + VQLBridge.parse("SELECT DOCUMENT.* FROM HEXAD 'abc-123' WHERE DOCUMENT.title = 'Test'") assert is_map(ast) end test "parses query with LIMIT and OFFSET" do - {:ok, ast} = - VQLBridge.parse( - "SELECT GRAPH.* FROM HEXAD 'abc-123' LIMIT 10 OFFSET 5" - ) + {:ok, ast} = VQLBridge.parse("SELECT GRAPH.* FROM HEXAD 'abc-123' LIMIT 10 OFFSET 5") assert is_map(ast) end test "parses query with ORDER BY" do {:ok, ast} = - VQLBridge.parse( - "SELECT DOCUMENT.* FROM HEXAD 'abc-123' ORDER BY DOCUMENT.title ASC" - ) + VQLBridge.parse("SELECT DOCUMENT.* FROM HEXAD 'abc-123' ORDER BY DOCUMENT.title ASC") assert is_map(ast) end test "parses query with aggregate in projection" do - {:ok, ast} = - VQLBridge.parse( - "SELECT GRAPH.* FROM HEXAD 'abc-123' LIMIT 5" - ) + {:ok, ast} = VQLBridge.parse("SELECT GRAPH.* FROM HEXAD 'abc-123' LIMIT 5") assert is_map(ast) end @@ -108,9 +97,7 @@ defmodule VeriSim.Query.VQLTest do describe "VQLBridge — mutations via parse_statement/1" do test "parses INSERT with document data" do {:ok, ast} = - VQLBridge.parse_statement( - "INSERT HEXAD WITH DOCUMENT(title = 'Test', body = 'Content')" - ) + VQLBridge.parse_statement("INSERT HEXAD WITH DOCUMENT(title = 'Test', body = 'Content')") assert is_map(ast) assert ast[:TAG] == "Mutation" or ast["TAG"] == "Mutation" @@ -118,16 +105,13 @@ defmodule VeriSim.Query.VQLTest do test "parses UPDATE with set clause" do {:ok, ast} = - VQLBridge.parse_statement( - "UPDATE HEXAD 'abc-123' SET DOCUMENT.title = 'Updated'" - ) + VQLBridge.parse_statement("UPDATE HEXAD 'abc-123' SET DOCUMENT.title = 'Updated'") assert is_map(ast) end test "parses DELETE" do - {:ok, ast} = - VQLBridge.parse_statement("DELETE HEXAD 'abc-123'") + {:ok, ast} = VQLBridge.parse_statement("DELETE HEXAD 'abc-123'") assert is_map(ast) end @@ -212,9 +196,7 @@ defmodule VeriSim.Query.VQLTest do describe "condition classification" do test "simple condition is pushed down" do {:ok, ast} = - VQLBridge.parse( - "SELECT GRAPH.* FROM HEXAD 'abc-123' WHERE GRAPH.type = 'Person'" - ) + VQLBridge.parse("SELECT GRAPH.* FROM HEXAD 'abc-123' WHERE GRAPH.type = 'Person'") # The where clause should be present and classified as pushdown assert is_map(ast) @@ -227,8 +209,7 @@ defmodule VeriSim.Query.VQLTest do describe "provenance query routing" do test "parses provenance-only query" do - {:ok, ast} = - VQLBridge.parse("SELECT PROVENANCE.* FROM HEXAD 'abc-123'") + {:ok, ast} = VQLBridge.parse("SELECT PROVENANCE.* FROM HEXAD 'abc-123'") assert is_map(ast) end @@ -240,8 +221,7 @@ defmodule VeriSim.Query.VQLTest do describe "spatial query routing" do test "parses spatial-only query" do - {:ok, ast} = - VQLBridge.parse("SELECT SPATIAL.* FROM HEXAD 'abc-123'") + {:ok, ast} = VQLBridge.parse("SELECT SPATIAL.* FROM HEXAD 'abc-123'") assert is_map(ast) end diff --git a/elixir-orchestration/test/verisim/query/vql_type_checker_test.exs b/elixir-orchestration/test/verisim/query/vql_type_checker_test.exs index c81ba4f..5a90568 100644 --- a/elixir-orchestration/test/verisim/query/vql_type_checker_test.exs +++ b/elixir-orchestration/test/verisim/query/vql_type_checker_test.exs @@ -23,7 +23,10 @@ defmodule VeriSim.Query.VQLTypeCheckerTest do describe "parse_proof_specs/1" do test "splits AND-connected proofs" do - specs = VQLTypeChecker.parse_proof_specs(%{raw: "EXISTENCE(entity-001) AND PROVENANCE(entity-001)"}) + specs = + VQLTypeChecker.parse_proof_specs(%{ + raw: "EXISTENCE(entity-001) AND PROVENANCE(entity-001)" + }) assert length(specs) == 2 assert Enum.at(specs, 0).proofType == "EXISTENCE" @@ -57,9 +60,8 @@ defmodule VeriSim.Query.VQLTypeCheckerTest do end test "handles three proofs" do - specs = VQLTypeChecker.parse_proof_specs( - %{raw: "EXISTENCE(a) AND PROVENANCE(b) AND INTEGRITY(c)"} - ) + specs = + VQLTypeChecker.parse_proof_specs(%{raw: "EXISTENCE(a) AND PROVENANCE(b) AND INTEGRITY(c)"}) assert length(specs) == 3 assert Enum.map(specs, & &1.proofType) == ["EXISTENCE", "PROVENANCE", "INTEGRITY"] @@ -82,6 +84,7 @@ defmodule VeriSim.Query.VQLTypeCheckerTest do %{proofType: "EXISTENCE", contractName: "a"}, %{proofType: "INTEGRITY", contractName: "b"} ] + specs = VQLTypeChecker.parse_proof_specs(input) assert length(specs) == 2 @@ -308,9 +311,10 @@ defmodule VeriSim.Query.VQLTypeCheckerTest do {:ok, info} = VQLTypeChecker.typecheck(ast) assert info.total_estimated_ms > 0 + assert info.total_estimated_ms == - Enum.at(info.proof_obligations, 0).estimated_time_ms + - Enum.at(info.proof_obligations, 1).estimated_time_ms + Enum.at(info.proof_obligations, 0).estimated_time_ms + + Enum.at(info.proof_obligations, 1).estimated_time_ms end test "provenance circuit is provenance-proof-v1" do @@ -402,14 +406,17 @@ defmodule VeriSim.Query.VQLTypeCheckerTest do for {type, modalities, contract} <- test_cases do proof_type_str = type |> Atom.to_string() |> String.upcase() + ast = %{ modalities: modalities, proof: [%{proofType: proof_type_str, contractName: contract}] } result = VQLTypeChecker.typecheck(ast) + assert {:ok, info} = result, - "#{proof_type_str} should be accepted, got: #{inspect(result)}" + "#{proof_type_str} should be accepted, got: #{inspect(result)}" + assert Enum.at(info.proof_obligations, 0).type == type end end diff --git a/elixir-orchestration/test/verisim/telemetry_test.exs b/elixir-orchestration/test/verisim/telemetry_test.exs index 12693e3..7308ad1 100644 --- a/elixir-orchestration/test/verisim/telemetry_test.exs +++ b/elixir-orchestration/test/verisim/telemetry_test.exs @@ -21,6 +21,7 @@ defmodule VeriSim.TelemetryTest do nil -> {:ok, _pid} = Collector.start_link() :ok + _pid -> Collector.reset() :ok @@ -258,7 +259,7 @@ defmodule VeriSim.TelemetryTest do # All keys should be atoms or {atom, atom/string} tuples — never raw strings. Enum.each(snapshot, fn {key, _value} -> assert is_atom(key) or is_tuple(key), - "Expected atom or tuple key, got: #{inspect(key)}" + "Expected atom or tuple key, got: #{inspect(key)}" end) end end diff --git a/rust-core/verisim-api/src/auth.rs b/rust-core/verisim-api/src/auth.rs index 57c8424..a5e3e7b 100644 --- a/rust-core/verisim-api/src/auth.rs +++ b/rust-core/verisim-api/src/auth.rs @@ -116,9 +116,7 @@ impl ApiKeyRegistry { pub fn validate(&self, plaintext_key: &str) -> Option { let hash = hash_key(plaintext_key); let keys = self.keys.lock().expect("key registry lock"); - keys.get(&hash) - .filter(|entry| entry.active) - .cloned() + keys.get(&hash).filter(|entry| entry.active).cloned() } /// Revoke an API key by its hash. @@ -289,8 +287,10 @@ pub async fn auth_middleware( return ( StatusCode::TOO_MANY_REQUESTS, [ - (header::HeaderName::from_static("x-ratelimit-remaining"), - remaining.to_string()), + ( + header::HeaderName::from_static("x-ratelimit-remaining"), + remaining.to_string(), + ), (header::RETRY_AFTER, "60".to_string()), ], Json(AuthError { @@ -303,12 +303,8 @@ pub async fn auth_middleware( // RBAC authorization check. let method = request.method().clone(); - if let Err(authz_err) = crate::rbac::check_authorization( - &identity, - &path, - &method, - &auth.rbac, - ) { + if let Err(authz_err) = crate::rbac::check_authorization(&identity, &path, &method, &auth.rbac) + { warn!( client = %identity.id, role = ?identity.role, @@ -323,6 +319,7 @@ pub async fn auth_middleware( } /// Extract client identity from request headers. +#[allow(clippy::result_large_err)] fn extract_identity(request: &Request, auth: &AuthState) -> Result { // Try X-API-Key header first. if let Some(api_key) = request @@ -378,7 +375,9 @@ fn extract_identity(request: &Request, auth: &AuthState) -> Result".to_string(), + error: + "Authentication required. Provide X-API-Key header or Authorization: Bearer " + .to_string(), code: 401, }), ) @@ -402,22 +401,22 @@ fn validate_jwt(token: &str, config: &AuthConfig) -> Result Result Vec { // Outer hash. let mut outer_hasher = Sha256::new(); outer_hasher.update(&opad); - outer_hasher.update(&inner_hash); + outer_hasher.update(inner_hash); outer_hasher.finalize().to_vec() } @@ -528,7 +524,7 @@ fn base64_decode(input: &str) -> Result, &'static str> { } let bytes = input.as_bytes(); - if bytes.len() % 4 != 0 { + if !bytes.len().is_multiple_of(4) { return Err("invalid base64 length"); } @@ -690,9 +686,8 @@ mod tests { // Create JWT: header.payload.signature let header = base64url_encode(b"{\"alg\":\"HS256\",\"typ\":\"JWT\"}"); // Set exp far in the future. - let payload = base64url_encode( - b"{\"sub\":\"test-user\",\"role\":\"admin\",\"exp\":9999999999}", - ); + let payload = + base64url_encode(b"{\"sub\":\"test-user\",\"role\":\"admin\",\"exp\":9999999999}"); let signing_input = format!("{header}.{payload}"); let sig = hmac_sha256(signing_input.as_bytes(), b"test-secret"); let sig_encoded = base64url_encode(&sig); @@ -750,8 +745,7 @@ mod tests { /// Base64url encode for test helpers. fn base64url_encode(input: &[u8]) -> String { - const CHARSET: &[u8] = - b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let mut result = String::new(); let mut i = 0; diff --git a/rust-core/verisim-api/src/federation.rs b/rust-core/verisim-api/src/federation.rs index e7047b3..2468978 100644 --- a/rust-core/verisim-api/src/federation.rs +++ b/rust-core/verisim-api/src/federation.rs @@ -25,37 +25,32 @@ use axum::{ Json, Router, }; use serde::{Deserialize, Serialize}; -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; -use tracing::{error, info, warn, instrument}; +use tracing::{error, info, instrument, warn}; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- /// Drift policy for federated queries. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum DriftPolicy { Strict, Repair, + #[default] Tolerate, Latest, } -impl Default for DriftPolicy { - fn default() -> Self { - DriftPolicy::Tolerate - } -} - /// A registered peer store in the federation. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PeerStore { /// Unique store identifier. pub store_id: String, - /// HTTP endpoint URL (e.g., "https://store-2.verisimdb.example.com/api/v1"). + /// HTTP endpoint URL (e.g., `https://store-2.verisimdb.example.com/api/v1`). pub endpoint: String, /// Modalities this store supports. pub modalities: Vec, @@ -304,7 +299,10 @@ async fn register_peer( // Validate store_id format (alphanumeric + dash + underscore, max 128) if request.store_id.is_empty() || request.store_id.len() > 128 - || !request.store_id.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '/') + || !request + .store_id + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '/') { return Err(StatusCode::BAD_REQUEST); } @@ -426,9 +424,7 @@ async fn federation_query( } let include = match request.drift_policy { - DriftPolicy::Strict => { - store.trust_level >= (1.0 - state.strict_drift_threshold) - } + DriftPolicy::Strict => store.trust_level >= (1.0 - state.strict_drift_threshold), DriftPolicy::Repair | DriftPolicy::Tolerate | DriftPolicy::Latest => true, }; @@ -448,10 +444,7 @@ async fn federation_query( }; // RwLock dropped here — safe to make async HTTP calls - let stores_queried: Vec = stores_to_query - .iter() - .map(|s| s.store_id.clone()) - .collect(); + let stores_queried: Vec = stores_to_query.iter().map(|s| s.store_id.clone()).collect(); let client = reqwest::Client::new(); let text_query = request.text_query.clone(); @@ -469,7 +462,13 @@ async fn federation_query( let timeout = std::time::Duration::from_secs(10); match tokio::time::timeout( timeout, - query_single_peer(&client, &store, text_q.as_deref(), vector_q.as_deref(), limit), + query_single_peer( + &client, + &store, + text_q.as_deref(), + vector_q.as_deref(), + limit, + ), ) .await { @@ -497,7 +496,11 @@ async fn federation_query( } // Sort by score descending and apply global limit - all_results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal)); + all_results.sort_by(|a, b| { + b.score + .partial_cmp(&a.score) + .unwrap_or(std::cmp::Ordering::Equal) + }); all_results.truncate(limit); Ok(Json(FederationQueryResponse { @@ -530,7 +533,11 @@ async fn query_single_peer( .map_err(|e| format!("HTTP request to {} failed: {}", store_id, e))?; if !resp.status().is_success() { - return Err(format!("Peer {} returned status {}", store_id, resp.status())); + return Err(format!( + "Peer {} returned status {}", + store_id, + resp.status() + )); } resp.json() @@ -548,7 +555,11 @@ async fn query_single_peer( .map_err(|e| format!("HTTP request to {} failed: {}", store_id, e))?; if !resp.status().is_success() { - return Err(format!("Peer {} returned status {}", store_id, resp.status())); + return Err(format!( + "Peer {} returned status {}", + store_id, + resp.status() + )); } resp.json() @@ -565,7 +576,11 @@ async fn query_single_peer( .map_err(|e| format!("HTTP request to {} failed: {}", store_id, e))?; if !resp.status().is_success() { - return Err(format!("Peer {} returned status {}", store_id, resp.status())); + return Err(format!( + "Peer {} returned status {}", + store_id, + resp.status() + )); } resp.json() @@ -614,7 +629,10 @@ mod tests { fn test_pattern_matching() { assert!(pattern_matches("*", "any-store")); assert!(pattern_matches("/universities/*", "/universities/oxford")); - assert!(pattern_matches("/universities/*", "/universities/cambridge")); + assert!(pattern_matches( + "/universities/*", + "/universities/cambridge" + )); assert!(!pattern_matches("/universities/*", "/hospitals/nhs")); assert!(pattern_matches("store-1", "store-1")); assert!(!pattern_matches("store-1", "store-2")); diff --git a/rust-core/verisim-api/src/graphql.rs b/rust-core/verisim-api/src/graphql.rs index 8e7e5ca..39c9b40 100644 --- a/rust-core/verisim-api/src/graphql.rs +++ b/rust-core/verisim-api/src/graphql.rs @@ -5,22 +5,18 @@ //! via a GraphQL schema at `/graphql`. use async_graphql::{ - Context, EmptySubscription, InputObject, Object, Schema, SimpleObject, - http::GraphiQLSource, + http::GraphiQLSource, Context, EmptySubscription, InputObject, Object, Schema, SimpleObject, }; use async_graphql_axum::{GraphQLRequest, GraphQLResponse}; -use tracing::error; use axum::{ extract::State as AxumState, response::{Html, IntoResponse}, routing::{get, post}, Router, }; +use tracing::error; -use verisim_planner::{ - ExplainOutput as PlannerExplainOutput, - LogicalPlan, -}; +use verisim_planner::{ExplainOutput as PlannerExplainOutput, LogicalPlan}; use crate::AppState; @@ -245,11 +241,10 @@ impl QueryRoot { /// Get drift status for all drift types. async fn drift_status(&self, ctx: &Context<'_>) -> async_graphql::Result> { let state = ctx.data::()?; - let all_metrics = state.drift_detector.all_metrics() - .map_err(|e| { - error!(error = %e, "GraphQL drift status query failed"); - async_graphql::Error::new("Internal server error") - })?; + let all_metrics = state.drift_detector.all_metrics().map_err(|e| { + error!(error = %e, "GraphQL drift status query failed"); + async_graphql::Error::new("Internal server error") + })?; Ok(all_metrics .iter() @@ -264,9 +259,15 @@ impl QueryRoot { } /// Get current planner configuration. - async fn planner_config(&self, ctx: &Context<'_>) -> async_graphql::Result { + async fn planner_config( + &self, + ctx: &Context<'_>, + ) -> async_graphql::Result { let state = ctx.data::()?; - let planner = state.planner.lock().map_err(|_| { error!("Planner lock poisoned in GraphQL"); async_graphql::Error::new("Internal server error") })?; + let planner = state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in GraphQL"); + async_graphql::Error::new("Internal server error") + })?; let cfg = planner.config(); Ok(PlannerConfigOutput { global_mode: format!("{:?}", cfg.global_mode), @@ -279,7 +280,10 @@ impl QueryRoot { /// Get planner statistics. async fn planner_stats(&self, ctx: &Context<'_>) -> async_graphql::Result { let state = ctx.data::()?; - let planner = state.planner.lock().map_err(|_| { error!("Planner lock poisoned in GraphQL"); async_graphql::Error::new("Internal server error") })?; + let planner = state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in GraphQL"); + async_graphql::Error::new("Internal server error") + })?; let stores: Vec = verisim_planner::Modality::ALL .iter() @@ -357,14 +361,10 @@ impl MutationRoot { } use verisim_octad::OctadStore; - let h = state - .octad_store - .create(octad_input) - .await - .map_err(|e| { - error!(error = %e, "GraphQL octad creation failed"); - async_graphql::Error::new("Internal server error") - })?; + let h = state.octad_store.create(octad_input).await.map_err(|e| { + error!(error = %e, "GraphQL octad creation failed"); + async_graphql::Error::new("Internal server error") + })?; Ok(Octad { id: h.id.to_string(), @@ -386,14 +386,10 @@ impl MutationRoot { let octad_id = verisim_octad::OctadId::new(&id); use verisim_octad::OctadStore; - state - .octad_store - .delete(&octad_id) - .await - .map_err(|e| { - error!(error = %e, "GraphQL octad deletion failed"); - async_graphql::Error::new("Internal server error") - })?; + state.octad_store.delete(&octad_id).await.map_err(|e| { + error!(error = %e, "GraphQL octad deletion failed"); + async_graphql::Error::new("Internal server error") + })?; Ok(true) } @@ -408,7 +404,10 @@ impl MutationRoot { let logical: LogicalPlan = serde_json::from_str(&plan_json) .map_err(|e| async_graphql::Error::new(format!("Invalid plan JSON: {}", e)))?; - let planner = state.planner.lock().map_err(|_| { error!("Planner lock poisoned in GraphQL"); async_graphql::Error::new("Internal server error") })?; + let planner = state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in GraphQL"); + async_graphql::Error::new("Internal server error") + })?; let physical = planner .optimize(&logical) .map_err(|e| async_graphql::Error::new(e.to_string()))?; @@ -423,7 +422,10 @@ impl MutationRoot { input: PlannerConfigInput, ) -> async_graphql::Result { let state = ctx.data::()?; - let mut planner = state.planner.lock().map_err(|_| { error!("Planner lock poisoned in GraphQL"); async_graphql::Error::new("Internal server error") })?; + let mut planner = state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in GraphQL"); + async_graphql::Error::new("Internal server error") + })?; let mut cfg = planner.config().clone(); if let Some(mode) = &input.global_mode { diff --git a/rust-core/verisim-api/src/grpc.rs b/rust-core/verisim-api/src/grpc.rs index 91f4d5c..bf4a0dc 100644 --- a/rust-core/verisim-api/src/grpc.rs +++ b/rust-core/verisim-api/src/grpc.rs @@ -15,8 +15,8 @@ use crate::AppState; #[path = "proto/verisim.rs"] pub mod proto; -use proto::veri_sim_planner_server::{VeriSimPlanner, VeriSimPlannerServer}; use proto::veri_sim_octad_server::{VeriSimOctad, VeriSimOctadServer}; +use proto::veri_sim_planner_server::{VeriSimPlanner, VeriSimPlannerServer}; // ============================================================================ // Planner gRPC Service @@ -42,8 +42,10 @@ impl VeriSimPlanner for PlannerService { let logical: LogicalPlan = serde_json::from_str(&req.plan_json) .map_err(|e| Status::invalid_argument(format!("Invalid plan JSON: {}", e)))?; - let planner = self.state.planner.lock() - .map_err(|_| { error!("Planner lock poisoned in gRPC"); Status::internal("Internal server error") })?; + let planner = self.state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in gRPC"); + Status::internal("Internal server error") + })?; let physical = planner .optimize(&logical) .map_err(|e| Status::invalid_argument(e.to_string()))?; @@ -79,8 +81,10 @@ impl VeriSimPlanner for PlannerService { let logical: LogicalPlan = serde_json::from_str(&req.plan_json) .map_err(|e| Status::invalid_argument(format!("Invalid plan JSON: {}", e)))?; - let planner = self.state.planner.lock() - .map_err(|_| { error!("Planner lock poisoned in gRPC"); Status::internal("Internal server error") })?; + let planner = self.state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in gRPC"); + Status::internal("Internal server error") + })?; let explain = planner .explain(&logical) .map_err(|e| Status::invalid_argument(e.to_string()))?; @@ -132,8 +136,10 @@ impl VeriSimPlanner for PlannerService { &self, _request: Request, ) -> Result, Status> { - let planner = self.state.planner.lock() - .map_err(|_| { error!("Planner lock poisoned in gRPC"); Status::internal("Internal server error") })?; + let planner = self.state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in gRPC"); + Status::internal("Internal server error") + })?; let cfg = planner.config(); Ok(Response::new(proto::PlannerConfigResponse { @@ -149,8 +155,10 @@ impl VeriSimPlanner for PlannerService { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let mut planner = self.state.planner.lock() - .map_err(|_| { error!("Planner lock poisoned in gRPC"); Status::internal("Internal server error") })?; + let mut planner = self.state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in gRPC"); + Status::internal("Internal server error") + })?; let mut cfg = planner.config().clone(); if !req.global_mode.is_empty() { @@ -182,8 +190,10 @@ impl VeriSimPlanner for PlannerService { &self, _request: Request, ) -> Result, Status> { - let planner = self.state.planner.lock() - .map_err(|_| { error!("Planner lock poisoned in gRPC"); Status::internal("Internal server error") })?; + let planner = self.state.planner.lock().map_err(|_| { + error!("Planner lock poisoned in gRPC"); + Status::internal("Internal server error") + })?; let stores: Vec = verisim_planner::Modality::ALL .iter() @@ -246,15 +256,10 @@ impl VeriSimOctad for OctadService { } use verisim_octad::OctadStore; - let h = self - .state - .octad_store - .create(input) - .await - .map_err(|e| { - error!(error = %e, "gRPC octad creation failed"); - Status::internal("Internal server error") - })?; + let h = self.state.octad_store.create(input).await.map_err(|e| { + error!(error = %e, "gRPC octad creation failed"); + Status::internal("Internal server error") + })?; Ok(Response::new(octad_to_proto(&h))) } @@ -348,7 +353,11 @@ impl VeriSimOctad for OctadService { request: Request, ) -> Result, Status> { let req = request.into_inner(); - let limit = if req.limit > 0 { req.limit as usize } else { 10 }; + let limit = if req.limit > 0 { + req.limit as usize + } else { + 10 + }; use verisim_octad::OctadStore; let octads = self diff --git a/rust-core/verisim-api/src/lib.rs b/rust-core/verisim-api/src/lib.rs index c9fd02c..3af49c6 100644 --- a/rust-core/verisim-api/src/lib.rs +++ b/rust-core/verisim-api/src/lib.rs @@ -31,49 +31,51 @@ use std::sync::Mutex; use verisim_document::TantivyDocumentStore; use verisim_drift::{DriftDetector, DriftMetrics, DriftThresholds, DriftType}; -#[cfg(not(feature = "persistent"))] -use verisim_graph::SimpleGraphStore; #[cfg(feature = "persistent")] use verisim_graph::RedbGraphStore; -#[cfg(feature = "persistent")] -use verisim_vector::RedbVectorStore; -#[cfg(feature = "persistent")] -use verisim_tensor::RedbTensorStore; -#[cfg(feature = "persistent")] -use verisim_semantic::RedbSemanticStore; -#[cfg(feature = "persistent")] -use verisim_temporal::RedbVersionStore; -#[cfg(feature = "persistent")] -use verisim_provenance::RedbProvenanceStore; -#[cfg(feature = "persistent")] -use verisim_spatial::RedbSpatialStore; -use verisim_planner::{ - CacheConfig, ExplainOutput, ExplainAnalyzeOutput, LogicalPlan, ParamValue, - PhysicalPlan, PlanCache, Planner, PlannerConfig, PreparedId, PreparedStatement, - Profiler, SlowQueryLog, SlowQuerySummary, StatisticsCollector, -}; +#[cfg(not(feature = "persistent"))] +use verisim_graph::SimpleGraphStore; +use verisim_normalizer::{create_default_normalizer, Normalizer, NormalizerStatus}; use verisim_octad::{ - BoundingBox, Coordinates, OctadConfig, OctadDocumentInput, OctadGraphInput, + BoundingBox, Coordinates, InMemoryOctadStore, OctadConfig, OctadDocumentInput, OctadGraphInput, OctadId, OctadInput, OctadProvenanceInput, OctadSemanticInput, OctadSnapshot, - OctadSpatialInput, OctadStore, OctadTensorInput, OctadVectorInput, - InMemoryOctadStore, ProvenanceStore, SpatialStore, + OctadSpatialInput, OctadStore, OctadTensorInput, OctadVectorInput, ProvenanceStore, + SpatialStore, +}; +use verisim_planner::{ + CacheConfig, ExplainAnalyzeOutput, ExplainOutput, LogicalPlan, ParamValue, PhysicalPlan, + PlanCache, Planner, PlannerConfig, PreparedId, PreparedStatement, Profiler, SlowQueryLog, + SlowQuerySummary, StatisticsCollector, }; #[cfg(not(feature = "persistent"))] use verisim_provenance::InMemoryProvenanceStore; -#[cfg(not(feature = "persistent"))] -use verisim_spatial::InMemorySpatialStore; -use verisim_normalizer::{create_default_normalizer, Normalizer, NormalizerStatus}; +#[cfg(feature = "persistent")] +use verisim_provenance::RedbProvenanceStore; +use verisim_semantic::circuit_registry::CircuitRegistry; +use verisim_semantic::zkp_bridge::{ + self as zkp_api, PrivacyLevel, ZkpProofRequest as ZkpBridgeRequest, +}; #[cfg(not(feature = "persistent"))] use verisim_semantic::InMemorySemanticStore; -use verisim_semantic::zkp_bridge::{self as zkp_api, PrivacyLevel, ZkpProofRequest as ZkpBridgeRequest}; -use verisim_semantic::circuit_registry::CircuitRegistry; +#[cfg(feature = "persistent")] +use verisim_semantic::RedbSemanticStore; +#[cfg(not(feature = "persistent"))] +use verisim_spatial::InMemorySpatialStore; +#[cfg(feature = "persistent")] +use verisim_spatial::RedbSpatialStore; #[cfg(not(feature = "persistent"))] use verisim_temporal::InMemoryVersionStore; +#[cfg(feature = "persistent")] +use verisim_temporal::RedbVersionStore; #[cfg(not(feature = "persistent"))] use verisim_tensor::InMemoryTensorStore; -use verisim_vector::DistanceMetric; +#[cfg(feature = "persistent")] +use verisim_tensor::RedbTensorStore; #[cfg(not(feature = "persistent"))] use verisim_vector::BruteForceVectorStore; +use verisim_vector::DistanceMetric; +#[cfg(feature = "persistent")] +use verisim_vector::RedbVectorStore; /// Type alias for our concrete OctadStore implementation (octad: 8 modality stores). /// @@ -129,11 +131,17 @@ impl IntoResponse for ApiError { ApiError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg.clone()), ApiError::Internal(msg) => { error!(error = %msg, "Internal server error"); - (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + ) } ApiError::Serialization(msg) => { error!(error = %msg, "Serialization error"); - (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string()) + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Internal server error".to_string(), + ) } }; @@ -207,14 +215,22 @@ fn validate_limit(limit: usize) -> usize { /// Validate a octad ID: max 128 chars, alphanumeric + dash + underscore only. fn validate_octad_id(id: &str) -> Result<(), ApiError> { if id.is_empty() { - return Err(ApiError::BadRequest("Octad ID must not be empty".to_string())); + return Err(ApiError::BadRequest( + "Octad ID must not be empty".to_string(), + )); } if id.len() > 128 { - return Err(ApiError::BadRequest("Octad ID must be at most 128 characters".to_string())); + return Err(ApiError::BadRequest( + "Octad ID must be at most 128 characters".to_string(), + )); } - if !id.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') { + if !id + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '_') + { return Err(ApiError::BadRequest( - "Octad ID must contain only alphanumeric characters, dashes, and underscores".to_string(), + "Octad ID must contain only alphanumeric characters, dashes, and underscores" + .to_string(), )); } Ok(()) @@ -513,8 +529,7 @@ impl AppState { SimpleGraphStore::in_memory().map_err(|e| ApiError::Internal(e.to_string()))?, ); let d = Arc::new( - TantivyDocumentStore::in_memory() - .map_err(|e| ApiError::Internal(e.to_string()))?, + TantivyDocumentStore::in_memory().map_err(|e| ApiError::Internal(e.to_string()))?, ); (g, d) }; @@ -654,15 +669,15 @@ impl AppState { let planner = Arc::new(Mutex::new(Planner::new(PlannerConfig::default()))); let plan_cache = Arc::new(PlanCache::new(CacheConfig::default())); let slow_query_log = Arc::new(SlowQueryLog::new(Default::default())); - let transaction_manager = Arc::new( - transaction::TransactionManager::new(transaction::TransactionConfig::default()), - ); + let transaction_manager = Arc::new(transaction::TransactionManager::new( + transaction::TransactionConfig::default(), + )); - let self_endpoint = format!("http://{}:{}{}", config.host, config.port, config.version_prefix); - let federation = federation::FederationState::new( - "self".to_string(), - self_endpoint, + let self_endpoint = format!( + "http://{}:{}{}", + config.host, config.port, config.version_prefix ); + let federation = federation::FederationState::new("self".to_string(), self_endpoint); let auth = auth::AuthState::default(); let circuit_registry = Arc::new(CircuitRegistry::new()); @@ -695,7 +710,10 @@ pub fn build_router(state: AppState) -> Router { .route("/ready", get(ready_handler)) .route("/metrics", get(metrics_handler)) // Octad CRUD - .route("/octads", get(list_octads_handler).post(create_octad_handler)) + .route( + "/octads", + get(list_octads_handler).post(create_octad_handler), + ) .route("/octads/{id}", get(get_octad_handler)) .route("/octads/{id}", put(update_octad_handler)) .route("/octads/{id}", delete(delete_octad_handler)) @@ -707,7 +725,10 @@ pub fn build_router(state: AppState) -> Router { .route("/drift/status", get(drift_status_handler)) .route("/drift/entity/{id}", get(entity_drift_handler)) .route("/normalizer/status", get(normalizer_status_handler)) - .route("/normalizer/trigger/{id}", post(trigger_normalization_handler)) + .route( + "/normalizer/trigger/{id}", + post(trigger_normalization_handler), + ) // Meta-query store (homoiconicity: queries as octads) .route("/queries", post(store_query_handler)) .route("/queries/similar", post(similar_queries_handler)) @@ -719,7 +740,10 @@ pub fn build_router(state: AppState) -> Router { .route("/planner/config", put(put_planner_config_handler)) .route("/planner/stats", get(planner_stats_handler)) // EXPLAIN ANALYZE - .route("/query/explain-analyze", post(query_explain_analyze_handler)) + .route( + "/query/explain-analyze", + post(query_explain_analyze_handler), + ) // Prepared statements .route("/prepared", post(prepared_create_handler)) .route("/prepared/{id}", get(prepared_get_handler)) @@ -729,20 +753,35 @@ pub fn build_router(state: AppState) -> Router { .route("/planner/slow-queries", get(slow_queries_handler)) // Transaction endpoints .route("/transactions/begin", post(transaction_begin_handler)) - .route("/transactions/{id}/commit", post(transaction_commit_handler)) - .route("/transactions/{id}/rollback", post(transaction_rollback_handler)) + .route( + "/transactions/{id}/commit", + post(transaction_commit_handler), + ) + .route( + "/transactions/{id}/rollback", + post(transaction_rollback_handler), + ) .route("/transactions/{id}", get(transaction_status_handler)) // ZKP proof endpoints .route("/proofs/generate", post(proof_generate_handler)) .route("/proofs/verify", post(proof_verify_handler)) - .route("/proofs/generate-with-circuit", post(proof_generate_with_circuit_handler)) + .route( + "/proofs/generate-with-circuit", + post(proof_generate_with_circuit_handler), + ) // Provenance endpoints .route("/provenance/{id}", get(provenance_get_chain_handler)) .route("/provenance/{id}/record", post(provenance_record_handler)) .route("/provenance/{id}/verify", get(provenance_verify_handler)) // Spatial search endpoints - .route("/spatial/search/radius", post(spatial_radius_search_handler)) - .route("/spatial/search/bounds", post(spatial_bounds_search_handler)) + .route( + "/spatial/search/radius", + post(spatial_radius_search_handler), + ) + .route( + "/spatial/search/bounds", + post(spatial_bounds_search_handler), + ) .route("/spatial/search/nearest", post(spatial_nearest_handler)) // VQL text query endpoint (used by verisim-repl) .route("/vql/execute", post(vql::vql_execute_handler)) @@ -809,12 +848,16 @@ async fn health_handler(State(state): State) -> (StatusCode, Json, -) -> Result<(StatusCode, [(axum::http::header::HeaderName, &'static str); 1], String), ApiError> { - use prometheus::{Encoder, TextEncoder, GaugeVec, Opts, Registry}; +async fn metrics_handler(State(state): State) -> Result { + use prometheus::{Encoder, GaugeVec, Opts, Registry, TextEncoder}; let registry = Registry::new(); @@ -826,28 +869,46 @@ async fn metrics_handler( .map_err(|e| ApiError::Internal(e.to_string()))?; let drift_avg_gauge = GaugeVec::new( - Opts::new("verisimdb_drift_moving_average", "Drift moving average by type"), + Opts::new( + "verisimdb_drift_moving_average", + "Drift moving average by type", + ), &["drift_type"], ) .map_err(|e| ApiError::Internal(e.to_string()))?; let drift_count_gauge = GaugeVec::new( - Opts::new("verisimdb_drift_measurement_count", "Drift measurement count by type"), + Opts::new( + "verisimdb_drift_measurement_count", + "Drift measurement count by type", + ), &["drift_type"], ) .map_err(|e| ApiError::Internal(e.to_string()))?; - registry.register(Box::new(drift_gauge.clone())).map_err(|e| ApiError::Internal(e.to_string()))?; - registry.register(Box::new(drift_avg_gauge.clone())).map_err(|e| ApiError::Internal(e.to_string()))?; - registry.register(Box::new(drift_count_gauge.clone())).map_err(|e| ApiError::Internal(e.to_string()))?; + registry + .register(Box::new(drift_gauge.clone())) + .map_err(|e| ApiError::Internal(e.to_string()))?; + registry + .register(Box::new(drift_avg_gauge.clone())) + .map_err(|e| ApiError::Internal(e.to_string()))?; + registry + .register(Box::new(drift_count_gauge.clone())) + .map_err(|e| ApiError::Internal(e.to_string()))?; // Populate drift metrics if let Ok(all_metrics) = state.drift_detector.all_metrics() { for (drift_type, metrics) in &all_metrics { let label = drift_type.to_string(); - drift_gauge.with_label_values(&[&label]).set(metrics.current_score); - drift_avg_gauge.with_label_values(&[&label]).set(metrics.moving_average); - drift_count_gauge.with_label_values(&[&label]).set(metrics.measurement_count as f64); + drift_gauge + .with_label_values(&[&label]) + .set(metrics.current_score); + drift_avg_gauge + .with_label_values(&[&label]) + .set(metrics.moving_average); + drift_count_gauge + .with_label_values(&[&label]) + .set(metrics.measurement_count as f64); } } @@ -855,20 +916,25 @@ async fn metrics_handler( let uptime = prometheus::Gauge::new("verisimdb_uptime_seconds", "Server uptime in seconds") .map_err(|e| ApiError::Internal(e.to_string()))?; uptime.set(state.start_time.elapsed().as_secs() as f64); - registry.register(Box::new(uptime)).map_err(|e| ApiError::Internal(e.to_string()))?; + registry + .register(Box::new(uptime)) + .map_err(|e| ApiError::Internal(e.to_string()))?; // Encode let encoder = TextEncoder::new(); let mut buffer = Vec::new(); - encoder.encode(®istry.gather(), &mut buffer) + encoder + .encode(®istry.gather(), &mut buffer) .map_err(|e| ApiError::Internal(e.to_string()))?; - let output = String::from_utf8(buffer) - .map_err(|e| ApiError::Internal(e.to_string()))?; + let output = String::from_utf8(buffer).map_err(|e| ApiError::Internal(e.to_string()))?; Ok(( StatusCode::OK, - [(axum::http::header::CONTENT_TYPE, "text/plain; version=0.0.4; charset=utf-8")], + [( + axum::http::header::CONTENT_TYPE, + "text/plain; version=0.0.4; charset=utf-8", + )], output, )) } @@ -1000,7 +1066,11 @@ async fn text_search_handler( ) -> Result>, ApiError> { let q = match query.q { Some(q) if !q.is_empty() => q, - _ => return Err(ApiError::BadRequest("Query parameter 'q' must not be empty".to_string())), + _ => { + return Err(ApiError::BadRequest( + "Query parameter 'q' must not be empty".to_string(), + )) + } }; let limit = validate_limit(query.limit.unwrap_or(10)); @@ -1092,7 +1162,9 @@ pub struct RelatedQuery { async fn drift_status_handler( State(state): State, ) -> Result>, ApiError> { - let all_metrics = state.drift_detector.all_metrics() + let all_metrics = state + .drift_detector + .all_metrics() .map_err(|e| ApiError::Internal(e.to_string()))?; let responses: Vec = all_metrics @@ -1130,11 +1202,17 @@ async fn entity_drift_handler( .ok_or_else(|| ApiError::NotFound(format!("Octad {} not found", id)))?; // Get aggregate health from drift detector - let all_metrics = state.drift_detector.all_metrics() + let all_metrics = state + .drift_detector + .all_metrics() .map_err(|e| ApiError::Internal(e.to_string()))?; let (worst_type, worst_score) = all_metrics .iter() - .max_by(|a, b| a.1.current_score.partial_cmp(&b.1.current_score).unwrap_or(std::cmp::Ordering::Equal)) + .max_by(|a, b| { + a.1.current_score + .partial_cmp(&b.1.current_score) + .unwrap_or(std::cmp::Ordering::Equal) + }) .map(|(dt, m)| (dt.to_string(), m.current_score)) .unwrap_or_else(|| ("none".to_string(), 0.0)); @@ -1195,7 +1273,10 @@ async fn query_plan_handler( State(state): State, Json(plan): Json, ) -> Result, ApiError> { - let planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + let planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; let physical = planner .optimize(&plan) .map_err(|e| ApiError::BadRequest(e.to_string()))?; @@ -1208,7 +1289,10 @@ async fn query_explain_handler( State(state): State, Json(plan): Json, ) -> Result, ApiError> { - let planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + let planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; let explain = planner .explain(&plan) .map_err(|e| ApiError::BadRequest(e.to_string()))?; @@ -1220,7 +1304,10 @@ async fn query_explain_handler( async fn get_planner_config_handler( State(state): State, ) -> Result, ApiError> { - let planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + let planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; Ok(Json(planner.config().clone())) } @@ -1230,7 +1317,10 @@ async fn put_planner_config_handler( State(state): State, Json(config): Json, ) -> Result, ApiError> { - let mut planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + let mut planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; planner.set_config(config); Ok(Json(planner.config().clone())) } @@ -1240,7 +1330,10 @@ async fn put_planner_config_handler( async fn planner_stats_handler( State(state): State, ) -> Result, ApiError> { - let planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + let planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; Ok(Json(planner.stats().clone())) } @@ -1364,18 +1457,17 @@ async fn optimize_query_handler( // Compute cost vector from the planner let cost_vector = if let Some(Json(logical_plan)) = body { // If a logical plan was provided, run the planner on it - let planner = state.planner.lock().map_err(|_| { - ApiError::Internal("Planner lock poisoned".to_string()) - })?; + let planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; match planner.explain(&logical_plan) { - Ok(explain) => { - explain - .steps - .iter() - .map(|s| s.estimated_cost_ms) - .collect::>() - } + Ok(explain) => explain + .steps + .iter() + .map(|s| s.estimated_cost_ms) + .collect::>(), Err(_) => vec![1.0, 0.0, 0.0], } } else { @@ -1389,11 +1481,13 @@ async fn optimize_query_handler( }; // Update the octad with new tensor data (cost vector) - let mut update_input = OctadInput::default(); - update_input.tensor = Some(OctadTensorInput { - shape: vec![1, cost_vector.len()], - data: cost_vector, - }); + let mut update_input = OctadInput { + tensor: Some(OctadTensorInput { + shape: vec![1, cost_vector.len()], + data: cost_vector, + }), + ..OctadInput::default() + }; update_input .metadata .insert("optimized_at".to_string(), chrono::Utc::now().to_rfc3339()); @@ -1428,7 +1522,10 @@ async fn query_explain_analyze_handler( State(state): State, Json(request): Json, ) -> Result, ApiError> { - let mut planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + let mut planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; let explain = planner .explain(&request.plan) .map_err(|e| ApiError::BadRequest(e.to_string()))?; @@ -1442,7 +1539,8 @@ async fn query_explain_analyze_handler( // Record simulated or default step timings let now = chrono::Utc::now(); for (i, step) in physical.steps.iter().enumerate() { - let actual_ms = request.simulated_timings + let actual_ms = request + .simulated_timings .as_ref() .and_then(|t| t.get(i).copied()) .unwrap_or(step.cost.time_ms * 1.1); // Default: 10% slower than estimate @@ -1474,8 +1572,9 @@ async fn prepared_create_handler( ) -> Result<(StatusCode, Json), ApiError> { let id = state.plan_cache.prepare(&request.query, request.plan).await; - let stmt = state.plan_cache.get(&id).await - .ok_or_else(|| ApiError::Internal("Failed to retrieve prepared statement after creation".to_string()))?; + let stmt = state.plan_cache.get(&id).await.ok_or_else(|| { + ApiError::Internal("Failed to retrieve prepared statement after creation".to_string()) + })?; Ok((StatusCode::CREATED, Json(stmt))) } @@ -1487,7 +1586,10 @@ async fn prepared_get_handler( Path(id): Path, ) -> Result, ApiError> { let prep_id = PreparedId::new(&id); - let stmt = state.plan_cache.get(&prep_id).await + let stmt = state + .plan_cache + .get(&prep_id) + .await .ok_or_else(|| ApiError::NotFound(format!("Prepared statement '{}' not found", id)))?; Ok(Json(stmt)) } @@ -1508,7 +1610,8 @@ async fn prepared_execute_handler( ) -> Result, ApiError> { let prep_id = PreparedId::new(&id); - let stmt = state.plan_cache + let stmt = state + .plan_cache .execute_prepared(&prep_id, &request.params) .await .map_err(|e| ApiError::BadRequest(e.to_string()))?; @@ -1517,12 +1620,20 @@ async fn prepared_execute_handler( let physical = if let Some(cached) = stmt.cached_physical_plan { cached } else { - let planner = state.planner.lock().map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; - planner.optimize(&stmt.logical_plan).map_err(|e| ApiError::Internal(e.to_string()))? + let planner = state + .planner + .lock() + .map_err(|_| ApiError::Internal("Planner lock poisoned".to_string()))?; + planner + .optimize(&stmt.logical_plan) + .map_err(|e| ApiError::Internal(e.to_string()))? }; // Cache the physical plan for future use - state.plan_cache.cache_plan(&prep_id, physical.clone()).await; + state + .plan_cache + .cache_plan(&prep_id, physical.clone()) + .await; Ok(Json(physical)) } @@ -1552,12 +1663,14 @@ async fn slow_queries_handler( async fn transaction_begin_handler( State(state): State, ) -> Result<(StatusCode, Json), ApiError> { - let txn_id = state.transaction_manager + let txn_id = state + .transaction_manager .begin() .await .map_err(|e| ApiError::Internal(e.to_string()))?; - let status = state.transaction_manager + let status = state + .transaction_manager .status(&txn_id) .await .map_err(|e| ApiError::Internal(e.to_string()))?; @@ -1571,9 +1684,10 @@ async fn transaction_commit_handler( State(state): State, Path(id): Path, ) -> Result, ApiError> { - let txn_id = transaction::TransactionId::from_str(&id); + let txn_id = transaction::TransactionId::from_string(&id); - let _ops = state.transaction_manager + let _ops = state + .transaction_manager .commit(&txn_id) .await .map_err(|e| match e { @@ -1582,7 +1696,8 @@ async fn transaction_commit_handler( })?; // In a full implementation, ops would be applied to the octad store here - let status = state.transaction_manager + let status = state + .transaction_manager .status(&txn_id) .await .map_err(|e| ApiError::Internal(e.to_string()))?; @@ -1596,9 +1711,10 @@ async fn transaction_rollback_handler( State(state): State, Path(id): Path, ) -> Result, ApiError> { - let txn_id = transaction::TransactionId::from_str(&id); + let txn_id = transaction::TransactionId::from_string(&id); - let _discarded = state.transaction_manager + let _discarded = state + .transaction_manager .rollback(&txn_id) .await .map_err(|e| match e { @@ -1606,7 +1722,8 @@ async fn transaction_rollback_handler( _ => ApiError::BadRequest(e.to_string()), })?; - let status = state.transaction_manager + let status = state + .transaction_manager .status(&txn_id) .await .map_err(|e| ApiError::Internal(e.to_string()))?; @@ -1620,9 +1737,10 @@ async fn transaction_status_handler( State(state): State, Path(id): Path, ) -> Result, ApiError> { - let txn_id = transaction::TransactionId::from_str(&id); + let txn_id = transaction::TransactionId::from_string(&id); - let status = state.transaction_manager + let status = state + .transaction_manager .status(&txn_id) .await .map_err(|e| match e { @@ -1708,7 +1826,9 @@ async fn proof_generate_handler( }; let membership_set = request.membership_set.as_ref().map(|set| { - set.iter().map(|s| s.as_bytes().to_vec()).collect::>() + set.iter() + .map(|s| s.as_bytes().to_vec()) + .collect::>() }); let bridge_request = ZkpBridgeRequest { @@ -1816,8 +1936,7 @@ pub async fn serve(config: ApiConfig) -> Result<(), std::io::Error> { info!(addr = %http_addr, "Starting VeriSimDB HTTP server"); let listener = TcpListener::bind(&http_addr).await?; - let http_server = axum::serve(listener, app) - .with_graceful_shutdown(shutdown_signal()); + let http_server = axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()); // Start gRPC server (if enabled) if config.grpc_port > 0 { @@ -2232,7 +2351,7 @@ mod tests { use tower::ServiceExt; async fn create_test_state() -> AppState { - let mut config = ApiConfig { + let config = ApiConfig { vector_dimension: 3, ..Default::default() }; @@ -2244,11 +2363,8 @@ mod tests { use std::sync::atomic::{AtomicU64, Ordering}; static COUNTER: AtomicU64 = AtomicU64::new(0); let id = COUNTER.fetch_add(1, Ordering::Relaxed); - let tmp = std::env::temp_dir().join(format!( - "verisimdb-test-{}-{}", - std::process::id(), - id, - )); + let tmp = + std::env::temp_dir().join(format!("verisimdb-test-{}-{}", std::process::id(), id,)); config.persistence_dir = Some(tmp.to_string_lossy().into_owned()); } diff --git a/rust-core/verisim-api/src/main.rs b/rust-core/verisim-api/src/main.rs index a7889a0..5f33f80 100644 --- a/rust-core/verisim-api/src/main.rs +++ b/rust-core/verisim-api/src/main.rs @@ -28,9 +28,7 @@ async fn main() -> Result<(), Box> { .with_env_filter(env_filter) .init(); } else { - tracing_subscriber::fmt() - .with_env_filter(env_filter) - .init(); + tracing_subscriber::fmt().with_env_filter(env_filter).init(); } // IPv6-only by default; VERISIM_ENABLE_IPV4=true for dual-stack (0.0.0.0) @@ -79,7 +77,11 @@ async fn main() -> Result<(), Box> { .unwrap_or(1024), }; - let storage_mode = if cfg!(feature = "persistent") { "persistent" } else { "in-memory" }; + let storage_mode = if cfg!(feature = "persistent") { + "persistent" + } else { + "in-memory" + }; tracing::info!( host = %config.host, diff --git a/rust-core/verisim-api/src/proto/verisim.rs b/rust-core/verisim-api/src/proto/verisim.rs index 92de8dc..20be1a0 100644 --- a/rust-core/verisim-api/src/proto/verisim.rs +++ b/rust-core/verisim-api/src/proto/verisim.rs @@ -199,10 +199,10 @@ pub mod veri_sim_planner_client { dead_code, missing_docs, clippy::wildcard_imports, - clippy::let_unit_value, + clippy::let_unit_value )] - use tonic::codegen::*; use tonic::codegen::http::Uri; + use tonic::codegen::*; #[derive(Debug, Clone)] pub struct VeriSimPlannerClient { inner: tonic::client::Grpc, @@ -246,9 +246,8 @@ pub mod veri_sim_planner_client { >::ResponseBody, >, >, - , - >>::Error: Into + std::marker::Send + std::marker::Sync, + >>::Error: + Into + std::marker::Send + std::marker::Sync, { VeriSimPlannerClient::new(InterceptedService::new(inner, interceptor)) } @@ -287,22 +286,13 @@ pub mod veri_sim_planner_client { pub async fn optimize_plan( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result, tonic::Status> + { + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimPlanner/OptimizePlan", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimPlanner/OptimizePlan"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimPlanner", "OptimizePlan")); @@ -312,22 +302,12 @@ pub mod veri_sim_planner_client { pub async fn explain_plan( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result, tonic::Status> { + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimPlanner/ExplainPlan", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimPlanner/ExplainPlan"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimPlanner", "ExplainPlan")); @@ -337,22 +317,13 @@ pub mod veri_sim_planner_client { pub async fn get_config( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result, tonic::Status> + { + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimPlanner/GetConfig", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimPlanner/GetConfig"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimPlanner", "GetConfig")); @@ -362,22 +333,13 @@ pub mod veri_sim_planner_client { pub async fn set_config( &mut self, request: impl tonic::IntoRequest, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - > { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + ) -> std::result::Result, tonic::Status> + { + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimPlanner/SetConfig", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimPlanner/SetConfig"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimPlanner", "SetConfig")); @@ -388,18 +350,11 @@ pub mod veri_sim_planner_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimPlanner/GetStats", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimPlanner/GetStats"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimPlanner", "GetStats")); @@ -414,7 +369,7 @@ pub mod veri_sim_planner_server { dead_code, missing_docs, clippy::wildcard_imports, - clippy::let_unit_value, + clippy::let_unit_value )] use tonic::codegen::*; /// Generated trait containing gRPC methods that should be implemented for use with VeriSimPlannerServer. @@ -424,10 +379,7 @@ pub mod veri_sim_planner_server { async fn optimize_plan( &self, request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; + ) -> std::result::Result, tonic::Status>; /// Generate EXPLAIN output for a logical plan. async fn explain_plan( &self, @@ -437,18 +389,12 @@ pub mod veri_sim_planner_server { async fn get_config( &self, request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; + ) -> std::result::Result, tonic::Status>; /// Update planner configuration. async fn set_config( &self, request: tonic::Request, - ) -> std::result::Result< - tonic::Response, - tonic::Status, - >; + ) -> std::result::Result, tonic::Status>; /// Get per-modality statistics snapshot. async fn get_stats( &self, @@ -476,10 +422,7 @@ pub mod veri_sim_planner_server { max_encoding_message_size: None, } } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> InterceptedService + pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService where F: tonic::service::Interceptor, { @@ -534,15 +477,11 @@ pub mod veri_sim_planner_server { "/verisim.VeriSimPlanner/OptimizePlan" => { #[allow(non_camel_case_types)] struct OptimizePlanSvc(pub Arc); - impl< - T: VeriSimPlanner, - > tonic::server::UnaryService - for OptimizePlanSvc { + impl tonic::server::UnaryService + for OptimizePlanSvc + { type Response = super::PhysicalPlanResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, @@ -579,15 +518,11 @@ pub mod veri_sim_planner_server { "/verisim.VeriSimPlanner/ExplainPlan" => { #[allow(non_camel_case_types)] struct ExplainPlanSvc(pub Arc); - impl< - T: VeriSimPlanner, - > tonic::server::UnaryService - for ExplainPlanSvc { + impl tonic::server::UnaryService + for ExplainPlanSvc + { type Response = super::ExplainResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, @@ -624,17 +559,10 @@ pub mod veri_sim_planner_server { "/verisim.VeriSimPlanner/GetConfig" => { #[allow(non_camel_case_types)] struct GetConfigSvc(pub Arc); - impl tonic::server::UnaryService - for GetConfigSvc { + impl tonic::server::UnaryService for GetConfigSvc { type Response = super::PlannerConfigResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { + type Future = BoxFuture, tonic::Status>; + fn call(&mut self, request: tonic::Request) -> Self::Future { let inner = Arc::clone(&self.0); let fut = async move { ::get_config(&inner, request).await @@ -667,15 +595,11 @@ pub mod veri_sim_planner_server { "/verisim.VeriSimPlanner/SetConfig" => { #[allow(non_camel_case_types)] struct SetConfigSvc(pub Arc); - impl< - T: VeriSimPlanner, - > tonic::server::UnaryService - for SetConfigSvc { + impl tonic::server::UnaryService + for SetConfigSvc + { type Response = super::PlannerConfigResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, @@ -712,17 +636,10 @@ pub mod veri_sim_planner_server { "/verisim.VeriSimPlanner/GetStats" => { #[allow(non_camel_case_types)] struct GetStatsSvc(pub Arc); - impl tonic::server::UnaryService - for GetStatsSvc { + impl tonic::server::UnaryService for GetStatsSvc { type Response = super::StatsResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; - fn call( - &mut self, - request: tonic::Request, - ) -> Self::Future { + type Future = BoxFuture, tonic::Status>; + fn call(&mut self, request: tonic::Request) -> Self::Future { let inner = Arc::clone(&self.0); let fut = async move { ::get_stats(&inner, request).await @@ -752,25 +669,19 @@ pub mod veri_sim_planner_server { }; Box::pin(fut) } - _ => { - Box::pin(async move { - let mut response = http::Response::new( - tonic::body::Body::default(), - ); - let headers = response.headers_mut(); - headers - .insert( - tonic::Status::GRPC_STATUS, - (tonic::Code::Unimplemented as i32).into(), - ); - headers - .insert( - http::header::CONTENT_TYPE, - tonic::metadata::GRPC_CONTENT_TYPE, - ); - Ok(response) - }) - } + _ => Box::pin(async move { + let mut response = http::Response::new(tonic::body::Body::default()); + let headers = response.headers_mut(); + headers.insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers.insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }), } } } @@ -799,10 +710,10 @@ pub mod veri_sim_octad_client { dead_code, missing_docs, clippy::wildcard_imports, - clippy::let_unit_value, + clippy::let_unit_value )] - use tonic::codegen::*; use tonic::codegen::http::Uri; + use tonic::codegen::*; #[derive(Debug, Clone)] pub struct VeriSimOctadClient { inner: tonic::client::Grpc, @@ -846,9 +757,8 @@ pub mod veri_sim_octad_client { >::ResponseBody, >, >, - , - >>::Error: Into + std::marker::Send + std::marker::Sync, + >>::Error: + Into + std::marker::Send + std::marker::Sync, { VeriSimOctadClient::new(InterceptedService::new(inner, interceptor)) } @@ -888,18 +798,11 @@ pub mod veri_sim_octad_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimOctad/Create", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimOctad/Create"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimOctad", "Create")); @@ -910,18 +813,14 @@ pub mod veri_sim_octad_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimOctad/Get"); let mut req = request.into_request(); - req.extensions_mut().insert(GrpcMethod::new("verisim.VeriSimOctad", "Get")); + req.extensions_mut() + .insert(GrpcMethod::new("verisim.VeriSimOctad", "Get")); self.inner.unary(req, path, codec).await } /// Update an existing octad. @@ -929,18 +828,11 @@ pub mod veri_sim_octad_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimOctad/Update", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimOctad/Update"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimOctad", "Update")); @@ -951,18 +843,11 @@ pub mod veri_sim_octad_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimOctad/Delete", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimOctad/Delete"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimOctad", "Delete")); @@ -973,18 +858,11 @@ pub mod veri_sim_octad_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimOctad/SearchText", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimOctad/SearchText"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimOctad", "SearchText")); @@ -995,18 +873,11 @@ pub mod veri_sim_octad_client { &mut self, request: impl tonic::IntoRequest, ) -> std::result::Result, tonic::Status> { - self.inner - .ready() - .await - .map_err(|e| { - tonic::Status::unknown( - format!("Service was not ready: {}", e.into()), - ) - })?; + self.inner.ready().await.map_err(|e| { + tonic::Status::unknown(format!("Service was not ready: {}", e.into())) + })?; let codec = tonic_prost::ProstCodec::default(); - let path = http::uri::PathAndQuery::from_static( - "/verisim.VeriSimOctad/SearchVector", - ); + let path = http::uri::PathAndQuery::from_static("/verisim.VeriSimOctad/SearchVector"); let mut req = request.into_request(); req.extensions_mut() .insert(GrpcMethod::new("verisim.VeriSimOctad", "SearchVector")); @@ -1021,7 +892,7 @@ pub mod veri_sim_octad_server { dead_code, missing_docs, clippy::wildcard_imports, - clippy::let_unit_value, + clippy::let_unit_value )] use tonic::codegen::*; /// Generated trait containing gRPC methods that should be implemented for use with VeriSimOctadServer. @@ -1079,10 +950,7 @@ pub mod veri_sim_octad_server { max_encoding_message_size: None, } } - pub fn with_interceptor( - inner: T, - interceptor: F, - ) -> InterceptedService + pub fn with_interceptor(inner: T, interceptor: F) -> InterceptedService where F: tonic::service::Interceptor, { @@ -1137,23 +1005,16 @@ pub mod veri_sim_octad_server { "/verisim.VeriSimOctad/Create" => { #[allow(non_camel_case_types)] struct CreateSvc(pub Arc); - impl< - T: VeriSimOctad, - > tonic::server::UnaryService - for CreateSvc { + impl tonic::server::UnaryService for CreateSvc { type Response = super::OctadResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - ::create(&inner, request).await - }; + let fut = + async move { ::create(&inner, request).await }; Box::pin(fut) } } @@ -1182,22 +1043,16 @@ pub mod veri_sim_octad_server { "/verisim.VeriSimOctad/Get" => { #[allow(non_camel_case_types)] struct GetSvc(pub Arc); - impl< - T: VeriSimOctad, - > tonic::server::UnaryService for GetSvc { + impl tonic::server::UnaryService for GetSvc { type Response = super::OctadResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - ::get(&inner, request).await - }; + let fut = + async move { ::get(&inner, request).await }; Box::pin(fut) } } @@ -1226,23 +1081,16 @@ pub mod veri_sim_octad_server { "/verisim.VeriSimOctad/Update" => { #[allow(non_camel_case_types)] struct UpdateSvc(pub Arc); - impl< - T: VeriSimOctad, - > tonic::server::UnaryService - for UpdateSvc { + impl tonic::server::UnaryService for UpdateSvc { type Response = super::OctadResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - ::update(&inner, request).await - }; + let fut = + async move { ::update(&inner, request).await }; Box::pin(fut) } } @@ -1271,23 +1119,16 @@ pub mod veri_sim_octad_server { "/verisim.VeriSimOctad/Delete" => { #[allow(non_camel_case_types)] struct DeleteSvc(pub Arc); - impl< - T: VeriSimOctad, - > tonic::server::UnaryService - for DeleteSvc { + impl tonic::server::UnaryService for DeleteSvc { type Response = super::Empty; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, ) -> Self::Future { let inner = Arc::clone(&self.0); - let fut = async move { - ::delete(&inner, request).await - }; + let fut = + async move { ::delete(&inner, request).await }; Box::pin(fut) } } @@ -1316,15 +1157,9 @@ pub mod veri_sim_octad_server { "/verisim.VeriSimOctad/SearchText" => { #[allow(non_camel_case_types)] struct SearchTextSvc(pub Arc); - impl< - T: VeriSimOctad, - > tonic::server::UnaryService - for SearchTextSvc { + impl tonic::server::UnaryService for SearchTextSvc { type Response = super::SearchResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, @@ -1361,15 +1196,11 @@ pub mod veri_sim_octad_server { "/verisim.VeriSimOctad/SearchVector" => { #[allow(non_camel_case_types)] struct SearchVectorSvc(pub Arc); - impl< - T: VeriSimOctad, - > tonic::server::UnaryService - for SearchVectorSvc { + impl tonic::server::UnaryService + for SearchVectorSvc + { type Response = super::SearchResponse; - type Future = BoxFuture< - tonic::Response, - tonic::Status, - >; + type Future = BoxFuture, tonic::Status>; fn call( &mut self, request: tonic::Request, @@ -1403,25 +1234,19 @@ pub mod veri_sim_octad_server { }; Box::pin(fut) } - _ => { - Box::pin(async move { - let mut response = http::Response::new( - tonic::body::Body::default(), - ); - let headers = response.headers_mut(); - headers - .insert( - tonic::Status::GRPC_STATUS, - (tonic::Code::Unimplemented as i32).into(), - ); - headers - .insert( - http::header::CONTENT_TYPE, - tonic::metadata::GRPC_CONTENT_TYPE, - ); - Ok(response) - }) - } + _ => Box::pin(async move { + let mut response = http::Response::new(tonic::body::Body::default()); + let headers = response.headers_mut(); + headers.insert( + tonic::Status::GRPC_STATUS, + (tonic::Code::Unimplemented as i32).into(), + ); + headers.insert( + http::header::CONTENT_TYPE, + tonic::metadata::GRPC_CONTENT_TYPE, + ); + Ok(response) + }), } } } diff --git a/rust-core/verisim-api/src/rbac.rs b/rust-core/verisim-api/src/rbac.rs index 208e0f3..f7b203a 100644 --- a/rust-core/verisim-api/src/rbac.rs +++ b/rust-core/verisim-api/src/rbac.rs @@ -15,7 +15,7 @@ //! # Integration with auth middleware //! //! After the [`auth_middleware`](crate::auth::auth_middleware) extracts a -//! [`ClientIdentity`](crate::auth::ClientIdentity), call +//! [`ClientIdentity`], call //! [`check_authorization`] to verify that the client's role permits the //! requested operation on the target resource. @@ -222,9 +222,8 @@ impl RbacPolicy { self.entity_acls .get(entity_id) .map(|acls| { - acls.iter().any(|(cid, perms)| { - cid == client_id && perms.contains(&permission) - }) + acls.iter() + .any(|(cid, perms)| cid == client_id && perms.contains(&permission)) }) .unwrap_or(false) } @@ -545,7 +544,10 @@ pub fn check_access( permission = %permission, "Access ALLOWED via entity ACL" ); - record(AccessDecision::Allowed, Some("entity ACL grant".to_string())); + record( + AccessDecision::Allowed, + Some("entity ACL grant".to_string()), + ); return Ok(()); } } @@ -581,7 +583,10 @@ pub fn check_access( permission = %permission, "Access ALLOWED via modality permission" ); - record(AccessDecision::Allowed, Some(format!("modality '{}' grant", modality))); + record( + AccessDecision::Allowed, + Some(format!("modality '{}' grant", modality)), + ); return Ok(()); } // If the role has a modality_permissions entry for this modality @@ -616,7 +621,10 @@ pub fn check_access( permission = %permission, "Access ALLOWED via global permission" ); - record(AccessDecision::Allowed, Some("global role grant".to_string())); + record( + AccessDecision::Allowed, + Some("global role grant".to_string()), + ); return Ok(()); } @@ -690,18 +698,8 @@ mod tests { // Execute assert!(check_access(&admin, "/query/plan", &Method::POST, &rbac).is_ok()); // Admin - assert!(check_access( - &admin, - "/normalizer/trigger/entity-1", - &Method::POST, - &rbac - ).is_ok()); - assert!(check_access( - &admin, - "/planner/config", - &Method::PUT, - &rbac - ).is_ok()); + assert!(check_access(&admin, "/normalizer/trigger/entity-1", &Method::POST, &rbac).is_ok()); + assert!(check_access(&admin, "/planner/config", &Method::PUT, &rbac).is_ok()); } // ------------------------------------------------------------------ @@ -836,21 +834,11 @@ mod tests { let reader = identity("special-reader", ClientRole::Reader); // Normally a reader cannot write. - let result = check_access( - &reader, - "/octads/other-octad", - &Method::PUT, - &rbac, - ); + let result = check_access(&reader, "/octads/other-octad", &Method::PUT, &rbac); assert!(result.is_err()); // But the entity ACL grants write on "secret-octad". - assert!(check_access( - &reader, - "/octads/secret-octad", - &Method::PUT, - &rbac, - ).is_ok()); + assert!(check_access(&reader, "/octads/secret-octad", &Method::PUT, &rbac,).is_ok()); } // ------------------------------------------------------------------ @@ -868,10 +856,16 @@ mod tests { let _ = check_access(&reader, "/octads", &Method::POST, &rbac); let entries = rbac.audit_log.entries(); - assert!(entries.len() >= 2, "Expected at least 2 audit entries, got {}", entries.len()); + assert!( + entries.len() >= 2, + "Expected at least 2 audit entries, got {}", + entries.len() + ); // First entry: admin allowed read. - let allowed_entry = entries.iter().find(|e| e.decision == AccessDecision::Allowed); + let allowed_entry = entries + .iter() + .find(|e| e.decision == AccessDecision::Allowed); assert!(allowed_entry.is_some(), "Expected an ALLOWED entry"); let allowed = allowed_entry.unwrap(); assert_eq!(allowed.client_id, "audit-admin"); @@ -879,7 +873,9 @@ mod tests { assert_eq!(allowed.required_permission, Permission::Read); // Second entry: reader denied write. - let denied_entry = entries.iter().find(|e| e.decision == AccessDecision::Denied); + let denied_entry = entries + .iter() + .find(|e| e.decision == AccessDecision::Denied); assert!(denied_entry.is_some(), "Expected a DENIED entry"); let denied = denied_entry.unwrap(); assert_eq!(denied.client_id, "audit-reader"); @@ -923,21 +919,54 @@ mod tests { #[test] fn test_required_permission_derivation() { // Read - assert_eq!(required_permission(&Method::GET, "/octads"), Permission::Read); - assert_eq!(required_permission(&Method::HEAD, "/octads"), Permission::Read); - assert_eq!(required_permission(&Method::OPTIONS, "/anything"), Permission::Read); + assert_eq!( + required_permission(&Method::GET, "/octads"), + Permission::Read + ); + assert_eq!( + required_permission(&Method::HEAD, "/octads"), + Permission::Read + ); + assert_eq!( + required_permission(&Method::OPTIONS, "/anything"), + Permission::Read + ); // Write - assert_eq!(required_permission(&Method::POST, "/octads"), Permission::Write); - assert_eq!(required_permission(&Method::PUT, "/octads/abc"), Permission::Write); - assert_eq!(required_permission(&Method::DELETE, "/octads/abc"), Permission::Write); + assert_eq!( + required_permission(&Method::POST, "/octads"), + Permission::Write + ); + assert_eq!( + required_permission(&Method::PUT, "/octads/abc"), + Permission::Write + ); + assert_eq!( + required_permission(&Method::DELETE, "/octads/abc"), + Permission::Write + ); // Execute - assert_eq!(required_permission(&Method::POST, "/query/plan"), Permission::Execute); - assert_eq!(required_permission(&Method::POST, "/query/explain"), Permission::Execute); - assert_eq!(required_permission(&Method::POST, "/search/vector"), Permission::Execute); - assert_eq!(required_permission(&Method::POST, "/search/text?q=foo"), Permission::Execute); - assert_eq!(required_permission(&Method::POST, "/queries/similar"), Permission::Execute); + assert_eq!( + required_permission(&Method::POST, "/query/plan"), + Permission::Execute + ); + assert_eq!( + required_permission(&Method::POST, "/query/explain"), + Permission::Execute + ); + assert_eq!( + required_permission(&Method::POST, "/search/vector"), + Permission::Execute + ); + assert_eq!( + required_permission(&Method::POST, "/search/text?q=foo"), + Permission::Execute + ); + assert_eq!( + required_permission(&Method::POST, "/queries/similar"), + Permission::Execute + ); // Admin assert_eq!( @@ -959,7 +988,10 @@ mod tests { assert_eq!(entity_from_path("/octads/my-entity"), Some("my-entity")); assert_eq!(entity_from_path("/octads/abc/sub"), Some("abc")); assert_eq!(entity_from_path("/drift/entity/drift-id"), Some("drift-id")); - assert_eq!(entity_from_path("/normalizer/trigger/norm-id"), Some("norm-id")); + assert_eq!( + entity_from_path("/normalizer/trigger/norm-id"), + Some("norm-id") + ); assert_eq!(entity_from_path("/octads"), None); assert_eq!(entity_from_path("/search/text"), None); @@ -1057,12 +1089,7 @@ mod tests { // Write to entity-alpha by a DIFFERENT client → denied. let other_reader = identity("reader-y", ClientRole::Reader); - let result = check_access( - &other_reader, - "/octads/entity-alpha", - &Method::PUT, - &rbac, - ); + let result = check_access(&other_reader, "/octads/entity-alpha", &Method::PUT, &rbac); assert!(result.is_err()); } diff --git a/rust-core/verisim-api/src/transaction.rs b/rust-core/verisim-api/src/transaction.rs index af03a8e..bc7a5c5 100644 --- a/rust-core/verisim-api/src/transaction.rs +++ b/rust-core/verisim-api/src/transaction.rs @@ -43,11 +43,17 @@ impl TransactionId { } /// Create a TransactionId from an existing string (for API lookups). - pub fn from_str(s: &str) -> Self { + pub fn from_string(s: &str) -> Self { Self(s.to_string()) } } +impl Default for TransactionId { + fn default() -> Self { + Self::new() + } +} + impl std::fmt::Display for TransactionId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "txn_{}", &self.0[..12]) @@ -275,10 +281,7 @@ impl TransactionManager { } /// Rollback a transaction — discard all buffered operations. - pub async fn rollback( - &self, - txn_id: &TransactionId, - ) -> Result { + pub async fn rollback(&self, txn_id: &TransactionId) -> Result { let mut txns = self.transactions.write().await; let txn = txns .get_mut(txn_id) diff --git a/rust-core/verisim-api/src/vql.rs b/rust-core/verisim-api/src/vql.rs index 0ff6b89..5bb69e5 100644 --- a/rust-core/verisim-api/src/vql.rs +++ b/rust-core/verisim-api/src/vql.rs @@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use tracing::{info, instrument}; -use verisim_octad::{OctadId, OctadInput, OctadDocumentInput, OctadStore}; +use verisim_octad::{OctadDocumentInput, OctadId, OctadInput, OctadStore}; use crate::{ApiError, AppState, OctadResponse}; @@ -84,7 +84,9 @@ pub async fn vql_execute_handler( // Parse and route the query. let tokens = tokenize(query); if tokens.is_empty() { - return Err(ApiError::BadRequest("Empty query after parsing".to_string())); + return Err(ApiError::BadRequest( + "Empty query after parsing".to_string(), + )); } // Pre-flight: validate with TypeLL VQL-UT checker if available. @@ -136,8 +138,8 @@ pub async fn vql_execute_handler( /// regardless. The safety metadata is attached to the response for downstream /// consumers (PanLL, ECHIDNA) to act on. async fn validate_with_typell(query: &str) -> Option<(u8, String, Vec)> { - let typell_url = std::env::var("TYPELL_URL") - .unwrap_or_else(|_| "http://localhost:7800".to_string()); + let typell_url = + std::env::var("TYPELL_URL").unwrap_or_else(|_| "http://localhost:7800".to_string()); let check_body = serde_json::json!({ "expression": query, @@ -336,16 +338,14 @@ async fn execute_select( } /// Find `WHERE id = ''` in token list. -fn find_where_id<'a>(tokens: &'a [String]) -> Option<&'a str> { +fn find_where_id(tokens: &[String]) -> Option<&str> { for (i, token) in tokens.iter().enumerate() { - if token.to_uppercase() == "WHERE" { - // Expect: WHERE id = '' - if tokens.get(i + 1).map(|t| t.to_lowercase()) == Some("id".to_string()) { - if tokens.get(i + 2).map(|t| t.as_str()) == Some("=") { - if let Some(val) = tokens.get(i + 3) { - return Some(unquote(val)); - } - } + if token.to_uppercase() == "WHERE" + && tokens.get(i + 1).map(|t| t.to_lowercase()) == Some("id".to_string()) + && tokens.get(i + 2).map(|t| t.as_str()) == Some("=") + { + if let Some(val) = tokens.get(i + 3) { + return Some(unquote(val)); } } } @@ -527,10 +527,7 @@ fn parse_vector(s: &str) -> Result, ApiError> { /// /// Also accepts simplified form: /// `INSERT '' '<body>'` -async fn execute_insert( - state: &AppState, - raw: &str, -) -> Result<VqlExecuteResponse, ApiError> { +async fn execute_insert(state: &AppState, raw: &str) -> Result<VqlExecuteResponse, ApiError> { let upper = raw.to_uppercase(); let (title, body) = if upper.starts_with("INSERT INTO") { @@ -541,7 +538,8 @@ async fn execute_insert( let tokens = tokenize(raw); if tokens.len() < 3 { return Err(ApiError::BadRequest( - "INSERT requires: INSERT INTO octads (title, body) VALUES ('<title>', '<body>')".to_string(), + "INSERT requires: INSERT INTO octads (title, body) VALUES ('<title>', '<body>')" + .to_string(), )); } ( @@ -550,12 +548,14 @@ async fn execute_insert( ) }; - let mut input = OctadInput::default(); - input.document = Some(OctadDocumentInput { - title: title.clone(), - body, - fields: std::collections::HashMap::new(), - }); + let input = OctadInput { + document: Some(OctadDocumentInput { + title: title.clone(), + body, + fields: std::collections::HashMap::new(), + }), + ..OctadInput::default() + }; let octad = state .octad_store @@ -586,9 +586,7 @@ fn parse_insert_values(raw: &str) -> Result<(String, String), ApiError> { .ok_or_else(|| ApiError::BadRequest("INSERT INTO requires a VALUES clause".to_string()))?; let values_part = &raw[values_idx + 6..].trim(); - let values_part = values_part - .trim_start_matches('(') - .trim_end_matches(')'); + let values_part = values_part.trim_start_matches('(').trim_end_matches(')'); // Split on comma, respecting quotes. let value_tokens = tokenize(&values_part.replace(',', " ")); @@ -618,9 +616,7 @@ async fn execute_delete( tokens: &[String], ) -> Result<VqlExecuteResponse, ApiError> { let id = find_where_id(tokens).ok_or_else(|| { - ApiError::BadRequest( - "DELETE requires: DELETE FROM octads WHERE id = '<id>'".to_string(), - ) + ApiError::BadRequest("DELETE requires: DELETE FROM octads WHERE id = '<id>'".to_string()) })?; let octad_id = OctadId::new(id); @@ -659,10 +655,7 @@ async fn execute_delete( /// - `SHOW DRIFT` — drift metrics /// - `SHOW NORMALIZER` — normalizer status /// - `SHOW OCTADS [LIMIT n]` — list octads (alias for SELECT) -async fn execute_show( - state: &AppState, - tokens: &[String], -) -> Result<VqlExecuteResponse, ApiError> { +async fn execute_show(state: &AppState, tokens: &[String]) -> Result<VqlExecuteResponse, ApiError> { if tokens.len() < 2 { return Err(ApiError::BadRequest( "SHOW requires: SHOW STATUS | SHOW DRIFT | SHOW NORMALIZER | SHOW OCTADS".to_string(), @@ -827,7 +820,9 @@ async fn execute_explain( raw: &str, ) -> Result<VqlExecuteResponse, ApiError> { if tokens.len() < 2 { - return Err(ApiError::BadRequest("EXPLAIN requires a query to explain".to_string())); + return Err(ApiError::BadRequest( + "EXPLAIN requires a query to explain".to_string(), + )); } let inner_query = &raw[raw.to_uppercase().find("EXPLAIN").unwrap() + 7..].trim(); @@ -863,7 +858,10 @@ async fn execute_explain( } } "SEARCH" => { - let search_type = inner_tokens.get(1).map(|t| t.to_uppercase()).unwrap_or_default(); + let search_type = inner_tokens + .get(1) + .map(|t| t.to_uppercase()) + .unwrap_or_default(); match search_type.as_str() { "TEXT" => json!({ "operation": "Full-Text Search", @@ -934,7 +932,10 @@ mod tests { #[test] fn test_tokenize_quoted() { let tokens = tokenize("SEARCH TEXT 'hello world' LIMIT 10"); - assert_eq!(tokens, vec!["SEARCH", "TEXT", "'hello world'", "LIMIT", "10"]); + assert_eq!( + tokens, + vec!["SEARCH", "TEXT", "'hello world'", "LIMIT", "10"] + ); } #[test] @@ -968,10 +969,19 @@ mod tests { #[test] fn test_find_where_id() { - let tokens: Vec<String> = vec!["SELECT", "*", "FROM", "octads", "WHERE", "id", "=", "'abc-123'"] - .into_iter() - .map(String::from) - .collect(); + let tokens: Vec<String> = vec![ + "SELECT", + "*", + "FROM", + "octads", + "WHERE", + "id", + "=", + "'abc-123'", + ] + .into_iter() + .map(String::from) + .collect(); assert_eq!(find_where_id(&tokens), Some("abc-123")); } diff --git a/rust-core/verisim-document/src/lib.rs b/rust-core/verisim-document/src/lib.rs index 0fdd69c..b4fe925 100644 --- a/rust-core/verisim-document/src/lib.rs +++ b/rust-core/verisim-document/src/lib.rs @@ -143,7 +143,12 @@ impl DocumentSchema { let body = schema_builder.add_text_field("body", TEXT | STORED); let schema = schema_builder.build(); - Self { id, title, body, schema } + Self { + id, + title, + body, + schema, + } } } @@ -197,7 +202,8 @@ impl TantivyDocumentStore { let mut documents = HashMap::new(); let searcher = reader.searcher(); for segment_reader in searcher.segment_readers() { - let store_reader = segment_reader.get_store_reader(1) + let store_reader = segment_reader + .get_store_reader(1) .map_err(|e| DocumentError::IndexError(format!("store reader: {e}")))?; for doc_id in 0..segment_reader.max_doc() { if segment_reader.is_deleted(doc_id) { @@ -216,10 +222,8 @@ impl TantivyDocumentStore { let body_val = extract(schema.body); if !id_val.is_empty() { - documents.insert( - id_val.clone(), - Document::new(id_val, title_val, body_val), - ); + documents + .insert(id_val.clone(), Document::new(id_val, title_val, body_val)); } } } @@ -257,27 +261,25 @@ impl DocumentStore for TantivyDocumentStore { } // Store original document - self.documents.write().await.insert(doc.id.clone(), doc.clone()); + self.documents + .write() + .await + .insert(doc.id.clone(), doc.clone()); Ok(()) } async fn search(&self, query: &str, limit: usize) -> Result<Vec<SearchResult>, DocumentError> { let searcher = self.reader.searcher(); - let query_parser = QueryParser::for_index( - &self.index, - vec![self.schema.title, self.schema.body], - ); + let query_parser = + QueryParser::for_index(&self.index, vec![self.schema.title, self.schema.body]); let parsed_query = query_parser.parse_query(query)?; let top_docs = searcher.search(&parsed_query, &TopDocs::with_limit(limit))?; // Create snippet generator for body field - let snippet_generator = SnippetGenerator::create( - &searcher, - &parsed_query, - self.schema.body, - )?; + let snippet_generator = + SnippetGenerator::create(&searcher, &parsed_query, self.schema.body)?; let mut results = Vec::new(); for (score, doc_address) in top_docs { @@ -341,7 +343,11 @@ mod tests { async fn test_index_and_search() { let store = TantivyDocumentStore::in_memory().unwrap(); - let doc1 = Document::new("d1", "Rust Programming", "Rust is a systems programming language"); + let doc1 = Document::new( + "d1", + "Rust Programming", + "Rust is a systems programming language", + ); let doc2 = Document::new("d2", "Python Tutorial", "Python is great for beginners"); store.index(&doc1).await.unwrap(); @@ -369,6 +375,9 @@ mod tests { assert_eq!(results.len(), 1); assert!(results[0].snippet.is_some(), "Snippet should not be None"); let snippet = results[0].snippet.as_ref().unwrap(); - assert!(snippet.contains("safety"), "Snippet should contain the search term"); + assert!( + snippet.contains("safety"), + "Snippet should contain the search term" + ); } } diff --git a/rust-core/verisim-document/tests/property_tests.rs b/rust-core/verisim-document/tests/property_tests.rs index 8876a34..2b44e0e 100644 --- a/rust-core/verisim-document/tests/property_tests.rs +++ b/rust-core/verisim-document/tests/property_tests.rs @@ -220,5 +220,8 @@ async fn test_realistic_document_lifecycle() { store.commit().await.unwrap(); // Verify deletion by ID - assert!(store.get("paper-123").await.unwrap().is_none(), "Document should be deleted"); + assert!( + store.get("paper-123").await.unwrap().is_none(), + "Document should be deleted" + ); } diff --git a/rust-core/verisim-drift/src/calculator.rs b/rust-core/verisim-drift/src/calculator.rs index 9ba728d..d7e1ce7 100644 --- a/rust-core/verisim-drift/src/calculator.rs +++ b/rust-core/verisim-drift/src/calculator.rs @@ -22,7 +22,9 @@ impl Default for DriftCalculator { impl DriftCalculator { /// Create a new drift calculator with custom threshold pub fn new(similarity_threshold: f64) -> Self { - Self { similarity_threshold } + Self { + similarity_threshold, + } } /// Calculate semantic-vector drift score @@ -87,11 +89,17 @@ impl DriftCalculator { let mut matched = 0; for entity in document_entities { // Check if entity appears in graph relationships - if graph_targets.iter().any(|t| t.contains(entity) || entity.contains(*t)) { + if graph_targets + .iter() + .any(|t| t.contains(entity) || entity.contains(*t)) + { matched += 1; } // Also check if entity is mentioned in document - if !document_text.to_lowercase().contains(&entity.to_lowercase()) { + if !document_text + .to_lowercase() + .contains(&entity.to_lowercase()) + { // Entity in list but not in document text - potential issue } } @@ -154,10 +162,7 @@ impl DriftCalculator { // Check for suspiciously large time gaps (might indicate data loss) if version_timestamps.len() >= 2 { - let mut deltas: Vec<i64> = version_timestamps - .windows(2) - .map(|w| w[1] - w[0]) - .collect(); + let mut deltas: Vec<i64> = version_timestamps.windows(2).map(|w| w[1] - w[0]).collect(); deltas.sort(); if deltas.len() >= 3 { let median = deltas[deltas.len() / 2]; @@ -360,6 +365,7 @@ impl DriftCalculator { } /// Calculate overall quality drift score including all 8 modality drift types + #[allow(clippy::too_many_arguments)] pub fn quality_drift_octad( &self, semantic_vector: f64, @@ -410,6 +416,7 @@ impl DriftCalculator { } /// Determine drift type from all octad scores (including provenance + spatial) + #[allow(clippy::too_many_arguments)] pub fn primary_drift_type_octad( &self, semantic_vector: f64, @@ -483,7 +490,8 @@ impl TensorStats { let sum: f64 = valid.iter().sum(); let mean = sum / valid.len() as f64; - let variance: f64 = valid.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / valid.len() as f64; + let variance: f64 = + valid.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / valid.len() as f64; let std_dev = variance.sqrt(); let min = valid.iter().copied().fold(f64::INFINITY, f64::min); @@ -506,7 +514,11 @@ fn cosine_similarity_f32(a: &[f32], b: &[f32]) -> f64 { return 0.0; } - let dot: f64 = a.iter().zip(b.iter()).map(|(x, y)| (*x as f64) * (*y as f64)).sum(); + let dot: f64 = a + .iter() + .zip(b.iter()) + .map(|(x, y)| (*x as f64) * (*y as f64)) + .sum(); let norm_a: f64 = a.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt(); let norm_b: f64 = b.iter().map(|x| (*x as f64).powi(2)).sum::<f64>().sqrt(); @@ -558,7 +570,11 @@ mod tests { let calc = DriftCalculator::default(); let document_text = "Alice knows Bob and Charlie"; - let document_entities = vec!["Alice".to_string(), "Bob".to_string(), "Charlie".to_string()]; + let document_entities = vec![ + "Alice".to_string(), + "Bob".to_string(), + "Charlie".to_string(), + ]; let graph_relationships = vec![ ("knows".to_string(), "Bob".to_string()), ("knows".to_string(), "Charlie".to_string()), @@ -578,12 +594,20 @@ mod tests { let timestamps = vec![1000, 2000, 3000, 4000]; let hashes = vec![1, 2, 3, 4]; let drift = calc.temporal_consistency_drift(×tamps, &hashes); - assert!(drift < 0.1, "Expected low drift for normal sequence, got {}", drift); + assert!( + drift < 0.1, + "Expected low drift for normal sequence, got {}", + drift + ); // Out of order timestamps let timestamps = vec![1000, 3000, 2000, 4000]; let drift = calc.temporal_consistency_drift(×tamps, &hashes); - assert!(drift > 0.0, "Expected drift for out-of-order timestamps, got {}", drift); + assert!( + drift > 0.0, + "Expected drift for out-of-order timestamps, got {}", + drift + ); } #[test] @@ -603,7 +627,11 @@ mod tests { }); let drift = calc.tensor_drift(&data, &expected_shape, &actual_shape, expected_stats); - assert!(drift < 0.3, "Expected low drift for matching tensor, got {}", drift); + assert!( + drift < 0.3, + "Expected low drift for matching tensor, got {}", + drift + ); // Test with NaN values let data_with_nan = vec![1.0, f64::NAN, 3.0, 4.0]; @@ -633,7 +661,10 @@ mod tests { // Some metrics bad let drift = calc.quality_drift(0.8, 0.2, 0.1, 0.1, 0.1); - assert!(drift > 0.1, "Expected higher drift with semantic-vector issues"); + assert!( + drift > 0.1, + "Expected higher drift with semantic-vector issues" + ); } #[test] diff --git a/rust-core/verisim-drift/src/lib.rs b/rust-core/verisim-drift/src/lib.rs index a7aaa80..7f29071 100644 --- a/rust-core/verisim-drift/src/lib.rs +++ b/rust-core/verisim-drift/src/lib.rs @@ -384,7 +384,12 @@ impl DriftDetector { } /// Record a drift measurement - pub async fn record(&self, drift_type: DriftType, score: f64, entities: Vec<String>) -> Result<Option<DriftEvent>, DriftError> { + pub async fn record( + &self, + drift_type: DriftType, + score: f64, + entities: Vec<String>, + ) -> Result<Option<DriftEvent>, DriftError> { // Update metrics { let mut metrics = self.metrics.write().map_err(|_| DriftError::LockPoisoned)?; @@ -522,7 +527,11 @@ mod tests { // Record high score (above threshold of 0.3 for semantic_vector) // Score 0.6 triggers Warning severity (> 0.5) let event = detector - .record(DriftType::SemanticVectorDrift, 0.6, vec!["entity1".to_string()]) + .record( + DriftType::SemanticVectorDrift, + 0.6, + vec!["entity1".to_string()], + ) .await .unwrap(); assert!(event.is_some()); diff --git a/rust-core/verisim-graph/src/lib.rs b/rust-core/verisim-graph/src/lib.rs index f4268c1..f85ec80 100644 --- a/rust-core/verisim-graph/src/lib.rs +++ b/rust-core/verisim-graph/src/lib.rs @@ -86,7 +86,10 @@ pub struct GraphEdge { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum GraphObject { Node(GraphNode), - Literal { value: String, datatype: Option<String> }, + Literal { + value: String, + datatype: Option<String>, + }, } /// Graph store trait for cross-modal consistency. @@ -111,7 +114,11 @@ pub trait GraphStore: Send + Sync { async fn delete(&self, edge: &GraphEdge) -> Result<(), GraphError>; /// Get all nodes connected to a given node within N hops - async fn neighborhood(&self, node: &GraphNode, hops: usize) -> Result<Vec<GraphNode>, GraphError>; + async fn neighborhood( + &self, + node: &GraphNode, + hops: usize, + ) -> Result<Vec<GraphNode>, GraphError>; } // ═══════════════════════════════════════════════════════════════════════════ @@ -131,7 +138,11 @@ impl TripleKey { GraphObject::Node(n) => n.iri.clone(), GraphObject::Literal { value, .. } => format!("literal::{}", value), }; - Self(edge.subject.iri.clone(), edge.predicate.iri.clone(), obj_key) + Self( + edge.subject.iri.clone(), + edge.predicate.iri.clone(), + obj_key, + ) } } @@ -206,14 +217,14 @@ impl GraphStore for SimpleGraphStore { } async fn outgoing(&self, node: &GraphNode) -> Result<Vec<GraphEdge>, GraphError> { - let subject_idx = self.subject_idx.read().map_err(|_| GraphError::LockPoisoned)?; + let subject_idx = self + .subject_idx + .read() + .map_err(|_| GraphError::LockPoisoned)?; let edges = self.edges.read().map_err(|_| GraphError::LockPoisoned)?; let result = match subject_idx.get(&node.iri) { - Some(keys) => keys - .iter() - .filter_map(|k| edges.get(k).cloned()) - .collect(), + Some(keys) => keys.iter().filter_map(|k| edges.get(k).cloned()).collect(), None => Vec::new(), }; @@ -221,14 +232,14 @@ impl GraphStore for SimpleGraphStore { } async fn incoming(&self, node: &GraphNode) -> Result<Vec<GraphEdge>, GraphError> { - let object_idx = self.object_idx.read().map_err(|_| GraphError::LockPoisoned)?; + let object_idx = self + .object_idx + .read() + .map_err(|_| GraphError::LockPoisoned)?; let edges = self.edges.read().map_err(|_| GraphError::LockPoisoned)?; let result = match object_idx.get(&node.iri) { - Some(keys) => keys - .iter() - .filter_map(|k| edges.get(k).cloned()) - .collect(), + Some(keys) => keys.iter().filter_map(|k| edges.get(k).cloned()).collect(), None => Vec::new(), }; @@ -275,7 +286,11 @@ impl GraphStore for SimpleGraphStore { Ok(()) } - async fn neighborhood(&self, node: &GraphNode, hops: usize) -> Result<Vec<GraphNode>, GraphError> { + async fn neighborhood( + &self, + node: &GraphNode, + hops: usize, + ) -> Result<Vec<GraphNode>, GraphError> { let mut visited = HashSet::new(); let mut frontier = vec![node.clone()]; visited.insert(node.iri.clone()); diff --git a/rust-core/verisim-graph/src/oxigraph_backend.rs b/rust-core/verisim-graph/src/oxigraph_backend.rs index fd8c4c3..0c23759 100644 --- a/rust-core/verisim-graph/src/oxigraph_backend.rs +++ b/rust-core/verisim-graph/src/oxigraph_backend.rs @@ -38,8 +38,7 @@ impl OxiGraphStore { /// Convert GraphEdge to Oxigraph Quad fn edge_to_quad(&self, edge: &GraphEdge) -> Result<Quad, GraphError> { let subject = Subject::NamedNode( - NamedNode::new(&edge.subject.iri) - .map_err(|e| GraphError::InvalidIri(e.to_string()))?, + NamedNode::new(&edge.subject.iri).map_err(|e| GraphError::InvalidIri(e.to_string()))?, ); let predicate = NamedNode::new(&edge.predicate.iri) .map_err(|e| GraphError::InvalidIri(e.to_string()))?; @@ -51,7 +50,12 @@ impl OxiGraphStore { Term::Literal(oxigraph::model::Literal::new_simple_literal(value)) } }; - Ok(Quad::new(subject, predicate, object, GraphName::DefaultGraph)) + Ok(Quad::new( + subject, + predicate, + object, + GraphName::DefaultGraph, + )) } } @@ -59,15 +63,21 @@ impl OxiGraphStore { impl GraphStore for OxiGraphStore { async fn insert(&self, edge: &GraphEdge) -> Result<(), GraphError> { let quad = self.edge_to_quad(edge)?; - self.store.insert(&quad).map_err(|e| GraphError::StoreError(e.to_string()))?; + self.store + .insert(&quad) + .map_err(|e| GraphError::StoreError(e.to_string()))?; Ok(()) } async fn outgoing(&self, node: &GraphNode) -> Result<Vec<GraphEdge>, GraphError> { - let subject = NamedNode::new(&node.iri).map_err(|e| GraphError::InvalidIri(e.to_string()))?; + let subject = + NamedNode::new(&node.iri).map_err(|e| GraphError::InvalidIri(e.to_string()))?; let mut edges = Vec::new(); - for quad in self.store.quads_for_pattern(Some(subject.as_ref().into()), None, None, None) { + for quad in self + .store + .quads_for_pattern(Some(subject.as_ref().into()), None, None, None) + { let quad = quad.map_err(|e| GraphError::StoreError(e.to_string()))?; let predicate = GraphNode::new(quad.predicate.as_str()); let object = match quad.object { @@ -88,10 +98,14 @@ impl GraphStore for OxiGraphStore { } async fn incoming(&self, node: &GraphNode) -> Result<Vec<GraphEdge>, GraphError> { - let object = NamedNode::new(&node.iri).map_err(|e| GraphError::InvalidIri(e.to_string()))?; + let object = + NamedNode::new(&node.iri).map_err(|e| GraphError::InvalidIri(e.to_string()))?; let mut edges = Vec::new(); - for quad in self.store.quads_for_pattern(None, None, Some(object.as_ref().into()), None) { + for quad in self + .store + .quads_for_pattern(None, None, Some(object.as_ref().into()), None) + { let quad = quad.map_err(|e| GraphError::StoreError(e.to_string()))?; let subject = match quad.subject { Subject::NamedNode(n) => GraphNode::new(n.as_str()), @@ -109,16 +123,24 @@ impl GraphStore for OxiGraphStore { async fn exists(&self, edge: &GraphEdge) -> Result<bool, GraphError> { let quad = self.edge_to_quad(edge)?; - self.store.contains(&quad).map_err(|e| GraphError::StoreError(e.to_string())) + self.store + .contains(&quad) + .map_err(|e| GraphError::StoreError(e.to_string())) } async fn delete(&self, edge: &GraphEdge) -> Result<(), GraphError> { let quad = self.edge_to_quad(edge)?; - self.store.remove(&quad).map_err(|e| GraphError::StoreError(e.to_string()))?; + self.store + .remove(&quad) + .map_err(|e| GraphError::StoreError(e.to_string()))?; Ok(()) } - async fn neighborhood(&self, node: &GraphNode, hops: usize) -> Result<Vec<GraphNode>, GraphError> { + async fn neighborhood( + &self, + node: &GraphNode, + hops: usize, + ) -> Result<Vec<GraphNode>, GraphError> { use std::collections::HashSet; let mut visited = HashSet::new(); diff --git a/rust-core/verisim-graph/src/redb_backend.rs b/rust-core/verisim-graph/src/redb_backend.rs index e629b27..905ee3d 100644 --- a/rust-core/verisim-graph/src/redb_backend.rs +++ b/rust-core/verisim-graph/src/redb_backend.rs @@ -154,8 +154,7 @@ impl RedbGraphStore { /// Serialise a GraphEdge to JSON bytes. fn serialise_edge(edge: &GraphEdge) -> Result<Vec<u8>, GraphError> { - serde_json::to_vec(edge) - .map_err(|e| GraphError::StoreError(format!("serialise edge: {e}"))) + serde_json::to_vec(edge).map_err(|e| GraphError::StoreError(format!("serialise edge: {e}"))) } /// Scan an index table for all triple keys matching a given IRI prefix, @@ -165,7 +164,9 @@ impl RedbGraphStore { index_table: TableDefinition<&[u8], &[u8]>, iri: &str, ) -> Result<Vec<GraphEdge>, GraphError> { - let txn = db.begin_read().map_err(|e| GraphError::StoreError(format!("read txn: {e}")))?; + let txn = db + .begin_read() + .map_err(|e| GraphError::StoreError(format!("read txn: {e}")))?; let idx = match txn.open_table(index_table) { Ok(t) => t, @@ -178,9 +179,9 @@ impl RedbGraphStore { }; let prefix = Self::iri_prefix(iri); - let iter = idx.range(prefix.as_slice()..).map_err(|e| { - GraphError::StoreError(format!("index scan: {e}")) - })?; + let iter = idx + .range(prefix.as_slice()..) + .map_err(|e| GraphError::StoreError(format!("index scan: {e}")))?; let mut edges = Vec::new(); for entry in iter { @@ -196,9 +197,10 @@ impl RedbGraphStore { let triple_key = &idx_key[prefix.len()..]; // Look up the edge in the triples table - if let Some(edge_bytes) = triples.get(triple_key).map_err(|e| { - GraphError::StoreError(format!("triple lookup: {e}")) - })? { + if let Some(edge_bytes) = triples + .get(triple_key) + .map_err(|e| GraphError::StoreError(format!("triple lookup: {e}")))? + { edges.push(Self::deserialise_edge(edge_bytes.value())?); } } @@ -217,47 +219,46 @@ impl GraphStore for RedbGraphStore { let tkey = Self::triple_key(&edge); let edge_bytes = Self::serialise_edge(&edge)?; - let txn = db.begin_write().map_err(|e| { - GraphError::StoreError(format!("write txn: {e}")) - })?; + let txn = db + .begin_write() + .map_err(|e| GraphError::StoreError(format!("write txn: {e}")))?; { // Insert the triple - let mut triples = txn.open_table(TRIPLES).map_err(|e| { - GraphError::StoreError(format!("open triples: {e}")) - })?; - triples.insert(tkey.as_slice(), edge_bytes.as_slice()).map_err(|e| { - GraphError::StoreError(format!("insert triple: {e}")) - })?; + let mut triples = txn + .open_table(TRIPLES) + .map_err(|e| GraphError::StoreError(format!("open triples: {e}")))?; + triples + .insert(tkey.as_slice(), edge_bytes.as_slice()) + .map_err(|e| GraphError::StoreError(format!("insert triple: {e}")))?; } { // Update subject index - let mut subject_idx = txn.open_table(SUBJECT_IDX).map_err(|e| { - GraphError::StoreError(format!("open subject_idx: {e}")) - })?; + let mut subject_idx = txn + .open_table(SUBJECT_IDX) + .map_err(|e| GraphError::StoreError(format!("open subject_idx: {e}")))?; let skey = Self::subject_index_key(&edge.subject.iri, &tkey); - subject_idx.insert(skey.as_slice(), &[] as &[u8]).map_err(|e| { - GraphError::StoreError(format!("insert subject_idx: {e}")) - })?; + subject_idx + .insert(skey.as_slice(), &[] as &[u8]) + .map_err(|e| GraphError::StoreError(format!("insert subject_idx: {e}")))?; } { // Update object index (node objects only) if let GraphObject::Node(n) = &edge.object { - let mut object_idx = txn.open_table(OBJECT_IDX).map_err(|e| { - GraphError::StoreError(format!("open object_idx: {e}")) - })?; + let mut object_idx = txn + .open_table(OBJECT_IDX) + .map_err(|e| GraphError::StoreError(format!("open object_idx: {e}")))?; let okey = Self::object_index_key(&n.iri, &tkey); - object_idx.insert(okey.as_slice(), &[] as &[u8]).map_err(|e| { - GraphError::StoreError(format!("insert object_idx: {e}")) - })?; + object_idx + .insert(okey.as_slice(), &[] as &[u8]) + .map_err(|e| GraphError::StoreError(format!("insert object_idx: {e}")))?; } } - txn.commit().map_err(|e| { - GraphError::StoreError(format!("commit: {e}")) - })?; + txn.commit() + .map_err(|e| GraphError::StoreError(format!("commit: {e}")))?; Ok(()) }) @@ -269,22 +270,18 @@ impl GraphStore for RedbGraphStore { let db = Arc::clone(&self.db); let iri = node.iri.clone(); - tokio::task::spawn_blocking(move || { - Self::scan_index_for_edges(&db, SUBJECT_IDX, &iri) - }) - .await - .map_err(|e| GraphError::StoreError(format!("task join: {e}")))? + tokio::task::spawn_blocking(move || Self::scan_index_for_edges(&db, SUBJECT_IDX, &iri)) + .await + .map_err(|e| GraphError::StoreError(format!("task join: {e}")))? } async fn incoming(&self, node: &GraphNode) -> Result<Vec<GraphEdge>, GraphError> { let db = Arc::clone(&self.db); let iri = node.iri.clone(); - tokio::task::spawn_blocking(move || { - Self::scan_index_for_edges(&db, OBJECT_IDX, &iri) - }) - .await - .map_err(|e| GraphError::StoreError(format!("task join: {e}")))? + tokio::task::spawn_blocking(move || Self::scan_index_for_edges(&db, OBJECT_IDX, &iri)) + .await + .map_err(|e| GraphError::StoreError(format!("task join: {e}")))? } async fn exists(&self, edge: &GraphEdge) -> Result<bool, GraphError> { @@ -292,9 +289,9 @@ impl GraphStore for RedbGraphStore { let tkey = Self::triple_key(edge); tokio::task::spawn_blocking(move || -> Result<bool, GraphError> { - let txn = db.begin_read().map_err(|e| { - GraphError::StoreError(format!("read txn: {e}")) - })?; + let txn = db + .begin_read() + .map_err(|e| GraphError::StoreError(format!("read txn: {e}")))?; let table = match txn.open_table(TRIPLES) { Ok(t) => t, @@ -318,47 +315,46 @@ impl GraphStore for RedbGraphStore { tokio::task::spawn_blocking(move || -> Result<(), GraphError> { let tkey = Self::triple_key(&edge); - let txn = db.begin_write().map_err(|e| { - GraphError::StoreError(format!("write txn: {e}")) - })?; + let txn = db + .begin_write() + .map_err(|e| GraphError::StoreError(format!("write txn: {e}")))?; { // Remove from triples table - let mut triples = txn.open_table(TRIPLES).map_err(|e| { - GraphError::StoreError(format!("open triples: {e}")) - })?; - triples.remove(tkey.as_slice()).map_err(|e| { - GraphError::StoreError(format!("remove triple: {e}")) - })?; + let mut triples = txn + .open_table(TRIPLES) + .map_err(|e| GraphError::StoreError(format!("open triples: {e}")))?; + triples + .remove(tkey.as_slice()) + .map_err(|e| GraphError::StoreError(format!("remove triple: {e}")))?; } { // Remove from subject index - let mut subject_idx = txn.open_table(SUBJECT_IDX).map_err(|e| { - GraphError::StoreError(format!("open subject_idx: {e}")) - })?; + let mut subject_idx = txn + .open_table(SUBJECT_IDX) + .map_err(|e| GraphError::StoreError(format!("open subject_idx: {e}")))?; let skey = Self::subject_index_key(&edge.subject.iri, &tkey); - subject_idx.remove(skey.as_slice()).map_err(|e| { - GraphError::StoreError(format!("remove subject_idx: {e}")) - })?; + subject_idx + .remove(skey.as_slice()) + .map_err(|e| GraphError::StoreError(format!("remove subject_idx: {e}")))?; } { // Remove from object index (node objects only) if let GraphObject::Node(n) = &edge.object { - let mut object_idx = txn.open_table(OBJECT_IDX).map_err(|e| { - GraphError::StoreError(format!("open object_idx: {e}")) - })?; + let mut object_idx = txn + .open_table(OBJECT_IDX) + .map_err(|e| GraphError::StoreError(format!("open object_idx: {e}")))?; let okey = Self::object_index_key(&n.iri, &tkey); - object_idx.remove(okey.as_slice()).map_err(|e| { - GraphError::StoreError(format!("remove object_idx: {e}")) - })?; + object_idx + .remove(okey.as_slice()) + .map_err(|e| GraphError::StoreError(format!("remove object_idx: {e}")))?; } } - txn.commit().map_err(|e| { - GraphError::StoreError(format!("commit: {e}")) - })?; + txn.commit() + .map_err(|e| GraphError::StoreError(format!("commit: {e}")))?; Ok(()) }) @@ -366,7 +362,11 @@ impl GraphStore for RedbGraphStore { .map_err(|e| GraphError::StoreError(format!("task join: {e}")))? } - async fn neighborhood(&self, node: &GraphNode, hops: usize) -> Result<Vec<GraphNode>, GraphError> { + async fn neighborhood( + &self, + node: &GraphNode, + hops: usize, + ) -> Result<Vec<GraphNode>, GraphError> { use std::collections::HashSet; let mut visited = HashSet::new(); diff --git a/rust-core/verisim-nif/src/lib.rs b/rust-core/verisim-nif/src/lib.rs index 0ef1198..ca4dcf6 100644 --- a/rust-core/verisim-nif/src/lib.rs +++ b/rust-core/verisim-nif/src/lib.rs @@ -28,7 +28,7 @@ //! ``` #![forbid(unsafe_code)] -use rustler::{Env, Error, NifResult, Term}; +use rustler::{Error, NifResult}; use serde_json::Value; use std::sync::OnceLock; use tokio::runtime::Runtime; @@ -201,16 +201,4 @@ fn trigger_normalise(octad_id: String) -> NifResult<String> { // NIF registration // --------------------------------------------------------------------------- -rustler::init!( - "Elixir.VeriSim.NifBridge", - [ - create_octad, - get_octad, - delete_octad, - search_text, - search_vector, - list_octads, - get_drift_score, - trigger_normalise, - ] -); +rustler::init!("Elixir.VeriSim.NifBridge"); diff --git a/rust-core/verisim-normalizer/src/conflict.rs b/rust-core/verisim-normalizer/src/conflict.rs index 9bf09c3..7ed661c 100644 --- a/rust-core/verisim-normalizer/src/conflict.rs +++ b/rust-core/verisim-normalizer/src/conflict.rs @@ -554,11 +554,7 @@ impl ConflictResolver { /// /// - [`ConflictError::NotFound`] if no active conflict with the given ID exists. /// - [`ConflictError::AlreadyResolved`] if the conflict is already Resolved or Dismissed. - pub async fn dismiss( - &self, - conflict_id: &str, - reason: &str, - ) -> Result<(), ConflictError> { + pub async fn dismiss(&self, conflict_id: &str, reason: &str) -> Result<(), ConflictError> { let mut active = self.active_conflicts.write().await; let conflict = active .iter_mut() @@ -1041,7 +1037,10 @@ mod tests { .await; resolver - .dismiss(&conflict.id, "False positive -- data was updated concurrently") + .dismiss( + &conflict.id, + "False positive -- data was updated concurrently", + ) .await .unwrap(); @@ -1190,10 +1189,7 @@ mod tests { // Override: Document vs Vector conflicts use ModalityPriority config.per_modality_policies.insert( (Modality::Document, Modality::Vector), - ConflictPolicy::ModalityPriority(vec![ - Modality::Document, - Modality::Vector, - ]), + ConflictPolicy::ModalityPriority(vec![Modality::Document, Modality::Vector]), ); let resolver = ConflictResolver::new(config); @@ -1260,10 +1256,7 @@ mod tests { async fn test_not_found_error() { let resolver = ConflictResolver::new(test_config()); - let err = resolver - .resolve("nonexistent-id", None) - .await - .unwrap_err(); + let err = resolver.resolve("nonexistent-id", None).await.unwrap_err(); assert!(matches!(err, ConflictError::NotFound(_))); assert!(err.to_string().contains("nonexistent-id")); } @@ -1451,10 +1444,13 @@ mod tests { let deserialized: ConflictConfig = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.default_policy, config.default_policy); - assert!((deserialized.auto_resolve_threshold - config.auto_resolve_threshold).abs() - < f64::EPSILON); - assert!((deserialized.require_manual_above - config.require_manual_above).abs() - < f64::EPSILON); + assert!( + (deserialized.auto_resolve_threshold - config.auto_resolve_threshold).abs() + < f64::EPSILON + ); + assert!( + (deserialized.require_manual_above - config.require_manual_above).abs() < f64::EPSILON + ); assert_eq!(deserialized.max_history_entries, config.max_history_entries); assert!(deserialized.per_modality_policies.is_empty()); } @@ -1501,7 +1497,10 @@ mod tests { ) .await; - resolver.dismiss(&conflict.id, "First dismissal").await.unwrap(); + resolver + .dismiss(&conflict.id, "First dismissal") + .await + .unwrap(); // Second dismissal should fail (not found -- moved to history) let err = resolver diff --git a/rust-core/verisim-normalizer/src/lib.rs b/rust-core/verisim-normalizer/src/lib.rs index 27eb27b..d19d881 100644 --- a/rust-core/verisim-normalizer/src/lib.rs +++ b/rust-core/verisim-normalizer/src/lib.rs @@ -16,7 +16,6 @@ //! auto-merge, custom), threshold-gated escalation, and full history tracking. #![allow(unused)] // Infrastructure code with planned future usage - #![forbid(unsafe_code)] pub mod conflict; pub mod regeneration; @@ -301,7 +300,8 @@ impl NormalizationStrategy for SemanticVectorStrategy { if !has_document && !has_semantic { return Err(NormalizerError::NormalizationFailed { entity_id: octad.id.to_string(), - message: "Cannot regenerate vector: no document or semantic source available".into(), + message: "Cannot regenerate vector: no document or semantic source available" + .into(), }); } @@ -609,6 +609,12 @@ pub struct QualityReconciliationStrategy { impl QualityReconciliationStrategy { /// Create with the default cascade of all other strategies pub fn new() -> Self { + Self::default() + } +} + +impl Default for QualityReconciliationStrategy { + fn default() -> Self { Self { inner: vec![ Arc::new(SemanticVectorStrategy), @@ -701,7 +707,7 @@ mod tests { use super::*; use verisim_document::Document; use verisim_drift::DriftThresholds; - use verisim_octad::{OctadStatus, ModalityStatus}; + use verisim_octad::{ModalityStatus, OctadStatus}; use verisim_vector::Embedding; fn create_test_octad() -> Octad { @@ -718,7 +724,11 @@ mod tests { embedding: Some(Embedding::new("test-1", vec![0.1, 0.2, 0.3])), tensor: None, semantic: None, - document: Some(Document::new("test-1", "Test Document", "Test content for normalization")), + document: Some(Document::new( + "test-1", + "Test Document", + "Test content for normalization", + )), version_count: 1, provenance_chain_length: 0, spatial_data: None, @@ -762,11 +772,7 @@ mod tests { let normalizer = create_default_normalizer(drift_detector).await; let octad = create_test_octad(); - let event = DriftEvent::new( - DriftType::SemanticVectorDrift, - 0.5, - "Test drift", - ); + let event = DriftEvent::new(DriftType::SemanticVectorDrift, 0.5, "Test drift"); let result = normalizer.handle_drift(&octad, &event).await.unwrap(); assert!(result.is_some()); @@ -780,11 +786,7 @@ mod tests { async fn test_semantic_vector_strategy_empty_octad_errors() { let strategy = SemanticVectorStrategy; let octad = create_empty_octad(); - let event = DriftEvent::new( - DriftType::SemanticVectorDrift, - 0.8, - "Critical drift", - ); + let event = DriftEvent::new(DriftType::SemanticVectorDrift, 0.8, "Critical drift"); let result = strategy.normalize(&octad, &event).await; assert!(result.is_err()); @@ -794,11 +796,7 @@ mod tests { async fn test_graph_document_strategy_empty_octad_errors() { let strategy = GraphDocumentStrategy; let octad = create_empty_octad(); - let event = DriftEvent::new( - DriftType::GraphDocumentDrift, - 0.8, - "Critical drift", - ); + let event = DriftEvent::new(DriftType::GraphDocumentDrift, 0.8, "Critical drift"); let result = strategy.normalize(&octad, &event).await; assert!(result.is_err()); @@ -809,11 +807,7 @@ mod tests { let strategy = GraphDocumentStrategy; let mut octad = create_test_octad(); octad.graph_node = None; // Only document present - let event = DriftEvent::new( - DriftType::GraphDocumentDrift, - 0.5, - "Graph missing", - ); + let event = DriftEvent::new(DriftType::GraphDocumentDrift, 0.5, "Graph missing"); let result = strategy.normalize(&octad, &event).await.unwrap(); assert!(result.success); diff --git a/rust-core/verisim-normalizer/src/regeneration.rs b/rust-core/verisim-normalizer/src/regeneration.rs index 9ede877..4f2dbc5 100644 --- a/rust-core/verisim-normalizer/src/regeneration.rs +++ b/rust-core/verisim-normalizer/src/regeneration.rs @@ -100,13 +100,10 @@ impl Modality { /// Returns `None` if the modality is not populated. pub fn summarize(self, octad: &Octad) -> Option<String> { match self { - Modality::Document => octad.document.as_ref().map(|d| { - format!( - "document(title='{}', body_len={})", - d.title, - d.body.len() - ) - }), + Modality::Document => octad + .document + .as_ref() + .map(|d| format!("document(title='{}', body_len={})", d.title, d.body.len())), Modality::Semantic => octad.semantic.as_ref().map(|s| { format!( "semantic(types={}, properties={})", @@ -114,15 +111,18 @@ impl Modality { s.properties.len() ) }), - Modality::Graph => octad.graph_node.as_ref().map(|g| { - format!("graph(iri='{}', local_name='{}')", g.iri, g.local_name) - }), - Modality::Vector => octad.embedding.as_ref().map(|e| { - format!("vector(dim={})", e.vector.len()) - }), - Modality::Tensor => octad.tensor.as_ref().map(|t| { - format!("tensor(shape={:?}, len={})", t.shape, t.data.len()) - }), + Modality::Graph => octad + .graph_node + .as_ref() + .map(|g| format!("graph(iri='{}', local_name='{}')", g.iri, g.local_name)), + Modality::Vector => octad + .embedding + .as_ref() + .map(|e| format!("vector(dim={})", e.vector.len())), + Modality::Tensor => octad + .tensor + .as_ref() + .map(|t| format!("tensor(shape={:?}, len={})", t.shape, t.data.len())), Modality::Temporal => { if octad.version_count > 0 { Some(format!("temporal(versions={})", octad.version_count)) @@ -132,7 +132,10 @@ impl Modality { } Modality::Provenance => { if octad.provenance_chain_length > 0 { - Some(format!("provenance(chain_length={})", octad.provenance_chain_length)) + Some(format!( + "provenance(chain_length={})", + octad.provenance_chain_length + )) } else { None } @@ -140,9 +143,7 @@ impl Modality { Modality::Spatial => octad.spatial_data.as_ref().map(|s| { format!( "spatial(lat={}, lon={}, type={})", - s.coordinates.latitude, - s.coordinates.longitude, - s.geometry_type + s.coordinates.latitude, s.coordinates.longitude, s.geometry_type ) }), } @@ -397,9 +398,9 @@ impl NormalizationQueue { modality: Modality, ) -> Option<PendingNormalization> { let mut pending = self.pending.write().await; - let idx = pending.iter().position(|p| { - p.entity_id == entity_id && p.drifted_modality == modality - }); + let idx = pending + .iter() + .position(|p| p.entity_id == entity_id && p.drifted_modality == modality); idx.map(|i| pending.remove(i)) } @@ -528,11 +529,7 @@ impl ModalityRegenerator for SummaryRegenerator { format!("{}(w={:.2}) [{}]", m, w, s) }) .collect(); - Ok(format!( - "Merged into {} from: {}", - target, - parts.join(", ") - )) + Ok(format!("Merged into {} from: {}", target, parts.join(", "))) } async fn measure_drift( @@ -930,7 +927,7 @@ mod tests { use chrono::Utc; use verisim_document::Document; use verisim_graph::GraphNode; - use verisim_octad::{OctadId, OctadStatus, ModalityStatus}; + use verisim_octad::{ModalityStatus, OctadId, OctadStatus}; use verisim_semantic::{Provenance, SemanticAnnotation}; use verisim_vector::Embedding; @@ -1018,7 +1015,11 @@ mod tests { let order = Modality::DEFAULT_AUTHORITY_ORDER; assert_eq!(order[0], Modality::Document, "Document should be rank 1"); assert_eq!(order[1], Modality::Semantic, "Semantic should be rank 2"); - assert_eq!(order[2], Modality::Provenance, "Provenance should be rank 3"); + assert_eq!( + order[2], + Modality::Provenance, + "Provenance should be rank 3" + ); assert_eq!(order[3], Modality::Graph, "Graph should be rank 4"); assert_eq!(order[4], Modality::Vector, "Vector should be rank 5"); assert_eq!(order[5], Modality::Tensor, "Tensor should be rank 6"); @@ -1108,9 +1109,7 @@ mod tests { let h = rich_octad(); // Vector drifted -- Document is highest authority and is present. - let result = engine - .regenerate(&h, Modality::Vector, 0.8) - .await; + let result = engine.regenerate(&h, Modality::Vector, 0.8).await; match result { RegenerationResult::Repaired { event } => { @@ -1135,9 +1134,7 @@ mod tests { let h = rich_octad(); // Document itself drifted -- next authority is Semantic. - let result = engine - .regenerate(&h, Modality::Document, 0.7) - .await; + let result = engine.regenerate(&h, Modality::Document, 0.7).await; match result { RegenerationResult::Repaired { event } => { @@ -1157,9 +1154,7 @@ mod tests { let engine = RegenerationEngine::with_defaults(); let h = empty_octad(); - let result = engine - .regenerate(&h, Modality::Vector, 0.9) - .await; + let result = engine.regenerate(&h, Modality::Vector, 0.9).await; match result { RegenerationResult::Failed { error } => { @@ -1179,9 +1174,7 @@ mod tests { let h = doc_only_octad(); // Graph drifted, only Document is available. - let result = engine - .regenerate(&h, Modality::Graph, 0.6) - .await; + let result = engine.regenerate(&h, Modality::Graph, 0.6).await; match result { RegenerationResult::Repaired { event } => { @@ -1196,15 +1189,15 @@ mod tests { #[tokio::test] async fn test_merge_combines_multiple_sources() { - let mut config = RegenerationConfig::default(); - config.default_strategy = RegenerationStrategy::Merge; + let config = RegenerationConfig { + default_strategy: RegenerationStrategy::Merge, + ..RegenerationConfig::default() + }; let engine = RegenerationEngine::new(config); let h = rich_octad(); - let result = engine - .regenerate(&h, Modality::Tensor, 0.5) - .await; + let result = engine.regenerate(&h, Modality::Tensor, 0.5).await; match result { RegenerationResult::Repaired { event } => { @@ -1220,15 +1213,15 @@ mod tests { #[tokio::test] async fn test_merge_fails_no_sources() { - let mut config = RegenerationConfig::default(); - config.default_strategy = RegenerationStrategy::Merge; + let config = RegenerationConfig { + default_strategy: RegenerationStrategy::Merge, + ..RegenerationConfig::default() + }; let engine = RegenerationEngine::new(config); let h = empty_octad(); - let result = engine - .regenerate(&h, Modality::Vector, 0.9) - .await; + let result = engine.regenerate(&h, Modality::Vector, 0.9).await; match result { RegenerationResult::Failed { error } => { @@ -1252,9 +1245,7 @@ mod tests { assert!(engine.queue().is_empty().await); - let result = engine - .regenerate(&h, Modality::Tensor, 0.7) - .await; + let result = engine.regenerate(&h, Modality::Tensor, 0.7).await; match result { RegenerationResult::PendingResolution { entity_id, reason } => { @@ -1273,8 +1264,10 @@ mod tests { #[tokio::test] async fn test_user_resolve_queue_drain() { - let mut config = RegenerationConfig::default(); - config.default_strategy = RegenerationStrategy::UserResolve; + let config = RegenerationConfig { + default_strategy: RegenerationStrategy::UserResolve, + ..RegenerationConfig::default() + }; let engine = RegenerationEngine::new(config); let h = rich_octad(); @@ -1291,8 +1284,10 @@ mod tests { #[tokio::test] async fn test_user_resolve_queue_selective_resolve() { - let mut config = RegenerationConfig::default(); - config.default_strategy = RegenerationStrategy::UserResolve; + let config = RegenerationConfig { + default_strategy: RegenerationStrategy::UserResolve, + ..RegenerationConfig::default() + }; let engine = RegenerationEngine::new(config); let h = rich_octad(); @@ -1301,10 +1296,7 @@ mod tests { engine.regenerate(&h, Modality::Graph, 0.6).await; // Resolve only the graph item. - let resolved = engine - .queue() - .resolve("rich-1", Modality::Graph) - .await; + let resolved = engine.queue().resolve("rich-1", Modality::Graph).await; assert!(resolved.is_some()); assert_eq!(resolved.unwrap().drifted_modality, Modality::Graph); @@ -1325,9 +1317,7 @@ mod tests { let engine = RegenerationEngine::new(config); let h = rich_octad(); - let result = engine - .regenerate(&h, Modality::Vector, 0.3) - .await; + let result = engine.regenerate(&h, Modality::Vector, 0.3).await; assert!( matches!(result, RegenerationResult::NoActionNeeded), "Score 0.3 should be below threshold 0.5" @@ -1343,9 +1333,7 @@ mod tests { let engine = RegenerationEngine::new(config); let h = rich_octad(); - let result = engine - .regenerate(&h, Modality::Vector, 0.5) - .await; + let result = engine.regenerate(&h, Modality::Vector, 0.5).await; assert!( matches!(result, RegenerationResult::NoActionNeeded), "Score exactly at threshold should be no-action (requires > threshold)" @@ -1361,9 +1349,7 @@ mod tests { let engine = RegenerationEngine::new(config); let h = rich_octad(); - let result = engine - .regenerate(&h, Modality::Vector, 0.51) - .await; + let result = engine.regenerate(&h, Modality::Vector, 0.51).await; assert!( matches!(result, RegenerationResult::Repaired { .. }), "Score 0.51 should trigger action with threshold 0.5" @@ -1379,9 +1365,7 @@ mod tests { assert!(engine.events().await.is_empty()); - engine - .regenerate(&h, Modality::Vector, 0.8) - .await; + engine.regenerate(&h, Modality::Vector, 0.8).await; let events = engine.events().await; assert_eq!(events.len(), 1); @@ -1401,9 +1385,7 @@ mod tests { let engine = RegenerationEngine::with_defaults(); let h = empty_octad(); - engine - .regenerate(&h, Modality::Vector, 0.9) - .await; + engine.regenerate(&h, Modality::Vector, 0.9).await; let events = engine.events().await; assert_eq!(events.len(), 1); @@ -1446,9 +1428,7 @@ mod tests { let engine = RegenerationEngine::new(config); let h = rich_octad(); // has temporal (version_count=3) - let result = engine - .regenerate(&h, Modality::Document, 0.8) - .await; + let result = engine.regenerate(&h, Modality::Document, 0.8).await; match result { RegenerationResult::Repaired { event } => { @@ -1523,7 +1503,10 @@ mod tests { let config = RegenerationConfig::default(); assert_eq!(config.drift_threshold, 0.3); assert_eq!(config.max_concurrent, 10); - assert_eq!(config.default_strategy, RegenerationStrategy::FromAuthoritative); + assert_eq!( + config.default_strategy, + RegenerationStrategy::FromAuthoritative + ); assert!(config.modality_strategies.is_empty()); assert_eq!(config.authority_order.len(), 8); } diff --git a/rust-core/verisim-octad/src/lib.rs b/rust-core/verisim-octad/src/lib.rs index 86a671d..f70e938 100644 --- a/rust-core/verisim-octad/src/lib.rs +++ b/rust-core/verisim-octad/src/lib.rs @@ -21,26 +21,30 @@ pub use verisim_provenance::{ InMemoryProvenanceStore, ProvenanceChain, ProvenanceError, ProvenanceEventType, ProvenanceRecord, ProvenanceStore, }; -pub use verisim_semantic::{ProofBlob, Provenance, SemanticAnnotation, SemanticStore, SemanticType, SemanticValue}; +pub use verisim_semantic::{ + ProofBlob, Provenance, SemanticAnnotation, SemanticStore, SemanticType, SemanticValue, +}; pub use verisim_spatial::{ - BoundingBox, Coordinates, GeometryType, InMemorySpatialStore, SpatialData, - SpatialSearchResult, SpatialStore, + BoundingBox, Coordinates, GeometryType, InMemorySpatialStore, SpatialData, SpatialSearchResult, + SpatialStore, }; -pub use verisim_tensor::{Tensor, TensorStore}; pub use verisim_temporal::{TemporalStore, TimeRange, Version}; +pub use verisim_tensor::{Tensor, TensorStore}; pub use verisim_vector::{Embedding, VectorStore}; // In-memory store implementation mod store; -pub use store::{OctadSnapshot, InMemoryOctadStore}; +pub use store::{InMemoryOctadStore, OctadSnapshot}; // Homoiconicity: queries as octads pub mod query_octad; -pub use query_octad::{QueryOctadBuilder, QueryExecution}; +pub use query_octad::{QueryExecution, QueryOctadBuilder}; // ACID transaction manager for cross-modality atomicity pub mod transaction; -pub use transaction::{IsolationLevel, LockType, TransactionManager, TransactionError, TransactionState}; +pub use transaction::{ + IsolationLevel, LockType, TransactionError, TransactionManager, TransactionState, +}; // WAL types (re-exported for external use) pub use verisim_wal::{SyncMode, WalEntry, WalModality, WalOperation, WalWriter}; @@ -149,14 +153,30 @@ impl ModalityStatus { /// Get list of missing modalities pub fn missing(&self) -> Vec<&'static str> { let mut missing = Vec::new(); - if !self.graph { missing.push("graph"); } - if !self.vector { missing.push("vector"); } - if !self.tensor { missing.push("tensor"); } - if !self.semantic { missing.push("semantic"); } - if !self.document { missing.push("document"); } - if !self.temporal { missing.push("temporal"); } - if !self.provenance { missing.push("provenance"); } - if !self.spatial { missing.push("spatial"); } + if !self.graph { + missing.push("graph"); + } + if !self.vector { + missing.push("vector"); + } + if !self.tensor { + missing.push("tensor"); + } + if !self.semantic { + missing.push("semantic"); + } + if !self.document { + missing.push("document"); + } + if !self.temporal { + missing.push("temporal"); + } + if !self.provenance { + missing.push("provenance"); + } + if !self.spatial { + missing.push("spatial"); + } missing } } @@ -182,7 +202,6 @@ pub struct OctadInput { pub metadata: HashMap<String, String>, } - /// Graph modality input #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OctadGraphInput { @@ -312,7 +331,8 @@ pub trait OctadStore: Send + Sync { async fn query_related(&self, id: &OctadId, predicate: &str) -> Result<Vec<Octad>, OctadError>; /// Get version at a specific point in time - async fn at_time(&self, id: &OctadId, time: DateTime<Utc>) -> Result<Option<Octad>, OctadError>; + async fn at_time(&self, id: &OctadId, time: DateTime<Utc>) + -> Result<Option<Octad>, OctadError>; /// List octads with pagination async fn list(&self, limit: usize, offset: usize) -> Result<Vec<Octad>, OctadError>; @@ -427,7 +447,9 @@ impl OctadBuilder { /// Add metadata pub fn with_metadata(mut self, key: &str, value: &str) -> Self { - self.input.metadata.insert(key.to_string(), value.to_string()); + self.input + .metadata + .insert(key.to_string(), value.to_string()); self } @@ -451,7 +473,10 @@ mod tests { fn test_octad_id() { let id = OctadId::new("test-123"); assert_eq!(id.as_str(), "test-123"); - assert_eq!(id.to_iri("https://example.org"), "https://example.org/test-123"); + assert_eq!( + id.to_iri("https://example.org"), + "https://example.org/test-123" + ); } #[test] diff --git a/rust-core/verisim-octad/src/query_octad.rs b/rust-core/verisim-octad/src/query_octad.rs index 8a91b6b..e4cf906 100644 --- a/rust-core/verisim-octad/src/query_octad.rs +++ b/rust-core/verisim-octad/src/query_octad.rs @@ -13,8 +13,10 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::{OctadId, OctadInput, OctadDocumentInput, OctadVectorInput, - OctadGraphInput, OctadTensorInput, OctadSemanticInput}; +use crate::{ + OctadDocumentInput, OctadGraphInput, OctadId, OctadInput, OctadSemanticInput, OctadTensorInput, + OctadVectorInput, +}; /// Metadata about a query execution #[derive(Debug, Clone, Serialize, Deserialize)] @@ -100,10 +102,11 @@ impl QueryOctadBuilder { } /// Build the OctadInput for storage + #[allow(clippy::field_reassign_with_default)] pub fn build(self) -> (OctadId, OctadInput) { - let id = self.query_id.unwrap_or_else(|| { - format!("query-{}", uuid::Uuid::new_v4()) - }); + let id = self + .query_id + .unwrap_or_else(|| format!("query-{}", uuid::Uuid::new_v4())); let mut input = OctadInput::default(); diff --git a/rust-core/verisim-octad/src/store.rs b/rust-core/verisim-octad/src/store.rs index 671f331..0b7d547 100644 --- a/rust-core/verisim-octad/src/store.rs +++ b/rust-core/verisim-octad/src/store.rs @@ -11,16 +11,16 @@ use std::sync::Arc; use tokio::sync::RwLock; use tracing::{debug, info, instrument}; +use crate::transaction::{IsolationLevel, LockType, TransactionManager}; use crate::{ Coordinates, Document, DocumentStore, Embedding, GeometryType, GraphEdge, GraphNode, - GraphObject, GraphStore, Octad, OctadConfig, OctadDocumentInput, OctadError, OctadGraphInput, - OctadId, OctadInput, OctadProvenanceInput, OctadSemanticInput, OctadSpatialInput, - OctadStatus, OctadStore, OctadTensorInput, OctadVectorInput, ModalityStatus, Provenance, + GraphObject, GraphStore, ModalityStatus, Octad, OctadConfig, OctadDocumentInput, OctadError, + OctadGraphInput, OctadId, OctadInput, OctadProvenanceInput, OctadSemanticInput, + OctadSpatialInput, OctadStatus, OctadStore, OctadTensorInput, OctadVectorInput, Provenance, ProvenanceEventType, ProvenanceStore, SemanticAnnotation, SemanticStore, SemanticValue, - SpatialData, SpatialStore, Tensor, TensorStore, TemporalStore, VectorStore, + SpatialData, SpatialStore, TemporalStore, Tensor, TensorStore, VectorStore, }; -use crate::transaction::{IsolationLevel, LockType, TransactionManager}; -use verisim_wal::{WalEntry, WalModality, WalOperation, WalWriter, SyncMode}; +use verisim_wal::{SyncMode, WalEntry, WalModality, WalOperation, WalWriter}; /// Snapshot of a Octad for versioning #[derive(Debug, Clone, Serialize, Deserialize)] @@ -89,6 +89,7 @@ where /// /// Automatically creates a [`TransactionManager`] to provide ACID /// guarantees across all modality writes. + #[allow(clippy::too_many_arguments)] pub fn new( config: OctadConfig, graph: Arc<G>, @@ -131,12 +132,11 @@ where wal_dir: impl AsRef<std::path::Path>, sync_mode: SyncMode, ) -> Result<Self, OctadError> { - let writer = WalWriter::open(wal_dir, sync_mode).map_err(|e| { - OctadError::ModalityError { + let writer = + WalWriter::open(wal_dir, sync_mode).map_err(|e| OctadError::ModalityError { modality: "wal".to_string(), message: format!("Failed to open WAL: {e}"), - } - })?; + })?; self.wal = Some(Arc::new(tokio::sync::Mutex::new(writer))); Ok(self) } @@ -164,10 +164,12 @@ where payload: payload.to_vec(), }; let mut writer = wal.lock().await; - writer.append(entry).map_err(|e| OctadError::ModalityError { - modality: "wal".to_string(), - message: format!("WAL append failed: {e}"), - })?; + writer + .append(entry) + .map_err(|e| OctadError::ModalityError { + modality: "wal".to_string(), + message: format!("WAL append failed: {e}"), + })?; } Ok(()) } @@ -261,7 +263,7 @@ where WalOperation::Insert | WalOperation::Update | WalOperation::Delete => { entity_ops.insert( entry.entity_id.clone(), - (entry.operation.clone(), entry.payload.clone()), + (entry.operation, entry.payload.clone()), ); if !committed_entities.contains(&entry.entity_id) { uncommitted_entities.insert(entry.entity_id.clone()); @@ -303,10 +305,7 @@ where let now = Utc::now(); // Get existing version or start at 1 - let version = octads - .get(entity_id) - .map(|s| s.version + 1) - .unwrap_or(1); + let version = octads.get(entity_id).map(|s| s.version + 1).unwrap_or(1); octads.insert( entity_id.clone(), @@ -320,7 +319,10 @@ where ); recovered += 1; } else { - tracing::warn!(entity_id, "WAL replay: failed to deserialize OctadInput"); + tracing::warn!( + entity_id, + "WAL replay: failed to deserialize OctadInput" + ); } } WalOperation::Delete => { @@ -332,7 +334,12 @@ where } } - info!(recovered, committed = committed_entities.len(), uncommitted = uncommitted_entities.len(), "WAL replay complete"); + info!( + recovered, + committed = committed_entities.len(), + uncommitted = uncommitted_entities.len(), + "WAL replay complete" + ); // Write a fresh checkpoint to mark recovery complete drop(octads); // Release write lock before checkpoint @@ -391,10 +398,13 @@ where self.config.base_iri, target_id ))), }; - self.graph.insert(&edge).await.map_err(|e| OctadError::ModalityError { - modality: "graph".to_string(), - message: e.to_string(), - })?; + self.graph + .insert(&edge) + .await + .map_err(|e| OctadError::ModalityError { + modality: "graph".to_string(), + message: e.to_string(), + })?; } debug!(id = %id, relationships = input.relationships.len(), "Graph modality populated"); @@ -416,10 +426,13 @@ where } let embedding = Embedding::new(id.as_str(), input.embedding.clone()); - self.vector.upsert(&embedding).await.map_err(|e| OctadError::ModalityError { - modality: "vector".to_string(), - message: e.to_string(), - })?; + self.vector + .upsert(&embedding) + .await + .map_err(|e| OctadError::ModalityError { + modality: "vector".to_string(), + message: e.to_string(), + })?; debug!(id = %id, dimension = input.embedding.len(), "Vector modality populated"); Ok(embedding) @@ -436,14 +449,20 @@ where doc = doc.with_field(key, value); } - self.document.index(&doc).await.map_err(|e| OctadError::ModalityError { - modality: "document".to_string(), - message: e.to_string(), - })?; - self.document.commit().await.map_err(|e| OctadError::ModalityError { - modality: "document".to_string(), - message: e.to_string(), - })?; + self.document + .index(&doc) + .await + .map_err(|e| OctadError::ModalityError { + modality: "document".to_string(), + message: e.to_string(), + })?; + self.document + .commit() + .await + .map_err(|e| OctadError::ModalityError { + modality: "document".to_string(), + message: e.to_string(), + })?; debug!(id = %id, title = %input.title, "Document modality populated"); Ok(doc) @@ -455,17 +474,21 @@ where id: &OctadId, input: &OctadTensorInput, ) -> Result<Tensor, OctadError> { - let tensor = Tensor::new(id.as_str(), input.shape.clone(), input.data.clone()).map_err( - |e| OctadError::ModalityError { + let tensor = + Tensor::new(id.as_str(), input.shape.clone(), input.data.clone()).map_err(|e| { + OctadError::ModalityError { + modality: "tensor".to_string(), + message: e.to_string(), + } + })?; + + self.tensor + .put(&tensor) + .await + .map_err(|e| OctadError::ModalityError { modality: "tensor".to_string(), message: e.to_string(), - }, - )?; - - self.tensor.put(&tensor).await.map_err(|e| OctadError::ModalityError { - modality: "tensor".to_string(), - message: e.to_string(), - })?; + })?; debug!(id = %id, shape = ?input.shape, "Tensor modality populated"); Ok(tensor) @@ -495,10 +518,13 @@ where provenance: Provenance::default(), }; - self.semantic.annotate(&annotation).await.map_err(|e| OctadError::ModalityError { - modality: "semantic".to_string(), - message: e.to_string(), - })?; + self.semantic + .annotate(&annotation) + .await + .map_err(|e| OctadError::ModalityError { + modality: "semantic".to_string(), + message: e.to_string(), + })?; debug!(id = %id, types = ?input.types, "Semantic modality populated"); Ok(annotation) @@ -522,21 +548,25 @@ where }; self.provenance - .record_event(id.as_str(), event_type, &input.actor, input.source.clone(), &input.description) + .record_event( + id.as_str(), + event_type, + &input.actor, + input.source.clone(), + &input.description, + ) .await .map_err(|e| OctadError::ModalityError { modality: "provenance".to_string(), message: e.to_string(), })?; - let chain = self - .provenance - .get_chain(id.as_str()) - .await - .map_err(|e| OctadError::ModalityError { + let chain = self.provenance.get_chain(id.as_str()).await.map_err(|e| { + OctadError::ModalityError { modality: "provenance".to_string(), message: e.to_string(), - })?; + } + })?; debug!(id = %id, chain_length = chain.len(), "Provenance modality populated"); Ok(chain.len() as u64) @@ -598,7 +628,12 @@ where } /// Create a snapshot for versioning - fn create_snapshot(&self, id: &OctadId, input: &OctadInput, status: &ModalityStatus) -> OctadSnapshot { + fn create_snapshot( + &self, + id: &OctadId, + input: &OctadInput, + status: &ModalityStatus, + ) -> OctadSnapshot { OctadSnapshot { id: id.clone(), input: input.clone(), @@ -624,37 +659,49 @@ where }; let embedding = if status.modality_status.vector { - self.vector.get(id.as_str()).await.map_err(|e| OctadError::ModalityError { - modality: "vector".to_string(), - message: e.to_string(), - })? + self.vector + .get(id.as_str()) + .await + .map_err(|e| OctadError::ModalityError { + modality: "vector".to_string(), + message: e.to_string(), + })? } else { None }; let document = if status.modality_status.document { - self.document.get(id.as_str()).await.map_err(|e| OctadError::ModalityError { - modality: "document".to_string(), - message: e.to_string(), - })? + self.document + .get(id.as_str()) + .await + .map_err(|e| OctadError::ModalityError { + modality: "document".to_string(), + message: e.to_string(), + })? } else { None }; let tensor = if status.modality_status.tensor { - self.tensor.get(id.as_str()).await.map_err(|e| OctadError::ModalityError { - modality: "tensor".to_string(), - message: e.to_string(), - })? + self.tensor + .get(id.as_str()) + .await + .map_err(|e| OctadError::ModalityError { + modality: "tensor".to_string(), + message: e.to_string(), + })? } else { None }; let semantic = if status.modality_status.semantic { - self.semantic.get_annotations(id.as_str()).await.map_err(|e| OctadError::ModalityError { - modality: "semantic".to_string(), - message: e.to_string(), - })? + self.semantic + .get_annotations(id.as_str()) + .await + .map_err(|e| OctadError::ModalityError { + modality: "semantic".to_string(), + message: e.to_string(), + })? } else { None }; @@ -679,10 +726,13 @@ where // Load spatial data let spatial_data = if status.modality_status.spatial { - self.spatial.get(id.as_str()).await.map_err(|e| OctadError::ModalityError { - modality: "spatial".to_string(), - message: e.to_string(), - })? + self.spatial + .get(id.as_str()) + .await + .map_err(|e| OctadError::ModalityError { + modality: "spatial".to_string(), + message: e.to_string(), + })? } else { None }; @@ -724,7 +774,13 @@ where // On crash recovery, PENDING entries without a matching COMMITTED // entry indicate incomplete operations that need rollback. let input_payload = serde_json::to_vec(&input).unwrap_or_default(); - self.wal_append(WalOperation::Insert, WalModality::All, &entity_id_str, &input_payload).await?; + self.wal_append( + WalOperation::Insert, + WalModality::All, + &entity_id_str, + &input_payload, + ) + .await?; // Begin ACID transaction — acquire exclusive locks on all requested // modalities before writing, ensuring atomicity across the octad. @@ -936,10 +992,20 @@ where }; // Store in registry - self.octads.write().await.insert(id.as_str().to_string(), status.clone()); + self.octads + .write() + .await + .insert(id.as_str().to_string(), status.clone()); // Write COMMITTED marker to WAL and checkpoint for crash recovery. - self.wal_append(WalOperation::Checkpoint, WalModality::All, &entity_id_str, b"COMMITTED").await.ok(); + self.wal_append( + WalOperation::Checkpoint, + WalModality::All, + &entity_id_str, + b"COMMITTED", + ) + .await + .ok(); self.wal_checkpoint().await.ok(); info!(id = %id, modalities = ?modality_status, "Created octad (transaction committed)"); @@ -972,7 +1038,13 @@ where // Write PENDING intent to WAL before modality writes let input_payload = serde_json::to_vec(&input).unwrap_or_default(); - self.wal_append(WalOperation::Update, WalModality::All, &entity_id_str, &input_payload).await?; + self.wal_append( + WalOperation::Update, + WalModality::All, + &entity_id_str, + &input_payload, + ) + .await?; // Begin ACID transaction for atomic update across all modalities let txn_id = self.txn_manager.begin(IsolationLevel::ReadCommitted).await; @@ -1174,10 +1246,20 @@ where }; // Update registry - self.octads.write().await.insert(id.as_str().to_string(), status.clone()); + self.octads + .write() + .await + .insert(id.as_str().to_string(), status.clone()); // Write COMMITTED marker to WAL and checkpoint - self.wal_append(WalOperation::Checkpoint, WalModality::All, &entity_id_str, b"COMMITTED").await.ok(); + self.wal_append( + WalOperation::Checkpoint, + WalModality::All, + &entity_id_str, + b"COMMITTED", + ) + .await + .ok(); self.wal_checkpoint().await.ok(); info!(id = %id, version = version, "Updated octad (transaction committed)"); @@ -1213,7 +1295,8 @@ where let existing = existing.ok_or_else(|| OctadError::NotFound(id.to_string()))?; // Write PENDING delete intent to WAL - self.wal_append(WalOperation::Delete, WalModality::All, &entity_id_str, b"").await?; + self.wal_append(WalOperation::Delete, WalModality::All, &entity_id_str, b"") + .await?; // Begin ACID transaction for atomic delete across all modalities let txn_id = self.txn_manager.begin(IsolationLevel::ReadCommitted).await; @@ -1273,7 +1356,14 @@ where self.octads.write().await.remove(id.as_str()); // Write COMMITTED marker to WAL and checkpoint - self.wal_append(WalOperation::Checkpoint, WalModality::All, &entity_id_str, b"COMMITTED").await.ok(); + self.wal_append( + WalOperation::Checkpoint, + WalModality::All, + &entity_id_str, + b"COMMITTED", + ) + .await + .ok(); self.wal_checkpoint().await.ok(); info!(id = %id, "Deleted octad (transaction committed)"); @@ -1285,10 +1375,14 @@ where } async fn search_similar(&self, embedding: &[f32], k: usize) -> Result<Vec<Octad>, OctadError> { - let results = self.vector.search(embedding, k).await.map_err(|e| OctadError::ModalityError { - modality: "vector".to_string(), - message: e.to_string(), - })?; + let results = + self.vector + .search(embedding, k) + .await + .map_err(|e| OctadError::ModalityError { + modality: "vector".to_string(), + message: e.to_string(), + })?; let mut octads = Vec::new(); for result in results { @@ -1302,10 +1396,13 @@ where async fn search_text(&self, query: &str, limit: usize) -> Result<Vec<Octad>, OctadError> { let results = - self.document.search(query, limit).await.map_err(|e| OctadError::ModalityError { - modality: "document".to_string(), - message: e.to_string(), - })?; + self.document + .search(query, limit) + .await + .map_err(|e| OctadError::ModalityError { + modality: "document".to_string(), + message: e.to_string(), + })?; let mut octads = Vec::new(); for result in results { @@ -1319,10 +1416,14 @@ where async fn query_related(&self, id: &OctadId, predicate: &str) -> Result<Vec<Octad>, OctadError> { let node = GraphNode::new(id.to_iri(&self.config.base_iri)); - let edges = self.graph.outgoing(&node).await.map_err(|e| OctadError::ModalityError { - modality: "graph".to_string(), - message: e.to_string(), - })?; + let edges = self + .graph + .outgoing(&node) + .await + .map_err(|e| OctadError::ModalityError { + modality: "graph".to_string(), + message: e.to_string(), + })?; let predicate_iri = format!("{}/{}", self.config.base_iri, predicate); let mut octads = Vec::new(); @@ -1348,12 +1449,7 @@ where async fn list(&self, limit: usize, offset: usize) -> Result<Vec<Octad>, OctadError> { let octads = self.octads.read().await; - let ids: Vec<String> = octads - .keys() - .skip(offset) - .take(limit) - .cloned() - .collect(); + let ids: Vec<String> = octads.keys().skip(offset).take(limit).cloned().collect(); drop(octads); let mut result = Vec::with_capacity(ids.len()); @@ -1365,7 +1461,11 @@ where Ok(result) } - async fn at_time(&self, id: &OctadId, time: DateTime<Utc>) -> Result<Option<Octad>, OctadError> { + async fn at_time( + &self, + id: &OctadId, + time: DateTime<Utc>, + ) -> Result<Option<Octad>, OctadError> { let version = self .temporal .at_time(id.as_str(), time) @@ -1403,7 +1503,7 @@ mod tests { use verisim_spatial::InMemorySpatialStore; use verisim_temporal::InMemoryVersionStore; use verisim_tensor::InMemoryTensorStore; - use verisim_vector::{DistanceMetric, BruteForceVectorStore}; + use verisim_vector::{BruteForceVectorStore, DistanceMetric}; fn create_test_store() -> InMemoryOctadStore< SimpleGraphStore, diff --git a/rust-core/verisim-octad/src/transaction.rs b/rust-core/verisim-octad/src/transaction.rs index 9864242..f275713 100644 --- a/rust-core/verisim-octad/src/transaction.rs +++ b/rust-core/verisim-octad/src/transaction.rs @@ -52,8 +52,14 @@ use uuid::Uuid; /// Originally six modalities; extended with `provenance` and `spatial` when /// VeriSimDB evolved from octad to octad model. pub const MODALITIES: &[&str] = &[ - "graph", "vector", "tensor", "semantic", "document", "temporal", - "provenance", "spatial", + "graph", + "vector", + "tensor", + "semantic", + "document", + "temporal", + "provenance", + "spatial", ]; // --------------------------------------------------------------------------- @@ -153,22 +159,17 @@ pub enum TransactionState { /// Transaction isolation level. /// /// Determines what data other concurrent transactions can see. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub enum IsolationLevel { /// Reads only committed data. A transaction may see different snapshots /// of the same entity if another transaction commits between reads. + #[default] ReadCommitted, /// Full serializability: the transaction operates as if it were the only /// one running. Version conflicts cause the transaction to abort. Serializable, } -impl Default for IsolationLevel { - fn default() -> Self { - Self::ReadCommitted - } -} - /// Type of lock held on an entity/modality pair. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum LockType { @@ -545,10 +546,8 @@ impl VersionTable { /// Set the version for an entity/modality pair. fn set(&mut self, entity_id: &str, modality: &str, version: u64) { - self.versions.insert( - (entity_id.to_string(), modality.to_string()), - version, - ); + self.versions + .insert((entity_id.to_string(), modality.to_string()), version); } /// Increment and return the new version for an entity/modality pair. @@ -602,10 +601,7 @@ impl TransactionManager { "Transaction started" ); - self.active_transactions - .write() - .await - .insert(txn_id, txn); + self.active_transactions.write().await.insert(txn_id, txn); txn_id } @@ -633,8 +629,7 @@ impl TransactionManager { if txn.isolation_level == IsolationLevel::Serializable { let version_table = self.version_table.read().await; for stamp in &txn.read_set { - let current_version = - version_table.get(&stamp.entity_id, &stamp.modality); + let current_version = version_table.get(&stamp.entity_id, &stamp.modality); if current_version != stamp.version_at_read { // Another transaction committed a write to this entity/modality // after we read it. Must abort. @@ -696,10 +691,7 @@ impl TransactionManager { /// /// Returns the undo log entries in reverse order so the caller can apply /// compensating actions to the modality stores. - pub async fn rollback( - &self, - transaction_id: Uuid, - ) -> Result<Vec<UndoEntry>, TransactionError> { + pub async fn rollback(&self, transaction_id: Uuid) -> Result<Vec<UndoEntry>, TransactionError> { let mut txns = self.active_transactions.write().await; let txn = txns .get_mut(&transaction_id) @@ -782,9 +774,8 @@ impl TransactionManager { lock_type, }; // Avoid duplicates (upgrade replaces) - txn.locks.retain(|l| { - !(l.entity_id == entry.entity_id && l.modality == entry.modality) - }); + txn.locks + .retain(|l| !(l.entity_id == entry.entity_id && l.modality == entry.modality)); txn.locks.push(entry); } } @@ -884,10 +875,7 @@ impl TransactionManager { /// Get a snapshot of a transaction's current state. /// /// Returns `None` if the transaction does not exist. - pub async fn get_transaction_state( - &self, - transaction_id: Uuid, - ) -> Option<TransactionState> { + pub async fn get_transaction_state(&self, transaction_id: Uuid) -> Option<TransactionState> { self.active_transactions .read() .await @@ -922,22 +910,12 @@ impl TransactionManager { /// Check whether a specific entity/modality pair is currently locked. pub async fn is_locked(&self, entity_id: &str, modality: &str) -> bool { - self.lock_table - .read() - .await - .is_locked(entity_id, modality) + self.lock_table.read().await.is_locked(entity_id, modality) } /// Get the lock holders for an entity/modality pair. - pub async fn lock_holders( - &self, - entity_id: &str, - modality: &str, - ) -> Vec<(Uuid, LockType)> { - self.lock_table - .read() - .await - .holders(entity_id, modality) + pub async fn lock_holders(&self, entity_id: &str, modality: &str) -> Vec<(Uuid, LockType)> { + self.lock_table.read().await.holders(entity_id, modality) } /// Remove completed (Committed or RolledBack) transactions from memory. @@ -1020,10 +998,7 @@ mod tests { mgr.commit(txn_id).await.unwrap(); let result = mgr.commit(txn_id).await; - assert!(matches!( - result, - Err(TransactionError::InvalidState { .. }) - )); + assert!(matches!(result, Err(TransactionError::InvalidState { .. }))); } // -- Test 4: Commit after rollback is rejected -- @@ -1036,10 +1011,7 @@ mod tests { mgr.rollback(txn_id).await.unwrap(); let result = mgr.commit(txn_id).await; - assert!(matches!( - result, - Err(TransactionError::InvalidState { .. }) - )); + assert!(matches!(result, Err(TransactionError::InvalidState { .. }))); } // -- Test 5: Nonexistent transaction -- @@ -1263,18 +1235,12 @@ mod tests { let result = mgr .acquire_lock(txn_id, "e1", "nosuch", LockType::Shared) .await; - assert!(matches!( - result, - Err(TransactionError::InvalidModality(_)) - )); + assert!(matches!(result, Err(TransactionError::InvalidModality(_)))); let result = mgr .record_undo(txn_id, "e1", "invalid_modality", None, 0) .await; - assert!(matches!( - result, - Err(TransactionError::InvalidModality(_)) - )); + assert!(matches!(result, Err(TransactionError::InvalidModality(_)))); } // -- Test 16: Deadlock detection -- diff --git a/rust-core/verisim-octad/tests/atomicity_tests.rs b/rust-core/verisim-octad/tests/atomicity_tests.rs index fcace55..e337269 100644 --- a/rust-core/verisim-octad/tests/atomicity_tests.rs +++ b/rust-core/verisim-octad/tests/atomicity_tests.rs @@ -7,11 +7,9 @@ //! integration in [`InMemoryOctadStore`]. use std::sync::Arc; -use verisim_octad::{ - OctadBuilder, OctadConfig, OctadId, OctadStore, InMemoryOctadStore, -}; use verisim_document::TantivyDocumentStore; use verisim_graph::SimpleGraphStore; +use verisim_octad::{InMemoryOctadStore, OctadBuilder, OctadConfig, OctadId, OctadStore}; use verisim_provenance::InMemoryProvenanceStore; use verisim_semantic::InMemorySemanticStore; use verisim_spatial::InMemorySpatialStore; @@ -39,7 +37,10 @@ fn create_test_store(vector_dim: usize) -> TestOctadStore { InMemoryOctadStore::new( config, Arc::new(SimpleGraphStore::in_memory().unwrap()), - Arc::new(BruteForceVectorStore::new(vector_dim, DistanceMetric::Cosine)), + Arc::new(BruteForceVectorStore::new( + vector_dim, + DistanceMetric::Cosine, + )), Arc::new(TantivyDocumentStore::in_memory().unwrap()), Arc::new(InMemoryTensorStore::new()), Arc::new(InMemorySemanticStore::new()), @@ -107,7 +108,10 @@ async fn test_create_vector_dimension_mismatch_rolls_back() { .build(); let result = store.create(input).await; - assert!(result.is_err(), "Create with wrong vector dimension should fail"); + assert!( + result.is_err(), + "Create with wrong vector dimension should fail" + ); // Verify no octads exist (the document write should have been rolled back) let all = store.list(100, 0).await.unwrap(); @@ -213,13 +217,24 @@ async fn test_update_with_invalid_vector_dimension_rolls_back() { .build(); let result = store.update(&octad.id, bad_update).await; - assert!(result.is_err(), "Update with wrong vector dimension should fail"); + assert!( + result.is_err(), + "Update with wrong vector dimension should fail" + ); // Original entity should still be intact let original = store.get(&octad.id).await.unwrap().unwrap(); - assert_eq!(original.status.version, 1, "Version should not change on failed update"); + assert_eq!( + original.status.version, 1, + "Version should not change on failed update" + ); assert!( - original.document.as_ref().unwrap().title.contains("Original"), + original + .document + .as_ref() + .unwrap() + .title + .contains("Original"), "Original data should survive failed update" ); } diff --git a/rust-core/verisim-octad/tests/crash_recovery_tests.rs b/rust-core/verisim-octad/tests/crash_recovery_tests.rs index 05433cf..707edfc 100644 --- a/rust-core/verisim-octad/tests/crash_recovery_tests.rs +++ b/rust-core/verisim-octad/tests/crash_recovery_tests.rs @@ -6,12 +6,11 @@ use std::collections::HashMap; use std::sync::Arc; -use verisim_octad::{ - InMemoryOctadStore, OctadConfig, OctadInput, OctadDocumentInput, - OctadSnapshot, OctadStore, -}; use verisim_document::TantivyDocumentStore; use verisim_graph::SimpleGraphStore; +use verisim_octad::{ + InMemoryOctadStore, OctadConfig, OctadDocumentInput, OctadInput, OctadSnapshot, OctadStore, +}; use verisim_semantic::InMemorySemanticStore; use verisim_temporal::InMemoryVersionStore; use verisim_tensor::InMemoryTensorStore; @@ -110,7 +109,10 @@ async fn ten_entities_survive_crash() { { let store = create_store(wal.to_str().unwrap()); for i in 0..10 { - let octad = store.create(doc(&format!("E{i}"), &format!("B{i}"))).await.unwrap(); + let octad = store + .create(doc(&format!("E{i}"), &format!("B{i}"))) + .await + .unwrap(); ids.push(octad.id); } // Crash @@ -144,7 +146,10 @@ async fn delete_survives_crash() { { let store = create_store(wal.to_str().unwrap()); let _n: usize = store.replay_wal(&wal).await.unwrap(); - assert!(store.get(&entity_id).await.unwrap().is_none(), "Should stay deleted"); + assert!( + store.get(&entity_id).await.unwrap().is_none(), + "Should stay deleted" + ); } } diff --git a/rust-core/verisim-octad/tests/integration_tests.rs b/rust-core/verisim-octad/tests/integration_tests.rs index b08edcf..917f3d4 100644 --- a/rust-core/verisim-octad/tests/integration_tests.rs +++ b/rust-core/verisim-octad/tests/integration_tests.rs @@ -5,11 +5,9 @@ //! Persistence tests are gated behind `#[ignore]` until store serialization is implemented. use std::sync::Arc; -use verisim_octad::{ - OctadBuilder, OctadConfig, OctadStore, InMemoryOctadStore, -}; use verisim_document::TantivyDocumentStore; use verisim_graph::SimpleGraphStore; +use verisim_octad::{InMemoryOctadStore, OctadBuilder, OctadConfig, OctadStore}; use verisim_semantic::InMemorySemanticStore; use verisim_temporal::InMemoryVersionStore; use verisim_tensor::InMemoryTensorStore; @@ -35,7 +33,10 @@ fn create_test_store(vector_dim: usize) -> TestOctadStore { InMemoryOctadStore::new( config, Arc::new(SimpleGraphStore::in_memory().unwrap()), - Arc::new(BruteForceVectorStore::new(vector_dim, DistanceMetric::Cosine)), + Arc::new(BruteForceVectorStore::new( + vector_dim, + DistanceMetric::Cosine, + )), Arc::new(TantivyDocumentStore::in_memory().unwrap()), Arc::new(InMemoryTensorStore::new()), Arc::new(InMemorySemanticStore::new()), @@ -126,9 +127,18 @@ async fn test_full_text_search() { // Create entities with different content let docs = vec![ - ("Rust Programming", "Rust is a systems programming language focused on safety"), - ("Python Guide", "Python is a high-level programming language"), - ("Database Design", "Relational databases use SQL for querying"), + ( + "Rust Programming", + "Rust is a systems programming language focused on safety", + ), + ( + "Python Guide", + "Python is a high-level programming language", + ), + ( + "Database Design", + "Relational databases use SQL for querying", + ), ]; for (title, body) in &docs { @@ -146,7 +156,10 @@ async fn test_full_text_search() { // Search for "programming" - should match multiple let results = store.search_text("programming", 10).await.unwrap(); - assert!(results.len() >= 2, "Should match at least 2 programming docs"); + assert!( + results.len() >= 2, + "Should match at least 2 programming docs" + ); } /// Test temporal versioning @@ -262,7 +275,7 @@ async fn test_tensor_persistence() { #[tokio::test] #[ignore = "persistence not yet implemented on InMemorySemanticStore"] async fn test_semantic_persistence() { - use verisim_semantic::{SemanticStore as _, SemanticType, Constraint, ConstraintKind}; + use verisim_semantic::{Constraint, ConstraintKind, SemanticStore as _, SemanticType}; let store = InMemorySemanticStore::new(); @@ -290,8 +303,14 @@ async fn test_temporal_persistence() { let store: InMemoryVersionStore<String> = InMemoryVersionStore::new(); - store.append("entity1", "v1 data".to_string(), "alice", Some("first")).await.unwrap(); - store.append("entity1", "v2 data".to_string(), "bob", Some("second")).await.unwrap(); + store + .append("entity1", "v1 data".to_string(), "alice", Some("first")) + .await + .unwrap(); + store + .append("entity1", "v2 data".to_string(), "bob", Some("second")) + .await + .unwrap(); // TODO: Implement save_to_file/load_from_file on InMemoryVersionStore // store.save_to_file(temp_path).unwrap(); @@ -346,7 +365,10 @@ async fn test_high_dimension_vector_search() { embedding[(i * 7) % 768] = 0.5; let input = OctadBuilder::new() - .with_document(&format!("Entity {}", i), &format!("High-dim entity number {}", i)) + .with_document( + &format!("Entity {}", i), + &format!("High-dim entity number {}", i), + ) .with_embedding(embedding) .build(); diff --git a/rust-core/verisim-octad/tests/stress_tests.rs b/rust-core/verisim-octad/tests/stress_tests.rs index b24b0c3..0c9e4b3 100644 --- a/rust-core/verisim-octad/tests/stress_tests.rs +++ b/rust-core/verisim-octad/tests/stress_tests.rs @@ -7,12 +7,11 @@ use std::collections::HashMap; use std::sync::Arc; -use verisim_octad::{ - InMemoryOctadStore, OctadConfig, OctadDocumentInput, OctadId, - OctadInput, OctadSnapshot, OctadStore, -}; use verisim_document::TantivyDocumentStore; use verisim_graph::SimpleGraphStore; +use verisim_octad::{ + InMemoryOctadStore, OctadConfig, OctadDocumentInput, OctadInput, OctadSnapshot, OctadStore, +}; use verisim_semantic::InMemorySemanticStore; use verisim_temporal::InMemoryVersionStore; use verisim_tensor::InMemoryTensorStore; @@ -101,7 +100,10 @@ async fn concurrent_read_write() { // Seed 100 entities first let mut seed_ids = Vec::new(); for i in 0..100 { - let octad = store.create(doc(&format!("Seed-{i}"), &format!("Seed body {i}"))).await.unwrap(); + let octad = store + .create(doc(&format!("Seed-{i}"), &format!("Seed body {i}"))) + .await + .unwrap(); seed_ids.push(octad.id); } @@ -110,10 +112,13 @@ async fn concurrent_read_write() { let store = store.clone(); handles.push(tokio::spawn(async move { for i in 0..10 { - store.create(doc( - &format!("Concurrent-W{writer_id}-{i}"), - &format!("Body {writer_id}-{i}"), - )).await.unwrap(); + store + .create(doc( + &format!("Concurrent-W{writer_id}-{i}"), + &format!("Body {writer_id}-{i}"), + )) + .await + .unwrap(); } })); } @@ -127,7 +132,7 @@ async fn concurrent_read_write() { let result = store.get(id).await; match result { Ok(Some(_)) => {} // Expected - Ok(None) => {} // Acceptable during concurrent writes + Ok(None) => {} // Acceptable during concurrent writes Err(e) => panic!("Reader {reader_id} error on {id}: {e}"), } } @@ -166,6 +171,9 @@ async fn concurrent_create_delete() { // All should be gone for id in &ids { - assert!(store.get(id).await.unwrap().is_none(), "{id} should be deleted"); + assert!( + store.get(id).await.unwrap().is_none(), + "{id} should be deleted" + ); } } diff --git a/rust-core/verisim-planner/src/config.rs b/rust-core/verisim-planner/src/config.rs index 39acd21..5567696 100644 --- a/rust-core/verisim-planner/src/config.rs +++ b/rust-core/verisim-planner/src/config.rs @@ -150,12 +150,8 @@ mod tests { assert!( (OptimizationMode::Conservative.selectivity_multiplier() - 2.0).abs() < f64::EPSILON ); - assert!( - (OptimizationMode::Balanced.selectivity_multiplier() - 1.0).abs() < f64::EPSILON - ); - assert!( - (OptimizationMode::Aggressive.selectivity_multiplier() - 0.5).abs() < f64::EPSILON - ); + assert!((OptimizationMode::Balanced.selectivity_multiplier() - 1.0).abs() < f64::EPSILON); + assert!((OptimizationMode::Aggressive.selectivity_multiplier() - 0.5).abs() < f64::EPSILON); } #[test] diff --git a/rust-core/verisim-planner/src/cost.rs b/rust-core/verisim-planner/src/cost.rs index e578453..311e10f 100644 --- a/rust-core/verisim-planner/src/cost.rs +++ b/rust-core/verisim-planner/src/cost.rs @@ -469,7 +469,12 @@ impl CostModel { /// or just "ContractName" (defaults to custom/ZKP). fn extract_proof_type(contract: &str) -> String { let known_types = [ - "existence", "citation", "access", "integrity", "provenance", "zkp", + "existence", + "citation", + "access", + "integrity", + "provenance", + "zkp", ]; let lower = contract.to_lowercase(); for t in &known_types { @@ -499,7 +504,9 @@ mod tests { fn test_base_selectivity_values() { assert!((BaseCost::for_modality(Modality::Graph).selectivity - 0.2).abs() < f64::EPSILON); assert!((BaseCost::for_modality(Modality::Vector).selectivity - 0.01).abs() < f64::EPSILON); - assert!((BaseCost::for_modality(Modality::Semantic).selectivity - 0.8).abs() < f64::EPSILON); + assert!( + (BaseCost::for_modality(Modality::Semantic).selectivity - 0.8).abs() < f64::EPSILON + ); } #[test] @@ -607,20 +614,32 @@ mod tests { fn test_proof_cost_zkp_is_expensive() { let pc = ProofCost::for_type("zkp"); assert!(pc.total_ms() > 100.0, "ZKP proof should be > 100ms"); - assert!(pc.circuit_ms > 0.0, "ZKP should have circuit generation cost"); + assert!( + pc.circuit_ms > 0.0, + "ZKP should have circuit generation cost" + ); } #[test] fn test_proof_cost_integrity_includes_circuit() { let pc = ProofCost::for_type("integrity"); - assert!(pc.circuit_ms > 0.0, "Integrity proof includes Merkle circuit"); - assert!(pc.verify_ms > pc.circuit_ms, "Verify > circuit for integrity"); + assert!( + pc.circuit_ms > 0.0, + "Integrity proof includes Merkle circuit" + ); + assert!( + pc.verify_ms > pc.circuit_ms, + "Verify > circuit for integrity" + ); } #[test] fn test_proof_cost_unknown_defaults_to_custom() { let pc = ProofCost::for_type("MyCustomContract"); - assert!(pc.total_ms() > 100.0, "Unknown proof defaults to expensive custom"); + assert!( + pc.total_ms() > 100.0, + "Unknown proof defaults to expensive custom" + ); } #[test] @@ -706,11 +725,15 @@ mod tests { fn test_post_processing_order_by_scales_with_rows() { use crate::plan::PostProcessing; let small = PostProcessingCost::estimate( - &PostProcessing::OrderBy { fields: vec![("name".into(), true)] }, + &PostProcessing::OrderBy { + fields: vec![("name".into(), true)], + }, 100, ); let large = PostProcessingCost::estimate( - &PostProcessing::OrderBy { fields: vec![("name".into(), true)] }, + &PostProcessing::OrderBy { + fields: vec![("name".into(), true)], + }, 10000, ); assert!(large.time_ms > small.time_ms * 10.0, "O(n log n) scaling"); @@ -740,7 +763,9 @@ mod tests { cpu_cost: 40.0, }; let pps = vec![ - PostProcessing::OrderBy { fields: vec![("score".into(), false)] }, + PostProcessing::OrderBy { + fields: vec![("score".into(), false)], + }, PostProcessing::Limit { count: 10 }, ]; let total = CostModel::estimate_with_post_processing(&base, &pps); diff --git a/rust-core/verisim-planner/src/explain.rs b/rust-core/verisim-planner/src/explain.rs index 5201eb1..95153a4 100644 --- a/rust-core/verisim-planner/src/explain.rs +++ b/rust-core/verisim-planner/src/explain.rs @@ -93,7 +93,11 @@ impl ExplainOutput { } }) .collect(); - cost_breakdown.sort_by(|a, b| b.percentage.partial_cmp(&a.percentage).unwrap_or(std::cmp::Ordering::Equal)); + cost_breakdown.sort_by(|a, b| { + b.percentage + .partial_cmp(&a.percentage) + .unwrap_or(std::cmp::Ordering::Equal) + }); // Generate performance hints let mut hints = Vec::new(); @@ -119,7 +123,9 @@ impl ExplainOutput { step.step, step.modality, step.cost.time_ms, - step.optimization_hint.as_deref().unwrap_or("consider optimization") + step.optimization_hint + .as_deref() + .unwrap_or("consider optimization") ), }); } @@ -140,9 +146,7 @@ impl ExplainOutput { } // Sequential when parallel is possible - if plan.steps.len() >= 2 - && plan.strategy == crate::plan::ExecutionStrategy::Sequential - { + if plan.steps.len() >= 2 && plan.strategy == crate::plan::ExecutionStrategy::Sequential { hints.push(PerformanceHint { severity: "suggestion".to_string(), message: "Multiple modalities could benefit from parallel execution".to_string(), @@ -153,7 +157,14 @@ impl ExplainOutput { let total_cost_ms = plan.total_cost.time_ms; // Build text output - let text_output = Self::render_text(&steps, &cost_breakdown, &hints, &strategy, total_cost_ms, config); + let text_output = Self::render_text( + &steps, + &cost_breakdown, + &hints, + &strategy, + total_cost_ms, + config, + ); ExplainOutput { steps, diff --git a/rust-core/verisim-planner/src/lib.rs b/rust-core/verisim-planner/src/lib.rs index 4143ccf..f8c73ef 100644 --- a/rust-core/verisim-planner/src/lib.rs +++ b/rust-core/verisim-planner/src/lib.rs @@ -28,8 +28,10 @@ pub use error::PlannerError; pub use explain::ExplainOutput; pub use optimizer::Planner; pub use plan::{LogicalPlan, PhysicalPlan}; -pub use profiler::{ExplainAnalyzeOutput, Profiler, ProfileStep, QueryProfile}; -pub use prepared::{CacheConfig, CacheError, CacheStats, ParamValue, PlanCache, PreparedId, PreparedStatement}; +pub use prepared::{ + CacheConfig, CacheError, CacheStats, ParamValue, PlanCache, PreparedId, PreparedStatement, +}; +pub use profiler::{ExplainAnalyzeOutput, ProfileStep, Profiler, QueryProfile}; pub use slow_query::{SlowQueryConfig, SlowQueryEntry, SlowQueryLog, SlowQuerySummary}; pub use stats::{AdaptiveTuner, StatisticsCollector, StoreStatistics}; diff --git a/rust-core/verisim-planner/src/optimizer.rs b/rust-core/verisim-planner/src/optimizer.rs index de6f02f..1953dac 100644 --- a/rust-core/verisim-planner/src/optimizer.rs +++ b/rust-core/verisim-planner/src/optimizer.rs @@ -57,10 +57,7 @@ impl Planner { return Err(PlannerError::EmptyPlan); } - debug!( - node_count = logical.nodes.len(), - "Optimizing logical plan" - ); + debug!(node_count = logical.nodes.len(), "Optimizing logical plan"); // 1. Estimate cost for each node let mut node_costs: Vec<(usize, CostEstimate, Option<String>)> = logical @@ -79,9 +76,11 @@ impl Planner { node_costs.sort_by(|a, b| { let pri_a = logical.nodes[a.0].modality.execution_priority(); let pri_b = logical.nodes[b.0].modality.execution_priority(); - pri_a - .cmp(&pri_b) - .then_with(|| a.1.time_ms.partial_cmp(&b.1.time_ms).unwrap_or(std::cmp::Ordering::Equal)) + pri_a.cmp(&pri_b).then_with(|| { + a.1.time_ms + .partial_cmp(&b.1.time_ms) + .unwrap_or(std::cmp::Ordering::Equal) + }) }); // 3. Select execution strategy @@ -116,11 +115,8 @@ impl Planner { } ); - let pushed_predicates: Vec<String> = node - .conditions - .iter() - .map(|c| format!("{:?}", c)) - .collect(); + let pushed_predicates: Vec<String> = + node.conditions.iter().map(|c| format!("{:?}", c)).collect(); steps.push(PlanStep { step: step_num + 1, @@ -137,10 +133,8 @@ impl Planner { // 5. Combine total cost (modality queries + post-processing) let is_parallel = strategy == ExecutionStrategy::Parallel; let modality_cost = CostEstimate::combine(&cost_estimates, is_parallel); - let total_cost = CostModel::estimate_with_post_processing( - &modality_cost, - &logical.post_processing, - ); + let total_cost = + CostModel::estimate_with_post_processing(&modality_cost, &logical.post_processing); // 6. Generate optimization notes if is_parallel { @@ -153,7 +147,10 @@ impl Planner { } if total_cost.time_ms > 500.0 { - notes.push("High estimated cost — consider adding LIMIT or more selective predicates".to_string()); + notes.push( + "High estimated cost — consider adding LIMIT or more selective predicates" + .to_string(), + ); } // Check if any step has poor selectivity diff --git a/rust-core/verisim-planner/src/plan.rs b/rust-core/verisim-planner/src/plan.rs index 300f995..3ec177b 100644 --- a/rust-core/verisim-planner/src/plan.rs +++ b/rust-core/verisim-planner/src/plan.rs @@ -25,13 +25,20 @@ pub enum ConditionKind { /// Equality filter (field = value). Equality { field: String, value: String }, /// Range filter (field BETWEEN low AND high). - Range { field: String, low: String, high: String }, + Range { + field: String, + low: String, + high: String, + }, /// Full-text search. Fulltext { query: String }, /// Vector similarity (k-NN). Similarity { k: usize }, /// Graph traversal. - Traversal { predicate: String, depth: Option<u32> }, + Traversal { + predicate: String, + depth: Option<u32>, + }, /// Temporal version lookup. AtTime { timestamp: String }, /// ZKP proof verification. @@ -64,7 +71,10 @@ pub enum PostProcessing { /// LIMIT result count. Limit { count: usize }, /// GROUP BY + aggregation. - GroupBy { fields: Vec<String>, aggregates: Vec<String> }, + GroupBy { + fields: Vec<String>, + aggregates: Vec<String>, + }, /// Final projection. Project { columns: Vec<String> }, } diff --git a/rust-core/verisim-planner/src/prepared.rs b/rust-core/verisim-planner/src/prepared.rs index 439ee2a..28c9893 100644 --- a/rust-core/verisim-planner/src/prepared.rs +++ b/rust-core/verisim-planner/src/prepared.rs @@ -515,9 +515,7 @@ impl PlanCache { let expired_ids: Vec<PreparedId> = stmts .iter() - .filter(|(_, stmt)| { - now.signed_duration_since(stmt.created_at) > ttl - }) + .filter(|(_, stmt)| now.signed_duration_since(stmt.created_at) > ttl) .map(|(id, _)| id.clone()) .collect(); @@ -601,26 +599,71 @@ impl PlanCache { /// the case of identifiers and string literals. fn normalize_query(query: &str) -> String { // Step 1: Collapse all whitespace into single spaces and trim. - let collapsed: String = query - .split_whitespace() - .collect::<Vec<&str>>() - .join(" "); + let collapsed: String = query.split_whitespace().collect::<Vec<&str>>().join(" "); // Step 2: Lowercase known keywords. // We split on whitespace, check each token against the keyword list, // and lowercase it if it matches. Non-keyword tokens keep original case. let keywords = [ // SQL/VQL standard keywords - "SELECT", "WHERE", "FROM", "SEARCH", "LIMIT", "ORDER", "BY", "GROUP", - "AND", "OR", "NOT", "JOIN", "ON", "AS", "HAVING", "INSERT", "UPDATE", - "DELETE", "SET", "INTO", "VALUES", "WITH", "UNION", "INTERSECT", "EXCEPT", - "EXISTS", "BETWEEN", "LIKE", "IN", "IS", "NULL", "TRUE", "FALSE", - "ASC", "DESC", "DISTINCT", "ALL", "ANY", "SOME", "CASE", "WHEN", "THEN", - "ELSE", "END", + "SELECT", + "WHERE", + "FROM", + "SEARCH", + "LIMIT", + "ORDER", + "BY", + "GROUP", + "AND", + "OR", + "NOT", + "JOIN", + "ON", + "AS", + "HAVING", + "INSERT", + "UPDATE", + "DELETE", + "SET", + "INTO", + "VALUES", + "WITH", + "UNION", + "INTERSECT", + "EXCEPT", + "EXISTS", + "BETWEEN", + "LIKE", + "IN", + "IS", + "NULL", + "TRUE", + "FALSE", + "ASC", + "DESC", + "DISTINCT", + "ALL", + "ANY", + "SOME", + "CASE", + "WHEN", + "THEN", + "ELSE", + "END", // VeriSimDB-specific keywords - "PROOF", "VERIFY", "DRIFT", "OCTAD", "MODALITY", "TYPE", + "PROOF", + "VERIFY", + "DRIFT", + "OCTAD", + "MODALITY", + "TYPE", // Modality names (treated as keywords for normalization) - "GRAPH", "VECTOR", "TENSOR", "SEMANTIC", "DOCUMENT", "TEMPORAL", + "GRAPH", + "VECTOR", + "TENSOR", + "SEMANTIC", + "DOCUMENT", + "TEMPORAL", ]; collapsed @@ -732,7 +775,9 @@ mod tests { let cache = PlanCache::new(CacheConfig::default()); let plan = sample_logical_plan(); - let id = cache.prepare("SEARCH graph WHERE type = $t", plan.clone()).await; + let id = cache + .prepare("SEARCH graph WHERE type = $t", plan.clone()) + .await; let stmt = cache.get(&id).await; assert!(stmt.is_some(), "prepared statement should be retrievable"); @@ -761,7 +806,10 @@ mod tests { // Different queries should produce different fingerprints. let fp_other = PlanCache::fingerprint("SEARCH vector WHERE k = 10"); - assert_ne!(fp1, fp_other, "different queries must have different fingerprints"); + assert_ne!( + fp1, fp_other, + "different queries must have different fingerprints" + ); } // -- Test 3: Lookup by query (cache hit) -- @@ -784,7 +832,10 @@ mod tests { let cache = PlanCache::new(CacheConfig::default()); let id = PreparedId::new("nonexistent"); - assert!(cache.get(&id).await.is_none(), "missing ID should return None"); + assert!( + cache.get(&id).await.is_none(), + "missing ID should return None" + ); let found = cache.lookup_by_query("SELECT * FROM nowhere").await; assert!(found.is_none(), "unknown query should return None"); @@ -802,10 +853,16 @@ mod tests { let removed = cache.invalidate(&id).await; assert!(removed, "invalidate should return true for existing entry"); - assert!(cache.get(&id).await.is_none(), "entry should be gone after invalidation"); + assert!( + cache.get(&id).await.is_none(), + "entry should be gone after invalidation" + ); let removed_again = cache.invalidate(&id).await; - assert!(!removed_again, "invalidate on missing entry should return false"); + assert!( + !removed_again, + "invalidate on missing entry should return false" + ); } // -- Test 6: Invalidate all -- @@ -815,7 +872,9 @@ mod tests { let cache = PlanCache::new(CacheConfig::default()); let plan = sample_logical_plan(); - let id1 = cache.prepare("SEARCH graph WHERE type = $t", plan.clone()).await; + let id1 = cache + .prepare("SEARCH graph WHERE type = $t", plan.clone()) + .await; let id2 = cache.prepare("SEARCH vector WHERE k = 5", plan).await; assert!(cache.get(&id1).await.is_some()); @@ -846,7 +905,10 @@ mod tests { assert_eq!(stmt.use_count, 1, "first execute should set use_count to 1"); let stmt2 = cache.execute_prepared(&id, ¶ms).await.unwrap(); - assert_eq!(stmt2.use_count, 2, "second execute should set use_count to 2"); + assert_eq!( + stmt2.use_count, 2, + "second execute should set use_count to 2" + ); assert!( stmt2.last_used >= stmt.last_used, "last_used should advance on execute" @@ -865,7 +927,9 @@ mod tests { let cache = PlanCache::new(config); let plan = sample_logical_plan(); - cache.prepare("SEARCH graph WHERE type = $t", plan.clone()).await; + cache + .prepare("SEARCH graph WHERE type = $t", plan.clone()) + .await; cache.prepare("SEARCH vector WHERE k = 5", plan).await; // Sleep briefly to ensure the TTL check sees them as expired. @@ -890,10 +954,14 @@ mod tests { let plan = sample_logical_plan(); // Prepare two entries (at capacity). - let id1 = cache.prepare("SEARCH graph WHERE type = $t", plan.clone()).await; + let id1 = cache + .prepare("SEARCH graph WHERE type = $t", plan.clone()) + .await; // Brief pause so id1 has older last_used than id2. tokio::time::sleep(tokio::time::Duration::from_millis(5)).await; - let _id2 = cache.prepare("SEARCH vector WHERE k = 5", plan.clone()).await; + let _id2 = cache + .prepare("SEARCH vector WHERE k = 5", plan.clone()) + .await; // Preparing a third should evict the LRU (id1). let _id3 = cache.prepare("SEARCH document WHERE text = $q", plan).await; @@ -914,7 +982,9 @@ mod tests { let cache = PlanCache::new(CacheConfig::default()); let plan = sample_logical_plan(); - let id = cache.prepare("SEARCH graph WHERE type = $t AND name = $n", plan).await; + let id = cache + .prepare("SEARCH graph WHERE type = $t AND name = $n", plan) + .await; // Provide wrong parameter names. let mut params = HashMap::new(); @@ -965,7 +1035,10 @@ mod tests { assert!((stats.hit_ratio - 0.5).abs() < f64::EPSILON); // Generation should have incremented from prepare + any mutations. - assert!(stats.generation > 0, "generation should be non-zero after mutations"); + assert!( + stats.generation > 0, + "generation should be non-zero after mutations" + ); } // -- Test 12: Plan caching on prepared statement -- @@ -1053,7 +1126,7 @@ mod tests { let params = vec![ ParamValue::String("hello".to_string()), ParamValue::Int(42), - ParamValue::Float(3.14), + ParamValue::Float(2.5), ParamValue::Bool(true), ParamValue::Vector(vec![0.1, 0.2, 0.3]), ParamValue::Null, @@ -1074,7 +1147,9 @@ mod tests { let cache = PlanCache::new(CacheConfig::default()); let plan = sample_logical_plan(); - let id1 = cache.prepare("SEARCH graph WHERE type = $t", plan.clone()).await; + let id1 = cache + .prepare("SEARCH graph WHERE type = $t", plan.clone()) + .await; let id2 = cache.prepare("SEARCH graph WHERE type = $t", plan).await; assert_eq!(id1, id2, "same query should produce same PreparedId"); diff --git a/rust-core/verisim-planner/src/profiler.rs b/rust-core/verisim-planner/src/profiler.rs index f9cd4ed..36a2b72 100644 --- a/rust-core/verisim-planner/src/profiler.rs +++ b/rust-core/verisim-planner/src/profiler.rs @@ -7,7 +7,7 @@ //! timings, actual row counts, and estimation accuracy for every step. Results //! are fed back into the [`StatisticsCollector`] via //! [`record_execution`](StatisticsCollector::record_execution) so the -//! [`AdaptiveTuner`] can refine future cost estimates. +//! [`AdaptiveTuner`](crate::AdaptiveTuner) can refine future cost estimates. use std::fmt; @@ -154,8 +154,14 @@ impl QueryProfile { impl fmt::Display for QueryProfile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "QueryProfile(plan={}, steps={}, estimated={:.1}ms, actual={:.1}ms)", - self.plan_id, self.steps.len(), self.total_estimated_ms, self.total_actual_ms) + write!( + f, + "QueryProfile(plan={}, steps={}, estimated={:.1}ms, actual={:.1}ms)", + self.plan_id, + self.steps.len(), + self.total_estimated_ms, + self.total_actual_ms + ) } } @@ -256,7 +262,7 @@ impl Profiler { /// are filled with zero actual values (and will generate accuracy hints). /// /// This method also feeds each step's actual latency and row count into - /// the provided [`StatisticsCollector`] so the [`AdaptiveTuner`] can + /// the provided [`StatisticsCollector`] so the [`AdaptiveTuner`](crate::AdaptiveTuner) can /// refine future estimates. pub fn finish(self, stats: &mut StatisticsCollector) -> QueryProfile { let mut steps: Vec<ProfileStep> = Vec::with_capacity(self.plan.steps.len()); @@ -541,11 +547,19 @@ mod tests { // Step 0 time accuracy: 35 / 40 = 0.875 let ratio_0 = profile.steps[0].time_accuracy_ratio(); - assert!((ratio_0 - 0.875).abs() < 0.001, "Expected 0.875, got {}", ratio_0); + assert!( + (ratio_0 - 0.875).abs() < 0.001, + "Expected 0.875, got {}", + ratio_0 + ); // Step 1 time accuracy: 450 / 225 = 2.0 let ratio_1 = profile.steps[1].time_accuracy_ratio(); - assert!((ratio_1 - 2.0).abs() < 0.001, "Expected 2.0, got {}", ratio_1); + assert!( + (ratio_1 - 2.0).abs() < 0.001, + "Expected 2.0, got {}", + ratio_1 + ); // Row accuracy for step 0: 10 / 10 = 1.0 (perfect) let row_ratio_0 = profile.steps[0].row_accuracy_ratio(); @@ -584,7 +598,10 @@ mod tests { "Expected hints for large estimation errors" ); - let has_slow_hint = profile.optimization_hints.iter().any(|h| h.contains("slower")); + let has_slow_hint = profile + .optimization_hints + .iter() + .any(|h| h.contains("slower")); assert!( has_slow_hint, "Expected a 'slower than estimated' hint, got: {:?}", @@ -614,14 +631,20 @@ mod tests { let profile = profiler.finish(&mut stats); - let has_row_over = profile.optimization_hints.iter().any(|h| h.contains("more rows")); + let has_row_over = profile + .optimization_hints + .iter() + .any(|h| h.contains("more rows")); assert!( has_row_over, "Expected 'more rows than estimated' hint, got: {:?}", profile.optimization_hints ); - let has_row_under = profile.optimization_hints.iter().any(|h| h.contains("fewer rows")); + let has_row_under = profile + .optimization_hints + .iter() + .any(|h| h.contains("fewer rows")); assert!( has_row_under, "Expected 'fewer rows than estimated' hint, got: {:?}", @@ -778,7 +801,7 @@ mod tests { let base = Utc::now(); // Both steps are much faster than estimated - let (s0, e0) = make_timestamps(base, 5.0); // estimated 40ms + let (s0, e0) = make_timestamps(base, 5.0); // estimated 40ms let (s1, e1) = make_timestamps(base, 20.0); // estimated 225ms profiler.record_step(0, 5.0, 10, s0, e0); @@ -786,7 +809,10 @@ mod tests { let profile = profiler.finish(&mut stats); - let has_fast_hint = profile.optimization_hints.iter().any(|h| h.contains("faster")); + let has_fast_hint = profile + .optimization_hints + .iter() + .any(|h| h.contains("faster")); assert!( has_fast_hint, "Expected 'faster than estimated' hint, got: {:?}", diff --git a/rust-core/verisim-planner/src/slow_query.rs b/rust-core/verisim-planner/src/slow_query.rs index 9e0ed68..af995dd 100644 --- a/rust-core/verisim-planner/src/slow_query.rs +++ b/rust-core/verisim-planner/src/slow_query.rs @@ -254,10 +254,7 @@ impl SlowQueryLog { let total = entries.len(); let sum_ms: f64 = entries.iter().map(|e| e.actual_ms).sum(); - let max_ms = entries - .iter() - .map(|e| e.actual_ms) - .fold(0.0_f64, f64::max); + let max_ms = entries.iter().map(|e| e.actual_ms).fold(0.0_f64, f64::max); let min_ms = entries .iter() .map(|e| e.actual_ms) @@ -359,8 +356,12 @@ mod tests { let plan = make_plan(vec![(Modality::Semantic, 50.0)]); let step_times = vec![(Modality::Semantic, 150.0, 5)]; - let was_slow = - log.record(Some("SELECT SEMANTIC FROM OCTAD"), 150.0, &plan, &step_times); + let was_slow = log.record( + Some("SELECT SEMANTIC FROM OCTAD"), + 150.0, + &plan, + &step_times, + ); assert!(was_slow); assert_eq!(log.count(), 1); @@ -412,7 +413,12 @@ mod tests { for i in 0..5 { let step_times = vec![(Modality::Vector, 20.0 + i as f64, 1)]; - log.record(Some(&format!("query-{i}")), 20.0 + i as f64, &plan, &step_times); + log.record( + Some(&format!("query-{i}")), + 20.0 + i as f64, + &plan, + &step_times, + ); } assert_eq!(log.count(), 3); @@ -424,14 +430,8 @@ mod tests { #[test] fn test_bottleneck_detection() { let log = SlowQueryLog::with_defaults(); - let plan = make_plan(vec![ - (Modality::Vector, 30.0), - (Modality::Semantic, 200.0), - ]); - let step_times = vec![ - (Modality::Vector, 25.0, 10), - (Modality::Semantic, 180.0, 5), - ]; + let plan = make_plan(vec![(Modality::Vector, 30.0), (Modality::Semantic, 200.0)]); + let step_times = vec![(Modality::Vector, 25.0, 10), (Modality::Semantic, 180.0, 5)]; log.record(Some("multi-modality query"), 205.0, &plan, &step_times); @@ -545,6 +545,9 @@ mod tests { let entries = log.recent(1); let json = serde_json::to_string(&entries[0]).unwrap(); let parsed: SlowQueryEntry = serde_json::from_str(&json).unwrap(); - assert_eq!(parsed.query_text, Some("SELECT TEMPORAL FROM OCTAD".to_string())); + assert_eq!( + parsed.query_text, + Some("SELECT TEMPORAL FROM OCTAD".to_string()) + ); } } diff --git a/rust-core/verisim-planner/src/stats.rs b/rust-core/verisim-planner/src/stats.rs index ef2bba9..c4b791b 100644 --- a/rust-core/verisim-planner/src/stats.rs +++ b/rust-core/verisim-planner/src/stats.rs @@ -69,13 +69,11 @@ impl StatisticsCollector { /// /// Uses exponential moving average (alpha=0.1) for latency, /// matching the drift detector's approach. - pub fn record_execution( - &mut self, - modality: Modality, - latency_ms: f64, - rows_returned: u64, - ) { - let entry = self.stats.entry(modality).or_insert_with(|| StoreStatistics::new(modality)); + pub fn record_execution(&mut self, modality: Modality, latency_ms: f64, rows_returned: u64) { + let entry = self + .stats + .entry(modality) + .or_insert_with(|| StoreStatistics::new(modality)); entry.query_count += 1; // Exponential moving average (alpha = 0.1) @@ -127,8 +125,8 @@ impl AdaptiveTuner { /// Create a new adaptive tuner with default thresholds. pub fn new() -> Self { Self { - aggressive_threshold: 0.5, // Actual < 50% of estimate → overestimating - conservative_threshold: 2.0, // Actual > 200% of estimate → underestimating + aggressive_threshold: 0.5, // Actual < 50% of estimate → overestimating + conservative_threshold: 2.0, // Actual > 200% of estimate → underestimating min_samples: 10, } } @@ -248,7 +246,9 @@ mod tests { // First execution collector.record_execution(Modality::Graph, 100.0, 50); - assert!((collector.get(Modality::Graph).unwrap().avg_latency_ms - 100.0).abs() < f64::EPSILON); + assert!( + (collector.get(Modality::Graph).unwrap().avg_latency_ms - 100.0).abs() < f64::EPSILON + ); // Second execution — EMA: 0.1 * 200 + 0.9 * 100 = 110 collector.record_execution(Modality::Graph, 200.0, 100); @@ -307,7 +307,7 @@ mod tests { collector.record_execution(Modality::Vector, 10.0, 5); } let config = crate::config::PlannerConfig::default(); - let adjustments = tuner.suggest_adjustments(&collector, &config); + let _adjustments = tuner.suggest_adjustments(&collector, &config); // Vector already has Aggressive override, so if actual confirms it, no change. // But ratio 0.25 < 0.5, already aggressive, stays aggressive → no adjustment. // Let's test Graph instead where it's Conservative @@ -370,8 +370,10 @@ mod tests { for _ in 0..15 { collector.record_execution(Modality::Document, 200.0, 50); } - let mut config = crate::config::PlannerConfig::default(); - config.enable_adaptive = false; + let config = crate::config::PlannerConfig { + enable_adaptive: false, + ..crate::config::PlannerConfig::default() + }; let new_config = tuner.apply(&collector, &config); // Should not change when adaptive is disabled assert_eq!( diff --git a/rust-core/verisim-planner/src/vql_bridge.rs b/rust-core/verisim-planner/src/vql_bridge.rs index 8216e7b..c6b32a6 100644 --- a/rust-core/verisim-planner/src/vql_bridge.rs +++ b/rust-core/verisim-planner/src/vql_bridge.rs @@ -295,10 +295,7 @@ pub enum VqlSimpleCondition { /// Full-text search: `CONTAINS "search text"`. FulltextContains(String), /// Vector similarity: `SIMILAR TO [embedding] THRESHOLD threshold`. - VectorSimilar { - embedding: Vec<f64>, - threshold: f64, - }, + VectorSimilar { embedding: Vec<f64>, threshold: f64 }, /// Graph pattern: `TRAVERSE predicate DEPTH depth`. GraphPattern { predicate: String, @@ -431,9 +428,7 @@ fn parse_simple_condition(value: &serde_json::Value) -> Result<VqlSimpleConditio }) } "ModalityDrift" => { - let mod_val = obj - .get("_0") - .ok_or("ModalityDrift missing _0 (modality)")?; + let mod_val = obj.get("_0").ok_or("ModalityDrift missing _0 (modality)")?; let modality: VqlModality = serde_json::from_value(mod_val.clone()).map_err(|e| e.to_string())?; let threshold = obj @@ -695,10 +690,7 @@ impl VqlAst { .map(|&m| PlanNode { modality: m, conditions: condition_map.remove(&m).unwrap_or_default(), - projections: projection_map - .get(&m) - .cloned() - .unwrap_or_default(), + projections: projection_map.get(&m).cloned().unwrap_or_default(), early_limit: None, }) .collect(); @@ -753,7 +745,9 @@ impl VqlAst { if let Some(skip) = query.offset { if skip > 0 { // Find existing Limit and adjust, or add one. - let has_limit = post_processing.iter().any(|p| matches!(p, PostProcessing::Limit { .. })); + let has_limit = post_processing + .iter() + .any(|p| matches!(p, PostProcessing::Limit { .. })); if !has_limit { // No limit — add a large synthetic limit so offset is meaningful. post_processing.push(PostProcessing::Limit { @@ -773,11 +767,7 @@ impl VqlAst { if let Some(ref projs) = query.projections { let columns: Vec<String> = projs .iter() - .map(|p| { - p.alias - .clone() - .unwrap_or_else(|| field_ref_name(&p.field)) - }) + .map(|p| p.alias.clone().unwrap_or_else(|| field_ref_name(&p.field))) .collect(); if !columns.is_empty() { post_processing.push(PostProcessing::Project { columns }); @@ -876,17 +866,17 @@ fn flatten_conditions( describe_condition(rhs) ); for &m in active_modalities { - out.entry(m) - .or_default() - .push(ConditionKind::Predicate { expression: desc.clone() }); + out.entry(m).or_default().push(ConditionKind::Predicate { + expression: desc.clone(), + }); } } VqlCondition::Not(inner) => { let desc = format!("NOT({})", describe_condition(inner)); for &m in active_modalities { - out.entry(m) - .or_default() - .push(ConditionKind::Predicate { expression: desc.clone() }); + out.entry(m).or_default().push(ConditionKind::Predicate { + expression: desc.clone(), + }); } } VqlCondition::Simple(simple) => { @@ -900,9 +890,9 @@ fn flatten_conditions( // Add it as a predicate on all active modalities. let desc = format!("target_modality={m}: {condition_kind:?}"); for &am in active_modalities { - out.entry(am) - .or_default() - .push(ConditionKind::Predicate { expression: desc.clone() }); + out.entry(am).or_default().push(ConditionKind::Predicate { + expression: desc.clone(), + }); } } None => { @@ -942,9 +932,7 @@ fn map_simple_condition( // that carries the embedding, but we work with existing types. Ok(( Some(Modality::Vector), - ConditionKind::Similarity { - k: embedding.len(), - }, + ConditionKind::Similarity { k: embedding.len() }, )) } VqlSimpleCondition::GraphPattern { predicate, depth } => Ok(( @@ -959,7 +947,10 @@ fn map_simple_condition( operator, value, } => { - let target = field.modality.as_ref().and_then(|vm| vql_modality_to_planner(vm).ok()); + let target = field + .modality + .as_ref() + .and_then(|vm| vql_modality_to_planner(vm).ok()); let field_name = field.field.clone(); let value_str = match value { serde_json::Value::String(s) => s.clone(), @@ -1006,10 +997,7 @@ fn map_simple_condition( .unwrap_or_else(|| "?".to_string()), right.field, ); - Ok(( - None, - ConditionKind::Predicate { expression: desc }, - )) + Ok((None, ConditionKind::Predicate { expression: desc })) } VqlSimpleCondition::ModalityDrift { modality, @@ -1017,26 +1005,17 @@ fn map_simple_condition( } => { let m = vql_modality_to_planner(modality)?; let desc = format!("drift({m}) > {threshold}"); - Ok(( - Some(m), - ConditionKind::Predicate { expression: desc }, - )) + Ok((Some(m), ConditionKind::Predicate { expression: desc })) } VqlSimpleCondition::ModalityExists(modality) => { let m = vql_modality_to_planner(modality)?; let desc = format!("exists({m})"); - Ok(( - Some(m), - ConditionKind::Predicate { expression: desc }, - )) + Ok((Some(m), ConditionKind::Predicate { expression: desc })) } VqlSimpleCondition::ModalityNotExists(modality) => { let m = vql_modality_to_planner(modality)?; let desc = format!("not_exists({m})"); - Ok(( - Some(m), - ConditionKind::Predicate { expression: desc }, - )) + Ok((Some(m), ConditionKind::Predicate { expression: desc })) } VqlSimpleCondition::ModalityConsistency { modalities, @@ -1048,10 +1027,7 @@ fn map_simple_condition( .map(|m| m.to_string()) .collect(); let desc = format!("consistency({}) > {threshold}", names.join(", ")); - Ok(( - None, - ConditionKind::Predicate { expression: desc }, - )) + Ok((None, ConditionKind::Predicate { expression: desc })) } } } @@ -1167,7 +1143,10 @@ mod tests { assert_eq!(plan.nodes[1].conditions.len(), 0); // Post-processing: Limit. - assert!(plan.post_processing.iter().any(|p| matches!(p, PostProcessing::Limit { count: 10 }))); + assert!(plan + .post_processing + .iter() + .any(|p| matches!(p, PostProcessing::Limit { count: 10 }))); } #[test] @@ -1425,7 +1404,10 @@ mod tests { .conditions .iter() .any(|c| matches!(c, ConditionKind::Similarity { .. })); - assert!(has_similarity, "vector node should have similarity condition"); + assert!( + has_similarity, + "vector node should have similarity condition" + ); // Graph node should NOT have similarity (it targets Vector). let graph_node = plan @@ -1437,7 +1419,10 @@ mod tests { .conditions .iter() .any(|c| matches!(c, ConditionKind::Similarity { .. })); - assert!(!has_similarity, "graph node should not have similarity condition"); + assert!( + !has_similarity, + "graph node should not have similarity condition" + ); // Proof spec should create a Semantic ProofVerification condition. // Semantic is not in active modalities (only Graph, Vector), but the @@ -1518,7 +1503,9 @@ mod tests { let plan = parse_and_plan(json).expect("should parse Store source"); assert!(matches!( plan.source, - QuerySource::Store { modality: Modality::Vector } + QuerySource::Store { + modality: Modality::Vector + } )); } diff --git a/rust-core/verisim-provenance/src/lib.rs b/rust-core/verisim-provenance/src/lib.rs index 7701d96..0209c88 100644 --- a/rust-core/verisim-provenance/src/lib.rs +++ b/rust-core/verisim-provenance/src/lib.rs @@ -19,10 +19,10 @@ #![forbid(unsafe_code)] #[cfg(feature = "redb-backend")] pub mod persistent; -#[cfg(feature = "redb-backend")] -pub use persistent::*; use async_trait::async_trait; use chrono::{DateTime, Utc}; +#[cfg(feature = "redb-backend")] +pub use persistent::*; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::HashMap; @@ -41,17 +41,11 @@ pub enum ProvenanceError { /// Hash chain integrity violation — records have been tampered with or /// a parent_hash does not match the SHA-256 of the preceding record #[error("Provenance chain corrupted for entity {entity}: {reason}")] - ChainCorrupted { - entity: String, - reason: String, - }, + ChainCorrupted { entity: String, reason: String }, /// A record's computed hash does not match its stored content_hash #[error("Hash mismatch at index {index} for entity {entity}")] - HashMismatch { - entity: String, - index: usize, - }, + HashMismatch { entity: String, index: usize }, /// Generic I/O or storage error #[error("Provenance I/O error: {0}")] @@ -327,13 +321,22 @@ pub trait ProvenanceStore: Send + Sync { async fn verify_chain(&self, entity_id: &str) -> Result<bool, ProvenanceError>; /// Get the origin (first) record for an entity. - async fn get_origin(&self, entity_id: &str) -> Result<Option<ProvenanceRecord>, ProvenanceError>; + async fn get_origin( + &self, + entity_id: &str, + ) -> Result<Option<ProvenanceRecord>, ProvenanceError>; /// Get the latest (most recent) record for an entity. - async fn get_latest(&self, entity_id: &str) -> Result<Option<ProvenanceRecord>, ProvenanceError>; + async fn get_latest( + &self, + entity_id: &str, + ) -> Result<Option<ProvenanceRecord>, ProvenanceError>; /// Search for provenance records by actor across all entities. - async fn search_by_actor(&self, actor: &str) -> Result<Vec<(String, ProvenanceRecord)>, ProvenanceError>; + async fn search_by_actor( + &self, + actor: &str, + ) -> Result<Vec<(String, ProvenanceRecord)>, ProvenanceError>; /// Delete the provenance chain for an entity (for testing / admin use). async fn delete_chain(&self, entity_id: &str) -> Result<(), ProvenanceError>; @@ -410,17 +413,26 @@ impl ProvenanceStore for InMemoryProvenanceStore { } } - async fn get_origin(&self, entity_id: &str) -> Result<Option<ProvenanceRecord>, ProvenanceError> { + async fn get_origin( + &self, + entity_id: &str, + ) -> Result<Option<ProvenanceRecord>, ProvenanceError> { let chains = self.chains.read().await; Ok(chains.get(entity_id).and_then(|c| c.origin().cloned())) } - async fn get_latest(&self, entity_id: &str) -> Result<Option<ProvenanceRecord>, ProvenanceError> { + async fn get_latest( + &self, + entity_id: &str, + ) -> Result<Option<ProvenanceRecord>, ProvenanceError> { let chains = self.chains.read().await; Ok(chains.get(entity_id).and_then(|c| c.latest().cloned())) } - async fn search_by_actor(&self, actor: &str) -> Result<Vec<(String, ProvenanceRecord)>, ProvenanceError> { + async fn search_by_actor( + &self, + actor: &str, + ) -> Result<Vec<(String, ProvenanceRecord)>, ProvenanceError> { let chains = self.chains.read().await; let mut results = Vec::new(); for (entity_id, chain) in chains.iter() { @@ -473,7 +485,12 @@ mod tests { #[test] fn test_provenance_chain_integrity() { let mut chain = ProvenanceChain::new("entity-1"); - chain.append(ProvenanceEventType::Created, "alice", None, "Created entity"); + chain.append( + ProvenanceEventType::Created, + "alice", + None, + "Created entity", + ); chain.append(ProvenanceEventType::Modified, "bob", None, "Updated title"); chain.append( ProvenanceEventType::Normalized, @@ -578,15 +595,33 @@ mod tests { let store = InMemoryProvenanceStore::new(); store - .record_event("e1", ProvenanceEventType::Created, "alice", None, "Created e1") + .record_event( + "e1", + ProvenanceEventType::Created, + "alice", + None, + "Created e1", + ) .await .unwrap(); store - .record_event("e2", ProvenanceEventType::Created, "bob", None, "Created e2") + .record_event( + "e2", + ProvenanceEventType::Created, + "bob", + None, + "Created e2", + ) .await .unwrap(); store - .record_event("e3", ProvenanceEventType::Imported, "alice", None, "Imported e3") + .record_event( + "e3", + ProvenanceEventType::Imported, + "alice", + None, + "Imported e3", + ) .await .unwrap(); diff --git a/rust-core/verisim-provenance/src/persistent.rs b/rust-core/verisim-provenance/src/persistent.rs index f172be0..0609112 100644 --- a/rust-core/verisim-provenance/src/persistent.rs +++ b/rust-core/verisim-provenance/src/persistent.rs @@ -234,11 +234,23 @@ mod tests { let store = RedbProvenanceStore::open(&path).await.unwrap(); store - .record_event("e1", ProvenanceEventType::Created, "alice", None, "Created e1") + .record_event( + "e1", + ProvenanceEventType::Created, + "alice", + None, + "Created e1", + ) .await .unwrap(); store - .record_event("e2", ProvenanceEventType::Created, "bob", None, "Created e2") + .record_event( + "e2", + ProvenanceEventType::Created, + "bob", + None, + "Created e2", + ) .await .unwrap(); store diff --git a/rust-core/verisim-repl/src/completer.rs b/rust-core/verisim-repl/src/completer.rs index 1ba680a..9bac730 100644 --- a/rust-core/verisim-repl/src/completer.rs +++ b/rust-core/verisim-repl/src/completer.rs @@ -13,27 +13,85 @@ use rustyline::Context; /// All completable VQL keywords. const KEYWORDS: &[&str] = &[ - "SELECT", "FROM", "WHERE", "PROOF", "LIMIT", "OFFSET", "ORDER", "BY", - "GROUP", "HAVING", "AS", "AND", "OR", "NOT", "IN", "BETWEEN", "LIKE", - "EXISTS", "CONTAINS", "SIMILAR", "TO", "TRAVERSE", "DEPTH", "THRESHOLD", - "DRIFT", "CONSISTENCY", "AT", "TIME", "EXPLAIN", "INSERT", "UPDATE", - "DELETE", "SET", "INTO", "VALUES", "CREATE", "DROP", "ALTER", "JOIN", - "ON", "WITH", "FEDERATION", "STORE", "OCTAD", "ALL", "ASC", "DESC", - "COUNT", "SUM", "AVG", "MIN", "MAX", "DISTINCT", + "SELECT", + "FROM", + "WHERE", + "PROOF", + "LIMIT", + "OFFSET", + "ORDER", + "BY", + "GROUP", + "HAVING", + "AS", + "AND", + "OR", + "NOT", + "IN", + "BETWEEN", + "LIKE", + "EXISTS", + "CONTAINS", + "SIMILAR", + "TO", + "TRAVERSE", + "DEPTH", + "THRESHOLD", + "DRIFT", + "CONSISTENCY", + "AT", + "TIME", + "EXPLAIN", + "INSERT", + "UPDATE", + "DELETE", + "SET", + "INTO", + "VALUES", + "CREATE", + "DROP", + "ALTER", + "JOIN", + "ON", + "WITH", + "FEDERATION", + "STORE", + "OCTAD", + "ALL", + "ASC", + "DESC", + "COUNT", + "SUM", + "AVG", + "MIN", + "MAX", + "DISTINCT", ]; /// Modality names (also offered as completions). /// All 8 octad modalities: Graph, Vector, Tensor, Semantic, Document, Temporal, /// Provenance, Spatial. const MODALITIES: &[&str] = &[ - "GRAPH", "VECTOR", "TENSOR", "SEMANTIC", "DOCUMENT", "TEMPORAL", - "PROVENANCE", "SPATIAL", + "GRAPH", + "VECTOR", + "TENSOR", + "SEMANTIC", + "DOCUMENT", + "TEMPORAL", + "PROVENANCE", + "SPATIAL", ]; /// Meta-commands starting with backslash. const META_COMMANDS: &[&str] = &[ - "\\connect", "\\explain", "\\timing", "\\format", "\\status", - "\\help", "\\quit", "\\q", + "\\connect", + "\\explain", + "\\timing", + "\\format", + "\\status", + "\\help", + "\\quit", + "\\q", ]; /// Tab-completer for VQL input. @@ -78,7 +136,9 @@ impl Completer for VqlCompleter { // Determine the user's casing preference: if the prefix is all // uppercase, offer uppercase completions; otherwise lowercase. - let use_upper = prefix.chars().all(|c| c.is_uppercase() || !c.is_alphabetic()); + let use_upper = prefix + .chars() + .all(|c| c.is_uppercase() || !c.is_alphabetic()); // Modality names. for name in MODALITIES { diff --git a/rust-core/verisim-repl/src/formatter.rs b/rust-core/verisim-repl/src/formatter.rs index 2d2b6db..7f448e6 100644 --- a/rust-core/verisim-repl/src/formatter.rs +++ b/rust-core/verisim-repl/src/formatter.rs @@ -105,7 +105,7 @@ fn format_array_table(rows: &[Value]) -> String { let mut table = Table::new(); table.set_content_arrangement(ContentArrangement::Dynamic); - table.set_header(columns.iter().map(|c| Cell::new(c))); + table.set_header(columns.iter().map(Cell::new)); for row in rows { let cells: Vec<Cell> = columns @@ -119,7 +119,10 @@ fn format_array_table(rows: &[Value]) -> String { } let row_count = rows.len(); - format!("{table}\n({row_count} row{})", if row_count == 1 { "" } else { "s" }) + format!( + "{table}\n({row_count} row{})", + if row_count == 1 { "" } else { "s" } + ) } /// Render a single JSON object as a two-column table (Field | Value). @@ -245,7 +248,10 @@ mod tests { #[test] fn test_output_format_parse() { - assert_eq!("table".parse::<OutputFormat>().unwrap(), OutputFormat::Table); + assert_eq!( + "table".parse::<OutputFormat>().unwrap(), + OutputFormat::Table + ); assert_eq!("JSON".parse::<OutputFormat>().unwrap(), OutputFormat::Json); assert_eq!("csv".parse::<OutputFormat>().unwrap(), OutputFormat::Csv); assert!("xml".parse::<OutputFormat>().is_err()); diff --git a/rust-core/verisim-repl/src/highlighter.rs b/rust-core/verisim-repl/src/highlighter.rs index 905d5dc..d3dc7cc 100644 --- a/rust-core/verisim-repl/src/highlighter.rs +++ b/rust-core/verisim-repl/src/highlighter.rs @@ -12,21 +12,73 @@ use std::borrow::Cow; /// VQL keywords that are highlighted in blue/bold. const VQL_KEYWORDS: &[&str] = &[ - "SELECT", "FROM", "WHERE", "PROOF", "LIMIT", "OFFSET", "ORDER", "BY", - "GROUP", "HAVING", "AS", "AND", "OR", "NOT", "IN", "BETWEEN", "LIKE", - "EXISTS", "CONTAINS", "SIMILAR", "TO", "TRAVERSE", "DEPTH", "THRESHOLD", - "DRIFT", "CONSISTENCY", "AT", "TIME", "EXPLAIN", "INSERT", "UPDATE", - "DELETE", "SET", "INTO", "VALUES", "CREATE", "DROP", "ALTER", "JOIN", - "ON", "WITH", "FEDERATION", "STORE", "OCTAD", "ALL", "ASC", "DESC", - "COUNT", "SUM", "AVG", "MIN", "MAX", "DISTINCT", + "SELECT", + "FROM", + "WHERE", + "PROOF", + "LIMIT", + "OFFSET", + "ORDER", + "BY", + "GROUP", + "HAVING", + "AS", + "AND", + "OR", + "NOT", + "IN", + "BETWEEN", + "LIKE", + "EXISTS", + "CONTAINS", + "SIMILAR", + "TO", + "TRAVERSE", + "DEPTH", + "THRESHOLD", + "DRIFT", + "CONSISTENCY", + "AT", + "TIME", + "EXPLAIN", + "INSERT", + "UPDATE", + "DELETE", + "SET", + "INTO", + "VALUES", + "CREATE", + "DROP", + "ALTER", + "JOIN", + "ON", + "WITH", + "FEDERATION", + "STORE", + "OCTAD", + "ALL", + "ASC", + "DESC", + "COUNT", + "SUM", + "AVG", + "MIN", + "MAX", + "DISTINCT", ]; /// VQL modality names highlighted in green. /// All 8 octad modalities: Graph, Vector, Tensor, Semantic, Document, Temporal, /// Provenance, Spatial. const VQL_MODALITIES: &[&str] = &[ - "GRAPH", "VECTOR", "TENSOR", "SEMANTIC", "DOCUMENT", "TEMPORAL", - "PROVENANCE", "SPATIAL", + "GRAPH", + "VECTOR", + "TENSOR", + "SEMANTIC", + "DOCUMENT", + "TEMPORAL", + "PROVENANCE", + "SPATIAL", ]; /// Syntax highlighter for VQL input lines. @@ -50,7 +102,12 @@ impl Highlighter for VqlHighlighter { } /// Indicate that we always want to repaint when the line changes. - fn highlight_char(&self, _line: &str, _pos: usize, _forced: rustyline::highlight::CmdKind) -> bool { + fn highlight_char( + &self, + _line: &str, + _pos: usize, + _forced: rustyline::highlight::CmdKind, + ) -> bool { true } diff --git a/rust-core/verisim-repl/src/linter.rs b/rust-core/verisim-repl/src/linter.rs index 103ecfa..77acdae 100644 --- a/rust-core/verisim-repl/src/linter.rs +++ b/rust-core/verisim-repl/src/linter.rs @@ -87,16 +87,30 @@ impl LintRule { pub fn description(&self) -> &'static str { match self { LintRule::MissingLimit => "Query lacks LIMIT clause — may return unbounded results", - LintRule::SelectAllModalities => "Query selects all 8 modalities — consider selecting only what you need", - LintRule::MissingProof => "Semantic modality accessed without PROOF clause — data integrity not verified", - LintRule::UnboundedTraverse => "TRAVERSE without DEPTH limit — may explore entire graph", - LintRule::MissingThreshold => "DRIFT/CONSISTENCY check without THRESHOLD — using implicit default", - LintRule::OrderByWithoutLimit => "ORDER BY without LIMIT — sorting potentially unbounded result set", + LintRule::SelectAllModalities => { + "Query selects all 8 modalities — consider selecting only what you need" + } + LintRule::MissingProof => { + "Semantic modality accessed without PROOF clause — data integrity not verified" + } + LintRule::UnboundedTraverse => { + "TRAVERSE without DEPTH limit — may explore entire graph" + } + LintRule::MissingThreshold => { + "DRIFT/CONSISTENCY check without THRESHOLD — using implicit default" + } + LintRule::OrderByWithoutLimit => { + "ORDER BY without LIMIT — sorting potentially unbounded result set" + } LintRule::DangerousWrite => "DELETE/UPDATE without WHERE clause — affects all entities", LintRule::UnknownProofType => "Unrecognized proof type in PROOF clause", LintRule::RedundantWhere => "WHERE clause appears redundant (always true condition)", - LintRule::MultiModalityNoExplain => "Multi-modality query — consider running EXPLAIN first to review the plan", - LintRule::FederationWithoutStore => "FEDERATION query without STORE — will query all federated instances", + LintRule::MultiModalityNoExplain => { + "Multi-modality query — consider running EXPLAIN first to review the plan" + } + LintRule::FederationWithoutStore => { + "FEDERATION query without STORE — will query all federated instances" + } } } } @@ -119,14 +133,27 @@ impl fmt::Display for LintDiagnostic { /// VQL modality names — all 8 octad modalities. const MODALITIES: &[&str] = &[ - "GRAPH", "VECTOR", "TENSOR", "SEMANTIC", "DOCUMENT", "TEMPORAL", - "PROVENANCE", "SPATIAL", + "GRAPH", + "VECTOR", + "TENSOR", + "SEMANTIC", + "DOCUMENT", + "TEMPORAL", + "PROVENANCE", + "SPATIAL", ]; /// Known VQL-DT proof types. const KNOWN_PROOF_TYPES: &[&str] = &[ - "EXISTENCE", "CONSISTENCY", "INTEGRITY", "AUTHENTICITY", - "PROVENANCE", "ACCESS", "CITATION", "ZKP", "PLONK", + "EXISTENCE", + "CONSISTENCY", + "INTEGRITY", + "AUTHENTICITY", + "PROVENANCE", + "ACCESS", + "CITATION", + "ZKP", + "PLONK", ]; /// Lint a VQL query string and return diagnostics. @@ -167,10 +194,7 @@ pub fn lint_query(query: &str) -> Vec<LintDiagnostic> { // VQL002: SELECT all modalities if is_select { - let modality_count = MODALITIES - .iter() - .filter(|m| tokens.contains(m)) - .count(); + let modality_count = MODALITIES.iter().filter(|m| tokens.contains(m)).count(); if modality_count >= 8 { diagnostics.push(LintDiagnostic { rule: LintRule::SelectAllModalities, @@ -207,7 +231,10 @@ pub fn lint_query(query: &str) -> Vec<LintDiagnostic> { rule: LintRule::MissingThreshold, severity: Severity::Hint, message: LintRule::MissingThreshold.description().to_string(), - offset: upper.find("DRIFT").or_else(|| upper.find("CONSISTENCY")).unwrap_or(0), + offset: upper + .find("DRIFT") + .or_else(|| upper.find("CONSISTENCY")) + .unwrap_or(0), }); } @@ -270,10 +297,7 @@ pub fn lint_query(query: &str) -> Vec<LintDiagnostic> { // VQL010: Multi-modality without EXPLAIN if is_select && !is_explain { - let modality_count = MODALITIES - .iter() - .filter(|m| tokens.contains(m)) - .count(); + let modality_count = MODALITIES.iter().filter(|m| tokens.contains(m)).count(); if modality_count >= 3 { diagnostics.push(LintDiagnostic { rule: LintRule::MultiModalityNoExplain, @@ -313,9 +337,18 @@ pub fn format_diagnostics(query: &str, diagnostics: &[LintDiagnostic]) -> String } let mut output = String::new(); - let error_count = diagnostics.iter().filter(|d| d.severity == Severity::Error).count(); - let warning_count = diagnostics.iter().filter(|d| d.severity == Severity::Warning).count(); - let hint_count = diagnostics.iter().filter(|d| d.severity == Severity::Hint).count(); + let error_count = diagnostics + .iter() + .filter(|d| d.severity == Severity::Error) + .count(); + let warning_count = diagnostics + .iter() + .filter(|d| d.severity == Severity::Warning) + .count(); + let hint_count = diagnostics + .iter() + .filter(|d| d.severity == Severity::Hint) + .count(); for diag in diagnostics { output.push_str(&format!(" {} {}\n", diag.rule, diag.message)); @@ -348,7 +381,10 @@ mod tests { #[test] fn test_clean_query_no_errors() { let diagnostics = lint_query("SELECT GRAPH FROM OCTAD WHERE id = 'abc' LIMIT 10"); - let errors: Vec<_> = diagnostics.iter().filter(|d| d.severity == Severity::Error).collect(); + let errors: Vec<_> = diagnostics + .iter() + .filter(|d| d.severity == Severity::Error) + .collect(); assert!(errors.is_empty()); } @@ -369,7 +405,9 @@ mod tests { let diagnostics = lint_query( "SELECT GRAPH VECTOR TENSOR SEMANTIC DOCUMENT TEMPORAL PROVENANCE SPATIAL FROM OCTAD LIMIT 10" ); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::SelectAllModalities)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::SelectAllModalities)); } #[test] @@ -387,45 +425,60 @@ mod tests { #[test] fn test_unbounded_traverse() { let diagnostics = lint_query("SELECT GRAPH FROM OCTAD TRAVERSE relates_to LIMIT 10"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::UnboundedTraverse)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::UnboundedTraverse)); assert!(diagnostics.iter().any(|d| d.severity == Severity::Error)); } #[test] fn test_traverse_with_depth_ok() { - let diagnostics = lint_query("SELECT GRAPH FROM OCTAD TRAVERSE relates_to DEPTH 3 LIMIT 10"); - assert!(!diagnostics.iter().any(|d| d.rule == LintRule::UnboundedTraverse)); + let diagnostics = + lint_query("SELECT GRAPH FROM OCTAD TRAVERSE relates_to DEPTH 3 LIMIT 10"); + assert!(!diagnostics + .iter() + .any(|d| d.rule == LintRule::UnboundedTraverse)); } #[test] fn test_drift_without_threshold() { let diagnostics = lint_query("SELECT GRAPH FROM OCTAD WHERE DRIFT LIMIT 10"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::MissingThreshold)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::MissingThreshold)); } #[test] fn test_order_without_limit() { let diagnostics = lint_query("SELECT GRAPH FROM OCTAD ORDER BY name"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::OrderByWithoutLimit)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::OrderByWithoutLimit)); } #[test] fn test_dangerous_delete() { let diagnostics = lint_query("DELETE FROM OCTAD"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::DangerousWrite)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::DangerousWrite)); assert!(diagnostics.iter().any(|d| d.severity == Severity::Error)); } #[test] fn test_delete_with_where_ok() { let diagnostics = lint_query("DELETE FROM OCTAD WHERE id = 'abc'"); - assert!(!diagnostics.iter().any(|d| d.rule == LintRule::DangerousWrite)); + assert!(!diagnostics + .iter() + .any(|d| d.rule == LintRule::DangerousWrite)); } #[test] fn test_unknown_proof_type() { let diagnostics = lint_query("SELECT SEMANTIC FROM OCTAD PROOF FOOBAR LIMIT 10"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::UnknownProofType)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::UnknownProofType)); } #[test] @@ -434,7 +487,9 @@ mod tests { let q = format!("SELECT SEMANTIC FROM OCTAD PROOF {} LIMIT 10", proof_type); let diagnostics = lint_query(&q); assert!( - !diagnostics.iter().any(|d| d.rule == LintRule::UnknownProofType), + !diagnostics + .iter() + .any(|d| d.rule == LintRule::UnknownProofType), "Proof type {} should be recognized", proof_type ); @@ -444,27 +499,34 @@ mod tests { #[test] fn test_redundant_where() { let diagnostics = lint_query("SELECT GRAPH FROM OCTAD WHERE 1=1 LIMIT 10"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::RedundantWhere)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::RedundantWhere)); } #[test] fn test_multi_modality_hint() { - let diagnostics = lint_query( - "SELECT GRAPH VECTOR TENSOR FROM OCTAD LIMIT 10" - ); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::MultiModalityNoExplain)); + let diagnostics = lint_query("SELECT GRAPH VECTOR TENSOR FROM OCTAD LIMIT 10"); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::MultiModalityNoExplain)); } #[test] fn test_federation_without_store() { let diagnostics = lint_query("SELECT GRAPH FROM FEDERATION OCTAD LIMIT 10"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::FederationWithoutStore)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::FederationWithoutStore)); } #[test] fn test_federation_with_store_ok() { - let diagnostics = lint_query("SELECT GRAPH FROM FEDERATION STORE 'remote-1' OCTAD LIMIT 10"); - assert!(!diagnostics.iter().any(|d| d.rule == LintRule::FederationWithoutStore)); + let diagnostics = + lint_query("SELECT GRAPH FROM FEDERATION STORE 'remote-1' OCTAD LIMIT 10"); + assert!(!diagnostics + .iter() + .any(|d| d.rule == LintRule::FederationWithoutStore)); } #[test] @@ -479,13 +541,14 @@ mod tests { #[test] fn test_diagnostics_sorted_by_severity() { - let diagnostics = lint_query( - "DELETE FROM FEDERATION OCTAD" - ); + let diagnostics = lint_query("DELETE FROM FEDERATION OCTAD"); // Errors should come before warnings let severities: Vec<_> = diagnostics.iter().map(|d| d.severity).collect(); for window in severities.windows(2) { - assert!(window[0] >= window[1], "Diagnostics should be sorted by severity"); + assert!( + window[0] >= window[1], + "Diagnostics should be sorted by severity" + ); } } @@ -512,6 +575,8 @@ mod tests { #[test] fn test_update_without_where() { let diagnostics = lint_query("UPDATE OCTAD SET name = 'test'"); - assert!(diagnostics.iter().any(|d| d.rule == LintRule::DangerousWrite)); + assert!(diagnostics + .iter() + .any(|d| d.rule == LintRule::DangerousWrite)); } } diff --git a/rust-core/verisim-repl/src/main.rs b/rust-core/verisim-repl/src/main.rs index b6bc8ef..4324c8d 100644 --- a/rust-core/verisim-repl/src/main.rs +++ b/rust-core/verisim-repl/src/main.rs @@ -175,8 +175,7 @@ fn main() { // Multiline continuation: if the line ends with '\', append // the line (minus the backslash) and continue reading. - if trimmed.ends_with('\\') { - let without_continuation = &trimmed[..trimmed.len() - 1]; + if let Some(without_continuation) = trimmed.strip_suffix('\\') { if !query_buf.is_empty() { query_buf.push(' '); } @@ -437,25 +436,10 @@ fn history_file_path() -> std::path::PathBuf { /// Print the welcome banner. fn print_banner(session: &Session) { println!(); - println!( - "{}", - " VeriSimDB VQL REPL".bright_cyan().bold() - ); - println!( - " {} {}", - "Version:".dimmed(), - VERSION - ); - println!( - " {} {}", - "Server: ".dimmed(), - session.client.base_url() - ); - println!( - " {} {}", - "Format: ".dimmed(), - session.format - ); + println!("{}", " VeriSimDB VQL REPL".bright_cyan().bold()); + println!(" {} {}", "Version:".dimmed(), VERSION); + println!(" {} {}", "Server: ".dimmed(), session.client.base_url()); + println!(" {} {}", "Format: ".dimmed(), session.format); println!(); println!( " Type {} for help, {} to exit.", @@ -471,39 +455,32 @@ fn print_help() { println!("{}", " VQL Meta-Commands".bright_cyan().bold()); println!(); println!( - " {} {}", - "\\connect <host:port>".bright_yellow(), - "Change server connection" + " {} Change server connection", + "\\connect <host:port>".bright_yellow() ); println!( - " {} {}", - "\\explain <query> ".bright_yellow(), - "Show EXPLAIN output for a query" + " {} Show EXPLAIN output for a query", + "\\explain <query> ".bright_yellow() ); println!( - " {} {}", - "\\timing ".bright_yellow(), - "Toggle query timing display" + " {} Toggle query timing display", + "\\timing ".bright_yellow() ); println!( - " {} {}", - "\\format <fmt> ".bright_yellow(), - "Set output format (table|json|csv)" + " {} Set output format (table|json|csv)", + "\\format <fmt> ".bright_yellow() ); println!( - " {} {}", - "\\status ".bright_yellow(), - "Show server health status" + " {} Show server health status", + "\\status ".bright_yellow() ); println!( - " {} {}", - "\\help ".bright_yellow(), - "Show this help message" + " {} Show this help message", + "\\help ".bright_yellow() ); println!( - " {} {}", - "\\quit / \\q ".bright_yellow(), - "Exit the REPL" + " {} Exit the REPL", + "\\quit / \\q ".bright_yellow() ); println!(); println!("{}", " Query Input".bright_cyan().bold()); diff --git a/rust-core/verisim-repl/src/vql_fmt.rs b/rust-core/verisim-repl/src/vql_fmt.rs index 41c98d6..9fb6d96 100644 --- a/rust-core/verisim-repl/src/vql_fmt.rs +++ b/rust-core/verisim-repl/src/vql_fmt.rs @@ -11,28 +11,80 @@ /// VQL keywords that should be uppercased. const VQL_KEYWORDS: &[&str] = &[ - "SELECT", "FROM", "WHERE", "PROOF", "LIMIT", "OFFSET", "ORDER", "BY", - "GROUP", "HAVING", "AS", "AND", "OR", "NOT", "IN", "BETWEEN", "LIKE", - "EXISTS", "CONTAINS", "SIMILAR", "TO", "TRAVERSE", "DEPTH", "THRESHOLD", - "DRIFT", "CONSISTENCY", "AT", "TIME", "EXPLAIN", "INSERT", "UPDATE", - "DELETE", "SET", "INTO", "VALUES", "CREATE", "DROP", "ALTER", "JOIN", - "ON", "WITH", "FEDERATION", "STORE", "OCTAD", "ALL", "ASC", "DESC", - "COUNT", "SUM", "AVG", "MIN", "MAX", "DISTINCT", "ANALYZE", + "SELECT", + "FROM", + "WHERE", + "PROOF", + "LIMIT", + "OFFSET", + "ORDER", + "BY", + "GROUP", + "HAVING", + "AS", + "AND", + "OR", + "NOT", + "IN", + "BETWEEN", + "LIKE", + "EXISTS", + "CONTAINS", + "SIMILAR", + "TO", + "TRAVERSE", + "DEPTH", + "THRESHOLD", + "DRIFT", + "CONSISTENCY", + "AT", + "TIME", + "EXPLAIN", + "INSERT", + "UPDATE", + "DELETE", + "SET", + "INTO", + "VALUES", + "CREATE", + "DROP", + "ALTER", + "JOIN", + "ON", + "WITH", + "FEDERATION", + "STORE", + "OCTAD", + "ALL", + "ASC", + "DESC", + "COUNT", + "SUM", + "AVG", + "MIN", + "MAX", + "DISTINCT", + "ANALYZE", ]; /// VQL modality names that should be uppercased. /// All 8 octad modalities: Graph, Vector, Tensor, Semantic, Document, Temporal, /// Provenance, Spatial. const VQL_MODALITIES: &[&str] = &[ - "GRAPH", "VECTOR", "TENSOR", "SEMANTIC", "DOCUMENT", "TEMPORAL", - "PROVENANCE", "SPATIAL", + "GRAPH", + "VECTOR", + "TENSOR", + "SEMANTIC", + "DOCUMENT", + "TEMPORAL", + "PROVENANCE", + "SPATIAL", ]; /// Keywords that start a new major clause (indented on a new line). const CLAUSE_STARTERS: &[&str] = &[ - "SELECT", "FROM", "WHERE", "ORDER", "GROUP", "HAVING", "LIMIT", - "OFFSET", "JOIN", "ON", "WITH", "SET", "INTO", "VALUES", - "TRAVERSE", "PROOF", "EXPLAIN", + "SELECT", "FROM", "WHERE", "ORDER", "GROUP", "HAVING", "LIMIT", "OFFSET", "JOIN", "ON", "WITH", + "SET", "INTO", "VALUES", "TRAVERSE", "PROOF", "EXPLAIN", ]; /// Format a VQL query string into canonical form. diff --git a/rust-core/verisim-semantic/src/circuit_compiler.rs b/rust-core/verisim-semantic/src/circuit_compiler.rs index c602607..cfcbade 100644 --- a/rust-core/verisim-semantic/src/circuit_compiler.rs +++ b/rust-core/verisim-semantic/src/circuit_compiler.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use super::circuit_registry::{ - CircuitError, CircuitIR, CompiledCircuit, GateType, R1CSConstraint, sha256_hex, + sha256_hex, CircuitError, CircuitIR, CompiledCircuit, GateType, R1CSConstraint, }; /// A wire in the circuit definition (from the DSL) @@ -69,12 +69,9 @@ pub fn compile_circuit(def: &CircuitDef) -> Result<CompiledCircuit, CircuitError let mut constraints = Vec::new(); for gate in &def.gates { - let output_idx = wire_map - .get(&gate.output) - .copied() - .ok_or_else(|| { - CircuitError::CompilationFailed(format!("Unknown output wire: {}", gate.output)) - })?; + let output_idx = wire_map.get(&gate.output).copied().ok_or_else(|| { + CircuitError::CompilationFailed(format!("Unknown output wire: {}", gate.output)) + })?; match gate.gate_type { GateType::And => { @@ -179,8 +176,8 @@ pub fn compile_circuit(def: &CircuitDef) -> Result<CompiledCircuit, CircuitError }; // Compute circuit hash for integrity - let circuit_bytes = serde_json::to_vec(&ir) - .map_err(|e| CircuitError::CompilationFailed(e.to_string()))?; + let circuit_bytes = + serde_json::to_vec(&ir).map_err(|e| CircuitError::CompilationFailed(e.to_string()))?; let hash = sha256_hex(&circuit_bytes); // Generate a verification key (Merkle commitment of constraints) @@ -228,9 +225,21 @@ mod tests { let def = CircuitDef { name: "test-multiply".to_string(), wires: vec![ - WireDef { name: "x".into(), is_public: true, is_output: false }, - WireDef { name: "y".into(), is_public: false, is_output: false }, - WireDef { name: "z".into(), is_public: false, is_output: true }, + WireDef { + name: "x".into(), + is_public: true, + is_output: false, + }, + WireDef { + name: "y".into(), + is_public: false, + is_output: false, + }, + WireDef { + name: "z".into(), + is_public: false, + is_output: true, + }, ], gates: vec![GateDef { gate_type: GateType::And, // multiplication for R1CS @@ -252,9 +261,21 @@ mod tests { let def = CircuitDef { name: "mul-check".to_string(), wires: vec![ - WireDef { name: "a".into(), is_public: true, is_output: false }, - WireDef { name: "b".into(), is_public: true, is_output: false }, - WireDef { name: "c".into(), is_public: false, is_output: false }, + WireDef { + name: "a".into(), + is_public: true, + is_output: false, + }, + WireDef { + name: "b".into(), + is_public: true, + is_output: false, + }, + WireDef { + name: "c".into(), + is_public: false, + is_output: false, + }, ], gates: vec![GateDef { gate_type: GateType::And, @@ -279,9 +300,11 @@ mod tests { fn test_unknown_wire_error() { let def = CircuitDef { name: "bad".to_string(), - wires: vec![ - WireDef { name: "a".into(), is_public: true, is_output: false }, - ], + wires: vec![WireDef { + name: "a".into(), + is_public: true, + is_output: false, + }], gates: vec![GateDef { gate_type: GateType::And, inputs: vec!["a".into(), "nonexistent".into()], diff --git a/rust-core/verisim-semantic/src/circuit_registry.rs b/rust-core/verisim-semantic/src/circuit_registry.rs index afd0461..be8def8 100644 --- a/rust-core/verisim-semantic/src/circuit_registry.rs +++ b/rust-core/verisim-semantic/src/circuit_registry.rs @@ -93,11 +93,7 @@ pub struct CompiledCircuit { impl CompiledCircuit { /// Verify a witness against this circuit's constraints - pub fn verify( - &self, - witness: &[f64], - public_inputs: &[f64], - ) -> Result<bool, CircuitError> { + pub fn verify(&self, witness: &[f64], public_inputs: &[f64]) -> Result<bool, CircuitError> { if public_inputs.len() != self.ir.num_public_inputs { return Err(CircuitError::InvalidWitness(format!( "Expected {} public inputs, got {}", @@ -163,7 +159,10 @@ impl CircuitRegistry { name: &str, circuit: CompiledCircuit, ) -> Result<(), CircuitError> { - let mut circuits = self.circuits.write().map_err(|_| CircuitError::LockPoisoned)?; + let mut circuits = self + .circuits + .write() + .map_err(|_| CircuitError::LockPoisoned)?; if circuits.contains_key(name) { return Err(CircuitError::AlreadyExists(name.to_string())); @@ -175,7 +174,10 @@ impl CircuitRegistry { /// Get a circuit by name pub fn get_circuit(&self, name: &str) -> Result<Option<CompiledCircuit>, CircuitError> { - let circuits = self.circuits.read().map_err(|_| CircuitError::LockPoisoned)?; + let circuits = self + .circuits + .read() + .map_err(|_| CircuitError::LockPoisoned)?; Ok(circuits.get(name).cloned()) } @@ -186,7 +188,10 @@ impl CircuitRegistry { witness: &[f64], public_inputs: &[f64], ) -> Result<bool, CircuitError> { - let circuits = self.circuits.read().map_err(|_| CircuitError::LockPoisoned)?; + let circuits = self + .circuits + .read() + .map_err(|_| CircuitError::LockPoisoned)?; let circuit = circuits .get(name) @@ -197,13 +202,19 @@ impl CircuitRegistry { /// List all registered circuit names pub fn list_circuits(&self) -> Result<Vec<String>, CircuitError> { - let circuits = self.circuits.read().map_err(|_| CircuitError::LockPoisoned)?; + let circuits = self + .circuits + .read() + .map_err(|_| CircuitError::LockPoisoned)?; Ok(circuits.keys().cloned().collect()) } /// Remove a circuit pub fn unregister_circuit(&self, name: &str) -> Result<bool, CircuitError> { - let mut circuits = self.circuits.write().map_err(|_| CircuitError::LockPoisoned)?; + let mut circuits = self + .circuits + .write() + .map_err(|_| CircuitError::LockPoisoned)?; Ok(circuits.remove(name).is_some()) } } @@ -234,21 +245,18 @@ mod tests { // Wire 2: y (witness) // Constraint: wire0 * wire2 = wire1 (x * y = z) let constraint = R1CSConstraint { - a: HashMap::from([(0, 1.0)]), // A = x - b: HashMap::from([(2, 1.0)]), // B = y (witness) - c: HashMap::from([(1, 1.0)]), // C = z + a: HashMap::from([(0, 1.0)]), // A = x + b: HashMap::from([(2, 1.0)]), // B = y (witness) + c: HashMap::from([(1, 1.0)]), // C = z }; let ir = CircuitIR { name: "multiply".to_string(), - num_public_inputs: 2, // x and z - num_witness_wires: 1, // y + num_public_inputs: 2, // x and z + num_witness_wires: 1, // y num_wires: 3, constraints: vec![constraint], - parameter_map: HashMap::from([ - ("x".to_string(), 0), - ("z".to_string(), 1), - ]), + parameter_map: HashMap::from([("x".to_string(), 0), ("z".to_string(), 1)]), }; let circuit_bytes = serde_json::to_vec(&ir).unwrap(); @@ -271,13 +279,17 @@ mod tests { // x=3, z=12 → y must be 4 (3 * 4 = 12) // Assignment: [x=3, z=12, y=4] → 3 * 4 = 12 ✓ let public_inputs = &[3.0, 12.0]; // x, z - let witness = &[4.0]; // y + let witness = &[4.0]; // y - let valid = registry.verify_with_circuit("multiply", witness, public_inputs).unwrap(); + let valid = registry + .verify_with_circuit("multiply", witness, public_inputs) + .unwrap(); assert!(valid); // Wrong witness: 3 * 5 = 15 ≠ 12 - let invalid = registry.verify_with_circuit("multiply", &[5.0], public_inputs).unwrap(); + let invalid = registry + .verify_with_circuit("multiply", &[5.0], public_inputs) + .unwrap(); assert!(!invalid); } @@ -293,7 +305,9 @@ mod tests { let registry = CircuitRegistry::new(); let circuit = make_test_circuit(); - registry.register_circuit("multiply", circuit.clone()).unwrap(); + registry + .register_circuit("multiply", circuit.clone()) + .unwrap(); let result = registry.register_circuit("multiply", circuit); assert!(matches!(result, Err(CircuitError::AlreadyExists(_)))); } diff --git a/rust-core/verisim-semantic/src/lib.rs b/rust-core/verisim-semantic/src/lib.rs index 7d004c1..efbe80e 100644 --- a/rust-core/verisim-semantic/src/lib.rs +++ b/rust-core/verisim-semantic/src/lib.rs @@ -9,13 +9,13 @@ pub mod persistent; #[cfg(feature = "redb-backend")] pub use persistent::*; -pub mod zkp; -pub mod zkp_bridge; +pub mod circuit_compiler; +pub mod circuit_registry; pub mod proven_bridge; pub mod sanctify_bridge; -pub mod circuit_registry; -pub mod circuit_compiler; pub mod verification_keys; +pub mod zkp; +pub mod zkp_bridge; use async_trait::async_trait; use serde::{Deserialize, Serialize}; @@ -98,7 +98,11 @@ pub enum ConstraintKind { /// Property must match pattern Pattern { property: String, regex: String }, /// Property must be in range - Range { property: String, min: Option<i64>, max: Option<i64> }, + Range { + property: String, + min: Option<i64>, + max: Option<i64>, + }, /// Custom validation (reference to validator function) Custom(String), } @@ -200,8 +204,7 @@ impl ProofBlob { /// Deserialize from CBOR pub fn from_cbor(data: &[u8]) -> Result<Self, SemanticError> { - ciborium::from_reader(data) - .map_err(|e| SemanticError::SerializationError(e.to_string())) + ciborium::from_reader(data).map_err(|e| SemanticError::SerializationError(e.to_string())) } /// Verify the proof data against the claim. @@ -248,10 +251,14 @@ pub trait SemanticStore: Send + Sync { async fn annotate(&self, annotation: &SemanticAnnotation) -> Result<(), SemanticError>; /// Get annotations for an entity - async fn get_annotations(&self, entity_id: &str) -> Result<Option<SemanticAnnotation>, SemanticError>; + async fn get_annotations( + &self, + entity_id: &str, + ) -> Result<Option<SemanticAnnotation>, SemanticError>; /// Validate an annotation against type constraints - async fn validate(&self, annotation: &SemanticAnnotation) -> Result<Vec<String>, SemanticError>; + async fn validate(&self, annotation: &SemanticAnnotation) + -> Result<Vec<String>, SemanticError>; /// Store a proof blob async fn store_proof(&self, proof: &ProofBlob) -> Result<(), SemanticError>; @@ -263,7 +270,10 @@ pub trait SemanticStore: Send + Sync { async fn verify_proofs(&self, claim: &str) -> Result<(usize, usize), SemanticError> { let proofs = self.get_proofs(claim).await?; let total = proofs.len(); - let valid = proofs.iter().filter(|p| p.verify().unwrap_or(false)).count(); + let valid = proofs + .iter() + .filter(|p| p.verify().unwrap_or(false)) + .count(); Ok((valid, total)) } } @@ -294,12 +304,20 @@ impl Default for InMemorySemanticStore { #[async_trait] impl SemanticStore for InMemorySemanticStore { async fn register_type(&self, typ: &SemanticType) -> Result<(), SemanticError> { - self.types.write().map_err(|_| SemanticError::LockPoisoned)?.insert(typ.iri.clone(), typ.clone()); + self.types + .write() + .map_err(|_| SemanticError::LockPoisoned)? + .insert(typ.iri.clone(), typ.clone()); Ok(()) } async fn get_type(&self, iri: &str) -> Result<Option<SemanticType>, SemanticError> { - Ok(self.types.read().map_err(|_| SemanticError::LockPoisoned)?.get(iri).cloned()) + Ok(self + .types + .read() + .map_err(|_| SemanticError::LockPoisoned)? + .get(iri) + .cloned()) } async fn annotate(&self, annotation: &SemanticAnnotation) -> Result<(), SemanticError> { @@ -308,15 +326,29 @@ impl SemanticStore for InMemorySemanticStore { if !violations.is_empty() { return Err(SemanticError::ConstraintViolation(violations.join("; "))); } - self.annotations.write().map_err(|_| SemanticError::LockPoisoned)?.insert(annotation.entity_id.clone(), annotation.clone()); + self.annotations + .write() + .map_err(|_| SemanticError::LockPoisoned)? + .insert(annotation.entity_id.clone(), annotation.clone()); Ok(()) } - async fn get_annotations(&self, entity_id: &str) -> Result<Option<SemanticAnnotation>, SemanticError> { - Ok(self.annotations.read().map_err(|_| SemanticError::LockPoisoned)?.get(entity_id).cloned()) + async fn get_annotations( + &self, + entity_id: &str, + ) -> Result<Option<SemanticAnnotation>, SemanticError> { + Ok(self + .annotations + .read() + .map_err(|_| SemanticError::LockPoisoned)? + .get(entity_id) + .cloned()) } - async fn validate(&self, annotation: &SemanticAnnotation) -> Result<Vec<String>, SemanticError> { + async fn validate( + &self, + annotation: &SemanticAnnotation, + ) -> Result<Vec<String>, SemanticError> { let types = self.types.read().map_err(|_| SemanticError::LockPoisoned)?; let mut violations = Vec::new(); @@ -326,15 +358,21 @@ impl SemanticStore for InMemorySemanticStore { match &constraint.kind { ConstraintKind::Required(prop) => { if !annotation.properties.contains_key(prop) { - violations.push(format!("{}: {}", constraint.name, constraint.message)); + violations + .push(format!("{}: {}", constraint.name, constraint.message)); } } ConstraintKind::Pattern { property, regex } => { - if let Some(SemanticValue::TypedLiteral { value, .. }) = annotation.properties.get(property) { + if let Some(SemanticValue::TypedLiteral { value, .. }) = + annotation.properties.get(property) + { let re = regex::Regex::new(regex).ok(); if let Some(re) = re { if !re.is_match(value) { - violations.push(format!("{}: {}", constraint.name, constraint.message)); + violations.push(format!( + "{}: {}", + constraint.name, constraint.message + )); } } } @@ -349,7 +387,9 @@ impl SemanticStore for InMemorySemanticStore { } async fn store_proof(&self, proof: &ProofBlob) -> Result<(), SemanticError> { - self.proofs.write().map_err(|_| SemanticError::LockPoisoned)? + self.proofs + .write() + .map_err(|_| SemanticError::LockPoisoned)? .entry(proof.claim.clone()) .or_default() .push(proof.clone()); @@ -357,7 +397,13 @@ impl SemanticStore for InMemorySemanticStore { } async fn get_proofs(&self, claim: &str) -> Result<Vec<ProofBlob>, SemanticError> { - Ok(self.proofs.read().map_err(|_| SemanticError::LockPoisoned)?.get(claim).cloned().unwrap_or_default()) + Ok(self + .proofs + .read() + .map_err(|_| SemanticError::LockPoisoned)? + .get(claim) + .cloned() + .unwrap_or_default()) } } diff --git a/rust-core/verisim-semantic/src/persistent.rs b/rust-core/verisim-semantic/src/persistent.rs index 72ff3d2..aba4820 100644 --- a/rust-core/verisim-semantic/src/persistent.rs +++ b/rust-core/verisim-semantic/src/persistent.rs @@ -179,10 +179,8 @@ impl SemanticStore for RedbSemanticStore { match &constraint.kind { ConstraintKind::Required(prop) => { if !annotation.properties.contains_key(prop) { - violations.push(format!( - "{}: {}", - constraint.name, constraint.message - )); + violations + .push(format!("{}: {}", constraint.name, constraint.message)); } } ConstraintKind::Pattern { property, regex } => { @@ -255,14 +253,12 @@ mod tests { { let store = RedbSemanticStore::open(&path).await.unwrap(); - let person_type = - SemanticType::new("https://example.org/Person", "Person").with_constraint( - Constraint { - name: "name_required".to_string(), - kind: ConstraintKind::Required("name".to_string()), - message: "Person must have a name".to_string(), - }, - ); + let person_type = SemanticType::new("https://example.org/Person", "Person") + .with_constraint(Constraint { + name: "name_required".to_string(), + kind: ConstraintKind::Required("name".to_string()), + message: "Person must have a name".to_string(), + }); store.register_type(&person_type).await.unwrap(); let mut properties = HashMap::new(); @@ -293,10 +289,7 @@ mod tests { { let store = RedbSemanticStore::open(&path).await.unwrap(); - let typ = store - .get_type("https://example.org/Person") - .await - .unwrap(); + let typ = store.get_type("https://example.org/Person").await.unwrap(); assert!(typ.is_some()); assert_eq!(typ.unwrap().label, "Person"); diff --git a/rust-core/verisim-semantic/src/proven_bridge.rs b/rust-core/verisim-semantic/src/proven_bridge.rs index f1d4cd1..74d37f2 100644 --- a/rust-core/verisim-semantic/src/proven_bridge.rs +++ b/rust-core/verisim-semantic/src/proven_bridge.rs @@ -112,14 +112,19 @@ impl ProvenCertificate { /// Parse a proven certificate from JSON bytes. pub fn parse_proven_certificate(bytes: &[u8]) -> Result<ProvenCertificate, SemanticError> { - serde_json::from_slice(bytes) - .map_err(|e| SemanticError::SerializationError(format!("Failed to parse proven certificate: {}", e))) + serde_json::from_slice(bytes).map_err(|e| { + SemanticError::SerializationError(format!("Failed to parse proven certificate: {}", e)) + }) } /// Parse a proven certificate from CBOR bytes. pub fn parse_proven_certificate_cbor(bytes: &[u8]) -> Result<ProvenCertificate, SemanticError> { - ciborium::from_reader(bytes) - .map_err(|e| SemanticError::SerializationError(format!("Failed to parse proven certificate (CBOR): {}", e))) + ciborium::from_reader(bytes).map_err(|e| { + SemanticError::SerializationError(format!( + "Failed to parse proven certificate (CBOR): {}", + e + )) + }) } /// Verify the structural integrity of a proven certificate. @@ -157,8 +162,8 @@ pub fn verify_proven_certificate(cert: &ProvenCertificate) -> Result<bool, Seman /// Convert a proven certificate into a VeriSimDB ProofBlob for storage /// in the semantic modality. pub fn certificate_to_proof_blob(cert: &ProvenCertificate) -> Result<ProofBlob, SemanticError> { - let data = serde_json::to_vec(cert) - .map_err(|e| SemanticError::SerializationError(e.to_string()))?; + let data = + serde_json::to_vec(cert).map_err(|e| SemanticError::SerializationError(e.to_string()))?; Ok(ProofBlob { claim: cert.statement.clone(), @@ -178,11 +183,8 @@ pub fn create_certificate( valid: bool, message: Option<String>, ) -> ProvenCertificate { - let signature = ProvenCertificate::compute_signature( - &prover, - &statement, - proof_term.as_deref(), - ); + let signature = + ProvenCertificate::compute_signature(&prover, &statement, proof_term.as_deref()); ProvenCertificate { version: "1.0".to_string(), @@ -237,7 +239,8 @@ mod tests { #[test] fn test_verify_invalid_signature() { let mut cert = sample_certificate(); - cert.signature = "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_string(); + cert.signature = + "sha256:0000000000000000000000000000000000000000000000000000000000000000".to_string(); assert!(!verify_proven_certificate(&cert).unwrap()); } @@ -272,6 +275,9 @@ mod tests { fn test_prover_kind_display() { assert_eq!(ProverKind::Z3.to_string(), "z3"); assert_eq!(ProverKind::Lean.to_string(), "lean"); - assert_eq!(ProverKind::Custom("myprover".to_string()).to_string(), "custom:myprover"); + assert_eq!( + ProverKind::Custom("myprover".to_string()).to_string(), + "custom:myprover" + ); } } diff --git a/rust-core/verisim-semantic/src/sanctify_bridge.rs b/rust-core/verisim-semantic/src/sanctify_bridge.rs index de63400..8ba44ed 100644 --- a/rust-core/verisim-semantic/src/sanctify_bridge.rs +++ b/rust-core/verisim-semantic/src/sanctify_bridge.rs @@ -168,8 +168,9 @@ pub struct SanctifyContract { /// Parse a sanctify report from JSON bytes. pub fn parse_sanctify_report(bytes: &[u8]) -> Result<SanctifyReport, SemanticError> { - serde_json::from_slice(bytes) - .map_err(|e| SemanticError::SerializationError(format!("Failed to parse sanctify report: {}", e))) + serde_json::from_slice(bytes).map_err(|e| { + SemanticError::SerializationError(format!("Failed to parse sanctify report: {}", e)) + }) } /// Validate a sanctify contract's structural integrity. @@ -180,10 +181,14 @@ pub fn parse_sanctify_report(bytes: &[u8]) -> Result<SanctifyReport, SemanticErr /// - Resolution flags are consistent with issue counts pub fn validate_contract(contract: &SanctifyContract) -> Result<bool, SemanticError> { if contract.contract_id.is_empty() { - return Err(SemanticError::ConstraintViolation("Contract ID must not be empty".to_string())); + return Err(SemanticError::ConstraintViolation( + "Contract ID must not be empty".to_string(), + )); } if contract.octad_id.is_empty() { - return Err(SemanticError::ConstraintViolation("Octad ID must not be empty".to_string())); + return Err(SemanticError::ConstraintViolation( + "Octad ID must not be empty".to_string(), + )); } // Verify summary is consistent @@ -260,7 +265,9 @@ mod tests { column: Some(15), description: "Unsanitized user input in SQL query".to_string(), remedy: "Use prepared statements with PDO".to_string(), - code: Some("$query = \"SELECT * FROM users WHERE id = \" . $_GET['id']".to_string()), + code: Some( + "$query = \"SELECT * FROM users WHERE id = \" . $_GET['id']".to_string(), + ), }, SecurityIssue { issue_type: IssueType::CrossSiteScripting, @@ -318,11 +325,8 @@ mod tests { #[test] fn test_bind_contract() { let report = sample_report(); - let contract = bind_contract_to_octad( - "audit-001".to_string(), - "octad-abc".to_string(), - report, - ); + let contract = + bind_contract_to_octad("audit-001".to_string(), "octad-abc".to_string(), report); assert!(!contract.all_critical_resolved); // Has 1 critical assert!(!contract.all_high_resolved); // Has 1 high assert_eq!(contract.report.issues.len(), 3); @@ -331,11 +335,8 @@ mod tests { #[test] fn test_validate_contract() { let report = sample_report(); - let contract = bind_contract_to_octad( - "audit-001".to_string(), - "octad-abc".to_string(), - report, - ); + let contract = + bind_contract_to_octad("audit-001".to_string(), "octad-abc".to_string(), report); // Valid contract (resolution flags match issue counts) assert!(validate_contract(&contract).unwrap()); } @@ -372,11 +373,8 @@ mod tests { #[test] fn test_contract_to_proof_blob() { let report = sample_report(); - let contract = bind_contract_to_octad( - "audit-003".to_string(), - "octad-xyz".to_string(), - report, - ); + let contract = + bind_contract_to_octad("audit-003".to_string(), "octad-xyz".to_string(), report); let blob = contract_to_proof_blob(&contract).unwrap(); assert!(blob.claim.contains("security-audit:audit-003")); assert!(blob.claim.contains("octad:octad-xyz")); @@ -401,11 +399,8 @@ mod tests { issues: vec![], summary: IssueSummary::default(), }; - let contract = bind_contract_to_octad( - "clean-001".to_string(), - "octad-clean".to_string(), - report, - ); + let contract = + bind_contract_to_octad("clean-001".to_string(), "octad-clean".to_string(), report); assert!(contract.all_critical_resolved); assert!(contract.all_high_resolved); assert!(validate_contract(&contract).unwrap()); diff --git a/rust-core/verisim-semantic/src/verification_keys.rs b/rust-core/verisim-semantic/src/verification_keys.rs index b742476..e3ae35e 100644 --- a/rust-core/verisim-semantic/src/verification_keys.rs +++ b/rust-core/verisim-semantic/src/verification_keys.rs @@ -54,11 +54,7 @@ impl VerificationKeyEntry { /// Check if a given key matches the active or previous key pub fn matches(&self, key: &[u8]) -> bool { - self.active_key == key - || self - .previous_key - .as_ref() - .is_some_and(|prev| prev == key) + self.active_key == key || self.previous_key.as_ref().is_some_and(|prev| prev == key) } } @@ -104,11 +100,7 @@ impl VerificationKeyStore { } /// Store a verification key for a circuit - pub fn store_key( - &self, - circuit_name: &str, - key: Vec<u8>, - ) -> Result<(), CircuitError> { + pub fn store_key(&self, circuit_name: &str, key: Vec<u8>) -> Result<(), CircuitError> { let mut keys = self.keys.write().map_err(|_| CircuitError::LockPoisoned)?; if let Some(entry) = keys.get_mut(circuit_name) { diff --git a/rust-core/verisim-semantic/src/zkp.rs b/rust-core/verisim-semantic/src/zkp.rs index cf6934b..14ead53 100644 --- a/rust-core/verisim-semantic/src/zkp.rs +++ b/rust-core/verisim-semantic/src/zkp.rs @@ -89,7 +89,7 @@ pub fn merkle_root(leaves: &[Vec<u8>]) -> [u8; 32] { // Pad to even number if necessary while current_level.len() > 1 { - if current_level.len() % 2 != 0 { + if !current_level.len().is_multiple_of(2) { let last = *current_level.last().unwrap(); current_level.push(last); } @@ -117,14 +117,18 @@ pub fn merkle_proof(leaves: &[Vec<u8>], index: usize) -> Option<MerkleProof> { while hashed.len() > 1 { // Pad to even - if hashed.len() % 2 != 0 { + if !hashed.len().is_multiple_of(2) { let last = *hashed.last().unwrap(); hashed.push(last); } // Find sibling - let sibling_idx = if idx % 2 == 0 { idx + 1 } else { idx - 1 }; - let is_left = idx % 2 != 0; // sibling is on left if we're on the right + let sibling_idx = if idx.is_multiple_of(2) { + idx + 1 + } else { + idx - 1 + }; + let is_left = !idx.is_multiple_of(2); // sibling is on left if we're on the right path.push(MerklePathElement { hash: hashed[sibling_idx], @@ -170,9 +174,7 @@ pub fn verify_merkle_proof(proof: &MerkleProof) -> bool { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum VerifiableProofData { /// Hash commitment: prover committed to a value. - Commitment { - commitment: [u8; 32], - }, + Commitment { commitment: [u8; 32] }, /// Hash reveal: prover reveals the secret for a prior commitment. Reveal { commitment: [u8; 32], @@ -181,9 +183,7 @@ pub enum VerifiableProofData { /// Merkle inclusion: value is a member of a committed set. MerkleInclusion(MerkleProof), /// Content integrity: SHA-256 hash of the original content. - ContentIntegrity { - content_hash: [u8; 32], - }, + ContentIntegrity { content_hash: [u8; 32] }, } /// Verify a verifiable proof against its claim. @@ -193,10 +193,7 @@ pub fn verify_proof(data: &VerifiableProofData, claim: &[u8]) -> bool { // Commitments are valid by construction — they're verified at reveal time. true } - VerifiableProofData::Reveal { - commitment, - secret, - } => { + VerifiableProofData::Reveal { commitment, secret } => { let expected = commit(claim, secret); constant_time_eq(commitment, &expected.commitment) } diff --git a/rust-core/verisim-semantic/src/zkp_bridge.rs b/rust-core/verisim-semantic/src/zkp_bridge.rs index e2a5a80..c6b0a9d 100644 --- a/rust-core/verisim-semantic/src/zkp_bridge.rs +++ b/rust-core/verisim-semantic/src/zkp_bridge.rs @@ -32,14 +32,14 @@ use serde::{Deserialize, Serialize}; use super::circuit_registry::{CircuitError, CircuitRegistry}; use super::zkp::{ - commit, hash, merkle_proof, merkle_root, verify_merkle_proof, - verify_proof, VerifiableProofData, + commit, hash, merkle_proof, merkle_root, verify_merkle_proof, verify_proof, VerifiableProofData, }; /// Privacy level for proof generation -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum PrivacyLevel { /// Data and proof are both visible to the verifier. + #[default] Public, /// Data is hidden behind a hash commitment; proof proves /// the committer knows the value without revealing it. @@ -50,12 +50,6 @@ pub enum PrivacyLevel { ZeroKnowledge, } -impl Default for PrivacyLevel { - fn default() -> Self { - Self::Public - } -} - impl std::fmt::Display for PrivacyLevel { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -159,9 +153,7 @@ pub fn generate_zkp_with_circuit( let satisfied = registry.verify_with_circuit(circuit_name, witness, public_inputs)?; let circuit = registry.get_circuit(circuit_name)?; - let constraints_checked = circuit - .map(|c| c.ir.constraints.len()) - .unwrap_or(0); + let constraints_checked = circuit.map(|c| c.ir.constraints.len()).unwrap_or(0); proof.circuit_result = Some(CircuitVerificationResult { circuit_name: circuit_name.clone(), @@ -205,29 +197,28 @@ fn generate_private_proof(request: &ZkpProofRequest) -> Result<ZkpProof, Circuit let commitment = commit(&request.claim, &nonce); // If a membership set is provided, generate a Merkle inclusion proof - let (proof_data, root) = if let (Some(ref set), Some(index)) = - (&request.membership_set, request.membership_index) - { - let root = merkle_root(set); - match merkle_proof(set, index) { - Some(mp) => (VerifiableProofData::MerkleInclusion(mp), Some(root)), - None => { - return Err(CircuitError::InvalidWitness(format!( - "Membership index {} out of bounds for set of size {}", - index, - set.len() - ))); + let (proof_data, root) = + if let (Some(ref set), Some(index)) = (&request.membership_set, request.membership_index) { + let root = merkle_root(set); + match merkle_proof(set, index) { + Some(mp) => (VerifiableProofData::MerkleInclusion(mp), Some(root)), + None => { + return Err(CircuitError::InvalidWitness(format!( + "Membership index {} out of bounds for set of size {}", + index, + set.len() + ))); + } } - } - } else { - // No membership set: commitment-only proof - ( - VerifiableProofData::Commitment { - commitment: commitment.commitment, - }, - None, - ) - }; + } else { + // No membership set: commitment-only proof + ( + VerifiableProofData::Commitment { + commitment: commitment.commitment, + }, + None, + ) + }; Ok(ZkpProof { privacy_level: PrivacyLevel::Private, @@ -253,40 +244,39 @@ fn generate_zk_proof(request: &ZkpProofRequest) -> Result<ZkpProof, CircuitError // Build a blinded membership set: // Each leaf is H(original_leaf || shared_nonce) so the verifier // cannot recover the original leaves, only verify structure. - let (proof_data, root) = if let (Some(ref set), Some(index)) = - (&request.membership_set, request.membership_index) - { - // Blind all leaves - let blinded_leaves: Vec<Vec<u8>> = set - .iter() - .map(|leaf| { - let blinded = commit(leaf, &nonce); - blinded.commitment.to_vec() - }) - .collect(); - - let root = merkle_root(&blinded_leaves); - match merkle_proof(&blinded_leaves, index) { - Some(mp) => (VerifiableProofData::MerkleInclusion(mp), Some(root)), - None => { - return Err(CircuitError::InvalidWitness(format!( - "Membership index {} out of bounds for set of size {}", - index, - set.len() - ))); + let (proof_data, root) = + if let (Some(ref set), Some(index)) = (&request.membership_set, request.membership_index) { + // Blind all leaves + let blinded_leaves: Vec<Vec<u8>> = set + .iter() + .map(|leaf| { + let blinded = commit(leaf, &nonce); + blinded.commitment.to_vec() + }) + .collect(); + + let root = merkle_root(&blinded_leaves); + match merkle_proof(&blinded_leaves, index) { + Some(mp) => (VerifiableProofData::MerkleInclusion(mp), Some(root)), + None => { + return Err(CircuitError::InvalidWitness(format!( + "Membership index {} out of bounds for set of size {}", + index, + set.len() + ))); + } } - } - } else { - // No membership set: commitment with reveal proof - // The verifier can check H(claim || nonce) == commitment - // without seeing the claim (they only see the commitment) - ( - VerifiableProofData::Commitment { - commitment: commitment.commitment, - }, - None, - ) - }; + } else { + // No membership set: commitment with reveal proof + // The verifier can check H(claim || nonce) == commitment + // without seeing the claim (they only see the commitment) + ( + VerifiableProofData::Commitment { + commitment: commitment.commitment, + }, + None, + ) + }; Ok(ZkpProof { privacy_level: PrivacyLevel::ZeroKnowledge, @@ -353,7 +343,7 @@ fn generate_nonce(claim: &[u8]) -> Vec<u8> { let separator = b"verisimdb-zkp-nonce-v1"; let mut hasher = sha2::Sha256::new(); use sha2::Digest; - hasher.update(&h); + hasher.update(h); hasher.update(separator); h = hasher.finalize().into(); h.to_vec() @@ -516,12 +506,7 @@ mod tests { #[test] fn test_zk_proof_blinded_root_differs_from_plain() { - let claims = vec![ - b"a".to_vec(), - b"b".to_vec(), - b"c".to_vec(), - b"d".to_vec(), - ]; + let claims = vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec(), b"d".to_vec()]; // Private proof: plain Merkle root let private_req = ZkpProofRequest { @@ -572,7 +557,7 @@ mod tests { #[test] fn test_generate_with_circuit_registry() { use super::super::circuit_registry::{ - CircuitIR, CompiledCircuit, R1CSConstraint, sha256_hex, + sha256_hex, CircuitIR, CompiledCircuit, R1CSConstraint, }; use std::collections::HashMap; @@ -604,7 +589,7 @@ mod tests { claim: b"verified-computation".to_vec(), privacy_level: PrivacyLevel::Public, circuit_name: Some("test-mul".to_string()), - witness: Some(vec![4.0]), // y = 4 + witness: Some(vec![4.0]), // y = 4 public_inputs: Some(vec![3.0, 12.0]), // x = 3, z = 12 membership_set: None, membership_index: None, diff --git a/rust-core/verisim-spatial/src/lib.rs b/rust-core/verisim-spatial/src/lib.rs index fc931bb..73df6db 100644 --- a/rust-core/verisim-spatial/src/lib.rs +++ b/rust-core/verisim-spatial/src/lib.rs @@ -20,9 +20,9 @@ #![forbid(unsafe_code)] #[cfg(feature = "redb-backend")] pub mod persistent; +use async_trait::async_trait; #[cfg(feature = "redb-backend")] pub use persistent::*; -use async_trait::async_trait; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; @@ -151,7 +151,11 @@ pub struct SpatialData { impl SpatialData { /// Create spatial data for a point with default WGS84 SRID. - pub fn point(latitude: f64, longitude: f64, altitude: Option<f64>) -> Result<Self, SpatialError> { + pub fn point( + latitude: f64, + longitude: f64, + altitude: Option<f64>, + ) -> Result<Self, SpatialError> { Ok(Self { coordinates: Coordinates::new(latitude, longitude, altitude)?, geometry_type: GeometryType::Point, @@ -161,11 +165,7 @@ impl SpatialData { } /// Create spatial data with a specified geometry type and SRID. - pub fn with_geometry( - coordinates: Coordinates, - geometry_type: GeometryType, - srid: u32, - ) -> Self { + pub fn with_geometry(coordinates: Coordinates, geometry_type: GeometryType, srid: u32) -> Self { Self { coordinates, geometry_type, @@ -256,8 +256,7 @@ pub fn haversine_distance(a: &Coordinates, b: &Coordinates) -> f64 { let dlat = (b.latitude - a.latitude).to_radians(); let dlon = (b.longitude - a.longitude).to_radians(); - let h = (dlat / 2.0).sin().powi(2) - + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2); + let h = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2); let c = 2.0 * h.sqrt().asin(); EARTH_RADIUS_KM * c @@ -451,7 +450,11 @@ mod tests { fn test_haversine_same_point() { let a = Coordinates::new_unchecked(51.5074, -0.1278, None); let dist = haversine_distance(&a, &a); - assert!(dist < 0.001, "Same point distance should be ~0, got {}", dist); + assert!( + dist < 0.001, + "Same point distance should be ~0, got {}", + dist + ); } #[test] @@ -514,7 +517,10 @@ mod tests { // London store - .index("london", SpatialData::point(51.5074, -0.1278, None).unwrap()) + .index( + "london", + SpatialData::point(51.5074, -0.1278, None).unwrap(), + ) .await .unwrap(); // Paris @@ -532,7 +538,11 @@ mod tests { let center = Coordinates::new(51.5074, -0.1278, None).unwrap(); let results = store.search_radius(¢er, 500.0, 10).await.unwrap(); - assert_eq!(results.len(), 2, "Should find London and Paris within 500km"); + assert_eq!( + results.len(), + 2, + "Should find London and Paris within 500km" + ); assert_eq!(results[0].entity_id, "london", "London should be closest"); } @@ -542,7 +552,10 @@ mod tests { // London store - .index("london", SpatialData::point(51.5074, -0.1278, None).unwrap()) + .index( + "london", + SpatialData::point(51.5074, -0.1278, None).unwrap(), + ) .await .unwrap(); // Paris @@ -565,7 +578,11 @@ mod tests { }; let results = store.search_within(&bounds, 10).await.unwrap(); - assert_eq!(results.len(), 2, "Should find London and Paris in W. Europe box"); + assert_eq!( + results.len(), + 2, + "Should find London and Paris in W. Europe box" + ); } #[tokio::test] @@ -573,7 +590,10 @@ mod tests { let store = InMemorySpatialStore::new(); store - .index("london", SpatialData::point(51.5074, -0.1278, None).unwrap()) + .index( + "london", + SpatialData::point(51.5074, -0.1278, None).unwrap(), + ) .await .unwrap(); store diff --git a/rust-core/verisim-storage/src/error.rs b/rust-core/verisim-storage/src/error.rs index c284ff4..4091dd2 100644 --- a/rust-core/verisim-storage/src/error.rs +++ b/rust-core/verisim-storage/src/error.rs @@ -88,7 +88,10 @@ mod tests { #[test] fn test_key_too_large_display() { - let err = StorageError::KeyTooLarge { size: 2048, max: 1024 }; + let err = StorageError::KeyTooLarge { + size: 2048, + max: 1024, + }; assert!(err.to_string().contains("key too large")); assert!(err.to_string().contains("2048")); assert!(err.to_string().contains("1024")); @@ -96,7 +99,10 @@ mod tests { #[test] fn test_value_too_large_display() { - let err = StorageError::ValueTooLarge { size: 4096, max: 2048 }; + let err = StorageError::ValueTooLarge { + size: 4096, + max: 2048, + }; assert!(err.to_string().contains("value too large")); assert!(err.to_string().contains("4096")); assert!(err.to_string().contains("2048")); diff --git a/rust-core/verisim-storage/src/memory.rs b/rust-core/verisim-storage/src/memory.rs index 70c9a60..31e0452 100644 --- a/rust-core/verisim-storage/src/memory.rs +++ b/rust-core/verisim-storage/src/memory.rs @@ -105,10 +105,7 @@ impl StorageBackend for InMemoryBackend { async fn multi_get(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>, StorageError> { let map = self.data.read().await; - let results = keys - .iter() - .map(|key| map.get(*key).cloned()) - .collect(); + let results = keys.iter().map(|key| map.get(*key).cloned()).collect(); Ok(results) } @@ -131,10 +128,7 @@ impl StorageBackend for InMemoryBackend { async fn approximate_size(&self) -> Result<Option<u64>, StorageError> { let map = self.data.read().await; - let size: u64 = map - .iter() - .map(|(k, v)| (k.len() + v.len()) as u64) - .sum(); + let size: u64 = map.iter().map(|(k, v)| (k.len() + v.len()) as u64).sum(); Ok(Some(size)) } } @@ -154,13 +148,19 @@ mod tests { // Put and get. backend.put(b"key1", b"value1").await.unwrap(); - assert_eq!(backend.get(b"key1").await.unwrap(), Some(b"value1".to_vec())); + assert_eq!( + backend.get(b"key1").await.unwrap(), + Some(b"value1".to_vec()) + ); assert!(backend.exists(b"key1").await.unwrap()); assert_eq!(backend.len().await, 1); // Overwrite. backend.put(b"key1", b"updated").await.unwrap(); - assert_eq!(backend.get(b"key1").await.unwrap(), Some(b"updated".to_vec())); + assert_eq!( + backend.get(b"key1").await.unwrap(), + Some(b"updated".to_vec()) + ); assert_eq!(backend.len().await, 1); // Delete existing key. diff --git a/rust-core/verisim-storage/src/metrics.rs b/rust-core/verisim-storage/src/metrics.rs index 2855145..75080fc 100644 --- a/rust-core/verisim-storage/src/metrics.rs +++ b/rust-core/verisim-storage/src/metrics.rs @@ -301,10 +301,7 @@ mod tests { let metered = MetricsBackend::new(inner); metered - .batch_put(&[ - (b"a" as &[u8], b"111" as &[u8]), - (b"b", b"2222"), - ]) + .batch_put(&[(b"a" as &[u8], b"111" as &[u8]), (b"b", b"2222")]) .await .unwrap(); @@ -363,10 +360,7 @@ mod tests { metered.put(b"k", b"v").await.unwrap(); metered.flush().await.unwrap(); // Data should survive flush. - assert_eq!( - metered.get(b"k").await.unwrap(), - Some(b"v".to_vec()) - ); + assert_eq!(metered.get(b"k").await.unwrap(), Some(b"v".to_vec())); } #[tokio::test] diff --git a/rust-core/verisim-storage/src/redb_backend.rs b/rust-core/verisim-storage/src/redb_backend.rs index 68ea980..d7fb1b7 100644 --- a/rust-core/verisim-storage/src/redb_backend.rs +++ b/rust-core/verisim-storage/src/redb_backend.rs @@ -74,7 +74,11 @@ impl RedbBackend { } let db = Database::create(&path).map_err(|e| { - StorageError::BackendUnavailable(format!("failed to open redb at {}: {}", path.display(), e)) + StorageError::BackendUnavailable(format!( + "failed to open redb at {}: {}", + path.display(), + e + )) })?; debug!(path = %path.display(), "opened redb backend"); @@ -125,9 +129,9 @@ impl StorageBackend for RedbBackend { let key = key.to_vec(); tokio::task::spawn_blocking(move || -> Result<Option<Vec<u8>>, StorageError> { - let txn = db.begin_read().map_err(|e| { - StorageError::BackendUnavailable(format!("read txn: {e}")) - })?; + let txn = db + .begin_read() + .map_err(|e| StorageError::BackendUnavailable(format!("read txn: {e}")))?; let table = match txn.open_table(MAIN_TABLE) { Ok(t) => t, @@ -151,20 +155,19 @@ impl StorageBackend for RedbBackend { let value = value.to_vec(); tokio::task::spawn_blocking(move || -> Result<(), StorageError> { - let txn = db.begin_write().map_err(|e| { - StorageError::BackendUnavailable(format!("write txn: {e}")) - })?; + let txn = db + .begin_write() + .map_err(|e| StorageError::BackendUnavailable(format!("write txn: {e}")))?; { - let mut table = txn.open_table(MAIN_TABLE).map_err(|e| { - StorageError::BackendUnavailable(format!("open table: {e}")) - })?; - table.insert(key.as_slice(), value.as_slice()).map_err(|e| { - StorageError::CorruptedData(format!("insert: {e}")) - })?; + let mut table = txn + .open_table(MAIN_TABLE) + .map_err(|e| StorageError::BackendUnavailable(format!("open table: {e}")))?; + table + .insert(key.as_slice(), value.as_slice()) + .map_err(|e| StorageError::CorruptedData(format!("insert: {e}")))?; } - txn.commit().map_err(|e| { - StorageError::CorruptedData(format!("commit: {e}")) - })?; + txn.commit() + .map_err(|e| StorageError::CorruptedData(format!("commit: {e}")))?; Ok(()) }) .await @@ -176,21 +179,21 @@ impl StorageBackend for RedbBackend { let key = key.to_vec(); tokio::task::spawn_blocking(move || -> Result<bool, StorageError> { - let txn = db.begin_write().map_err(|e| { - StorageError::BackendUnavailable(format!("write txn: {e}")) - })?; + let txn = db + .begin_write() + .map_err(|e| StorageError::BackendUnavailable(format!("write txn: {e}")))?; let existed; { - let mut table = txn.open_table(MAIN_TABLE).map_err(|e| { - StorageError::BackendUnavailable(format!("open table: {e}")) - })?; - existed = table.remove(key.as_slice()).map_err(|e| { - StorageError::CorruptedData(format!("remove: {e}")) - })?.is_some(); + let mut table = txn + .open_table(MAIN_TABLE) + .map_err(|e| StorageError::BackendUnavailable(format!("open table: {e}")))?; + existed = table + .remove(key.as_slice()) + .map_err(|e| StorageError::CorruptedData(format!("remove: {e}")))? + .is_some(); } - txn.commit().map_err(|e| { - StorageError::CorruptedData(format!("commit: {e}")) - })?; + txn.commit() + .map_err(|e| StorageError::CorruptedData(format!("commit: {e}")))?; Ok(existed) }) .await @@ -211,9 +214,9 @@ impl StorageBackend for RedbBackend { let prefix = prefix.to_vec(); tokio::task::spawn_blocking(move || -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> { - let txn = db.begin_read().map_err(|e| { - StorageError::BackendUnavailable(format!("read txn: {e}")) - })?; + let txn = db + .begin_read() + .map_err(|e| StorageError::BackendUnavailable(format!("read txn: {e}")))?; let table = match txn.open_table(MAIN_TABLE) { Ok(t) => t, Err(_) => return Ok(Vec::new()), // Table doesn't exist yet @@ -222,14 +225,13 @@ impl StorageBackend for RedbBackend { let mut results = Vec::new(); // Scan from the prefix key onward; stop when keys no longer match - let iter = table.range(prefix.as_slice()..).map_err(|e| { - StorageError::CorruptedData(format!("range scan: {e}")) - })?; + let iter = table + .range(prefix.as_slice()..) + .map_err(|e| StorageError::CorruptedData(format!("range scan: {e}")))?; for entry in iter { - let entry = entry.map_err(|e| { - StorageError::CorruptedData(format!("scan entry: {e}")) - })?; + let entry = + entry.map_err(|e| StorageError::CorruptedData(format!("scan entry: {e}")))?; let k = entry.0.value().to_vec(); let v = entry.1.value().to_vec(); @@ -254,9 +256,9 @@ impl StorageBackend for RedbBackend { let owned_keys: Vec<Vec<u8>> = keys.iter().map(|k| k.to_vec()).collect(); tokio::task::spawn_blocking(move || -> Result<Vec<Option<Vec<u8>>>, StorageError> { - let txn = db.begin_read().map_err(|e| { - StorageError::BackendUnavailable(format!("read txn: {e}")) - })?; + let txn = db + .begin_read() + .map_err(|e| StorageError::BackendUnavailable(format!("read txn: {e}")))?; let table = match txn.open_table(MAIN_TABLE) { Ok(t) => t, Err(_) => return Ok(owned_keys.iter().map(|_| None).collect()), @@ -267,9 +269,7 @@ impl StorageBackend for RedbBackend { match table.get(key.as_slice()) { Ok(Some(v)) => results.push(Some(v.value().to_vec())), Ok(None) => results.push(None), - Err(e) => { - return Err(StorageError::CorruptedData(format!("multi_get: {e}"))) - } + Err(e) => return Err(StorageError::CorruptedData(format!("multi_get: {e}"))), } } Ok(results) @@ -286,22 +286,21 @@ impl StorageBackend for RedbBackend { .collect(); tokio::task::spawn_blocking(move || -> Result<(), StorageError> { - let txn = db.begin_write().map_err(|e| { - StorageError::BackendUnavailable(format!("write txn: {e}")) - })?; + let txn = db + .begin_write() + .map_err(|e| StorageError::BackendUnavailable(format!("write txn: {e}")))?; { - let mut table = txn.open_table(MAIN_TABLE).map_err(|e| { - StorageError::BackendUnavailable(format!("open table: {e}")) - })?; + let mut table = txn + .open_table(MAIN_TABLE) + .map_err(|e| StorageError::BackendUnavailable(format!("open table: {e}")))?; for (k, v) in &owned { - table.insert(k.as_slice(), v.as_slice()).map_err(|e| { - StorageError::CorruptedData(format!("batch insert: {e}")) - })?; + table + .insert(k.as_slice(), v.as_slice()) + .map_err(|e| StorageError::CorruptedData(format!("batch insert: {e}")))?; } } - txn.commit().map_err(|e| { - StorageError::CorruptedData(format!("batch commit: {e}")) - })?; + txn.commit() + .map_err(|e| StorageError::CorruptedData(format!("batch commit: {e}")))?; Ok(()) }) .await @@ -355,12 +354,18 @@ mod tests { // Put and get backend.put(b"key1", b"value1").await.unwrap(); - assert_eq!(backend.get(b"key1").await.unwrap(), Some(b"value1".to_vec())); + assert_eq!( + backend.get(b"key1").await.unwrap(), + Some(b"value1".to_vec()) + ); assert!(backend.exists(b"key1").await.unwrap()); // Overwrite backend.put(b"key1", b"updated").await.unwrap(); - assert_eq!(backend.get(b"key1").await.unwrap(), Some(b"updated".to_vec())); + assert_eq!( + backend.get(b"key1").await.unwrap(), + Some(b"updated".to_vec()) + ); // Delete existing key assert!(backend.delete(b"key1").await.unwrap()); @@ -467,7 +472,10 @@ mod tests { // Write data and drop { let backend = RedbBackend::open(&path).unwrap(); - backend.put(b"persistent-key", b"persistent-value").await.unwrap(); + backend + .put(b"persistent-key", b"persistent-value") + .await + .unwrap(); } // Reopen and verify data survived @@ -493,16 +501,10 @@ mod tests { ); // All 0xFF bytes — no upper bound - assert_eq!( - RedbBackend::prefix_upper_bound(b"\xff\xff"), - None - ); + assert_eq!(RedbBackend::prefix_upper_bound(b"\xff\xff"), None); // Empty prefix — no upper bound - assert_eq!( - RedbBackend::prefix_upper_bound(b""), - None - ); + assert_eq!(RedbBackend::prefix_upper_bound(b""), None); // Single byte assert_eq!( diff --git a/rust-core/verisim-storage/src/typed.rs b/rust-core/verisim-storage/src/typed.rs index daf7571..6630c1a 100644 --- a/rust-core/verisim-storage/src/typed.rs +++ b/rust-core/verisim-storage/src/typed.rs @@ -251,7 +251,7 @@ mod tests { // Boolean. store.put("flag", &true).await.unwrap(); - assert_eq!(store.get::<bool>("flag").await.unwrap().unwrap(), true); + assert!(store.get::<bool>("flag").await.unwrap().unwrap()); // Vec. store.put("list", &vec![1, 2, 3]).await.unwrap(); diff --git a/rust-core/verisim-storage/src/wal_backend.rs b/rust-core/verisim-storage/src/wal_backend.rs index 7b342e0..a64fae4 100644 --- a/rust-core/verisim-storage/src/wal_backend.rs +++ b/rust-core/verisim-storage/src/wal_backend.rs @@ -66,9 +66,9 @@ impl<B: StorageBackend> WalBackend<B> { }; let mut writer = self.wal.lock().await; - writer.append(entry).map_err(|e| { - StorageError::BackendUnavailable(format!("WAL append failed: {e}")) - })?; + writer + .append(entry) + .map_err(|e| StorageError::BackendUnavailable(format!("WAL append failed: {e}")))?; Ok(()) } @@ -124,9 +124,9 @@ impl<B: StorageBackend> StorageBackend for WalBackend<B> { async fn flush(&self) -> Result<(), StorageError> { // Sync the WAL first, then the inner store. let mut writer = self.wal.lock().await; - writer.sync().map_err(|e| { - StorageError::BackendUnavailable(format!("WAL sync failed: {e}")) - })?; + writer + .sync() + .map_err(|e| StorageError::BackendUnavailable(format!("WAL sync failed: {e}")))?; drop(writer); self.inner.flush().await diff --git a/rust-core/verisim-temporal/src/diff.rs b/rust-core/verisim-temporal/src/diff.rs index 388e16c..50a8a5d 100644 --- a/rust-core/verisim-temporal/src/diff.rs +++ b/rust-core/verisim-temporal/src/diff.rs @@ -8,22 +8,13 @@ use std::fmt; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum Diff<T> { /// No changes between versions - NoChange { - value: T, - }, + NoChange { value: T }, /// Value changed from old to new - Changed { - old: T, - new: T, - }, + Changed { old: T, new: T }, /// Value was added (didn't exist before) - Added { - value: T, - }, + Added { value: T }, /// Value was removed (existed before, doesn't now) - Removed { - value: T, - }, + Removed { value: T }, } impl<T> Diff<T> { @@ -172,21 +163,37 @@ mod tests { let new_val = "new".to_string(); let diff = compare_values(Some(&old_val), Some(&new_val)).unwrap(); assert!(diff.has_change()); - assert_eq!(diff, Diff::Changed { old: "old".to_string(), new: "new".to_string() }); + assert_eq!( + diff, + Diff::Changed { + old: "old".to_string(), + new: "new".to_string() + } + ); } #[test] fn test_compare_values_added() { let new_val = "new".to_string(); let diff = compare_values(None, Some(&new_val)).unwrap(); - assert_eq!(diff, Diff::Added { value: "new".to_string() }); + assert_eq!( + diff, + Diff::Added { + value: "new".to_string() + } + ); } #[test] fn test_compare_values_removed() { let old_val = "old".to_string(); let diff = compare_values(Some(&old_val), None).unwrap(); - assert_eq!(diff, Diff::Removed { value: "old".to_string() }); + assert_eq!( + diff, + Diff::Removed { + value: "old".to_string() + } + ); } #[test] diff --git a/rust-core/verisim-temporal/src/lib.rs b/rust-core/verisim-temporal/src/lib.rs index b7bcd07..46f9396 100644 --- a/rust-core/verisim-temporal/src/lib.rs +++ b/rust-core/verisim-temporal/src/lib.rs @@ -146,25 +146,52 @@ pub trait TemporalStore: Send + Sync { type Data: Clone + Send + Sync; /// Append a new version - async fn append(&self, entity_id: &str, data: Self::Data, author: &str, message: Option<&str>) -> Result<u64, TemporalError>; + async fn append( + &self, + entity_id: &str, + data: Self::Data, + author: &str, + message: Option<&str>, + ) -> Result<u64, TemporalError>; /// Get the latest version async fn latest(&self, entity_id: &str) -> Result<Option<Version<Self::Data>>, TemporalError>; /// Get a specific version - async fn at_version(&self, entity_id: &str, version: u64) -> Result<Option<Version<Self::Data>>, TemporalError>; + async fn at_version( + &self, + entity_id: &str, + version: u64, + ) -> Result<Option<Version<Self::Data>>, TemporalError>; /// Get version at a specific time - async fn at_time(&self, entity_id: &str, time: DateTime<Utc>) -> Result<Option<Version<Self::Data>>, TemporalError>; + async fn at_time( + &self, + entity_id: &str, + time: DateTime<Utc>, + ) -> Result<Option<Version<Self::Data>>, TemporalError>; /// Get all versions in a time range - async fn in_range(&self, entity_id: &str, range: &TimeRange) -> Result<Vec<Version<Self::Data>>, TemporalError>; + async fn in_range( + &self, + entity_id: &str, + range: &TimeRange, + ) -> Result<Vec<Version<Self::Data>>, TemporalError>; /// Get version history - async fn history(&self, entity_id: &str, limit: usize) -> Result<Vec<Version<Self::Data>>, TemporalError>; + async fn history( + &self, + entity_id: &str, + limit: usize, + ) -> Result<Vec<Version<Self::Data>>, TemporalError>; /// Diff two versions - async fn diff(&self, entity_id: &str, v1: u64, v2: u64) -> Result<diff::Diff<Self::Data>, TemporalError> + async fn diff( + &self, + entity_id: &str, + v1: u64, + v2: u64, + ) -> Result<diff::Diff<Self::Data>, TemporalError> where Self::Data: PartialEq, { @@ -175,11 +202,18 @@ pub trait TemporalStore: Send + Sync { version1.as_ref().map(|v| &v.data), version2.as_ref().map(|v| &v.data), ) - .map_err(|_| TemporalError::NotFound(format!("No versions to compare for entity {}", entity_id))) + .map_err(|_| { + TemporalError::NotFound(format!("No versions to compare for entity {}", entity_id)) + }) } /// Diff two timestamps - async fn diff_time(&self, entity_id: &str, t1: DateTime<Utc>, t2: DateTime<Utc>) -> Result<diff::Diff<Self::Data>, TemporalError> + async fn diff_time( + &self, + entity_id: &str, + t1: DateTime<Utc>, + t2: DateTime<Utc>, + ) -> Result<diff::Diff<Self::Data>, TemporalError> where Self::Data: PartialEq, { @@ -190,7 +224,9 @@ pub trait TemporalStore: Send + Sync { version1.as_ref().map(|v| &v.data), version2.as_ref().map(|v| &v.data), ) - .map_err(|_| TemporalError::NotFound(format!("No versions to compare for entity {}", entity_id))) + .map_err(|_| { + TemporalError::NotFound(format!("No versions to compare for entity {}", entity_id)) + }) } } @@ -199,7 +235,7 @@ type VersionHistory<T> = HashMap<String, BTreeMap<u64, Version<T>>>; /// In-memory versioned store pub struct InMemoryVersionStore<T> { - /// Map of entity_id -> (version -> Version<T>) + /// Map of entity_id -> (version -> `Version<T>`) versions: Arc<RwLock<VersionHistory<T>>>, } @@ -221,8 +257,17 @@ impl<T: Clone + Send + Sync + 'static> Default for InMemoryVersionStore<T> { impl<T: Clone + Send + Sync + 'static> TemporalStore for InMemoryVersionStore<T> { type Data = T; - async fn append(&self, entity_id: &str, data: Self::Data, author: &str, message: Option<&str>) -> Result<u64, TemporalError> { - let mut store = self.versions.write().map_err(|_| TemporalError::LockPoisoned)?; + async fn append( + &self, + entity_id: &str, + data: Self::Data, + author: &str, + message: Option<&str>, + ) -> Result<u64, TemporalError> { + let mut store = self + .versions + .write() + .map_err(|_| TemporalError::LockPoisoned)?; let versions = store.entry(entity_id.to_string()).or_default(); let next_version = versions.keys().last().map(|v| v + 1).unwrap_or(1); @@ -236,21 +281,38 @@ impl<T: Clone + Send + Sync + 'static> TemporalStore for InMemoryVersionStore<T> } async fn latest(&self, entity_id: &str) -> Result<Option<Version<Self::Data>>, TemporalError> { - let store = self.versions.read().map_err(|_| TemporalError::LockPoisoned)?; + let store = self + .versions + .read() + .map_err(|_| TemporalError::LockPoisoned)?; Ok(store .get(entity_id) .and_then(|versions| versions.values().last().cloned())) } - async fn at_version(&self, entity_id: &str, version: u64) -> Result<Option<Version<Self::Data>>, TemporalError> { - let store = self.versions.read().map_err(|_| TemporalError::LockPoisoned)?; + async fn at_version( + &self, + entity_id: &str, + version: u64, + ) -> Result<Option<Version<Self::Data>>, TemporalError> { + let store = self + .versions + .read() + .map_err(|_| TemporalError::LockPoisoned)?; Ok(store .get(entity_id) .and_then(|versions| versions.get(&version).cloned())) } - async fn at_time(&self, entity_id: &str, time: DateTime<Utc>) -> Result<Option<Version<Self::Data>>, TemporalError> { - let store = self.versions.read().map_err(|_| TemporalError::LockPoisoned)?; + async fn at_time( + &self, + entity_id: &str, + time: DateTime<Utc>, + ) -> Result<Option<Version<Self::Data>>, TemporalError> { + let store = self + .versions + .read() + .map_err(|_| TemporalError::LockPoisoned)?; Ok(store.get(entity_id).and_then(|versions| { versions .values() @@ -260,8 +322,15 @@ impl<T: Clone + Send + Sync + 'static> TemporalStore for InMemoryVersionStore<T> })) } - async fn in_range(&self, entity_id: &str, range: &TimeRange) -> Result<Vec<Version<Self::Data>>, TemporalError> { - let store = self.versions.read().map_err(|_| TemporalError::LockPoisoned)?; + async fn in_range( + &self, + entity_id: &str, + range: &TimeRange, + ) -> Result<Vec<Version<Self::Data>>, TemporalError> { + let store = self + .versions + .read() + .map_err(|_| TemporalError::LockPoisoned)?; Ok(store .get(entity_id) .map(|versions| { @@ -274,18 +343,18 @@ impl<T: Clone + Send + Sync + 'static> TemporalStore for InMemoryVersionStore<T> .unwrap_or_default()) } - async fn history(&self, entity_id: &str, limit: usize) -> Result<Vec<Version<Self::Data>>, TemporalError> { - let store = self.versions.read().map_err(|_| TemporalError::LockPoisoned)?; + async fn history( + &self, + entity_id: &str, + limit: usize, + ) -> Result<Vec<Version<Self::Data>>, TemporalError> { + let store = self + .versions + .read() + .map_err(|_| TemporalError::LockPoisoned)?; Ok(store .get(entity_id) - .map(|versions| { - versions - .values() - .rev() - .take(limit) - .cloned() - .collect() - }) + .map(|versions| versions.values().rev().take(limit).cloned().collect()) .unwrap_or_default()) } } @@ -296,13 +365,24 @@ pub trait TimeSeriesStore: Send + Sync { type Value: Clone + Send + Sync; /// Append a time point - async fn append(&self, series_id: &str, point: TimePoint<Self::Value>) -> Result<(), TemporalError>; + async fn append( + &self, + series_id: &str, + point: TimePoint<Self::Value>, + ) -> Result<(), TemporalError>; /// Query points in a time range - async fn query(&self, series_id: &str, range: &TimeRange) -> Result<Vec<TimePoint<Self::Value>>, TemporalError>; + async fn query( + &self, + series_id: &str, + range: &TimeRange, + ) -> Result<Vec<TimePoint<Self::Value>>, TemporalError>; /// Get the latest point - async fn latest(&self, series_id: &str) -> Result<Option<TimePoint<Self::Value>>, TemporalError>; + async fn latest( + &self, + series_id: &str, + ) -> Result<Option<TimePoint<Self::Value>>, TemporalError>; } /// In-memory time series store @@ -328,14 +408,28 @@ impl<T: Clone + Send + Sync + 'static> Default for InMemoryTimeSeriesStore<T> { impl<T: Clone + Send + Sync + 'static> TimeSeriesStore for InMemoryTimeSeriesStore<T> { type Value = T; - async fn append(&self, series_id: &str, point: TimePoint<Self::Value>) -> Result<(), TemporalError> { - let mut store = self.series.write().map_err(|_| TemporalError::LockPoisoned)?; + async fn append( + &self, + series_id: &str, + point: TimePoint<Self::Value>, + ) -> Result<(), TemporalError> { + let mut store = self + .series + .write() + .map_err(|_| TemporalError::LockPoisoned)?; store.entry(series_id.to_string()).or_default().push(point); Ok(()) } - async fn query(&self, series_id: &str, range: &TimeRange) -> Result<Vec<TimePoint<Self::Value>>, TemporalError> { - let store = self.series.read().map_err(|_| TemporalError::LockPoisoned)?; + async fn query( + &self, + series_id: &str, + range: &TimeRange, + ) -> Result<Vec<TimePoint<Self::Value>>, TemporalError> { + let store = self + .series + .read() + .map_err(|_| TemporalError::LockPoisoned)?; Ok(store .get(series_id) .map(|points| { @@ -348,9 +442,17 @@ impl<T: Clone + Send + Sync + 'static> TimeSeriesStore for InMemoryTimeSeriesSto .unwrap_or_default()) } - async fn latest(&self, series_id: &str) -> Result<Option<TimePoint<Self::Value>>, TemporalError> { - let store = self.series.read().map_err(|_| TemporalError::LockPoisoned)?; - Ok(store.get(series_id).and_then(|points| points.last().cloned())) + async fn latest( + &self, + series_id: &str, + ) -> Result<Option<TimePoint<Self::Value>>, TemporalError> { + let store = self + .series + .read() + .map_err(|_| TemporalError::LockPoisoned)?; + Ok(store + .get(series_id) + .and_then(|points| points.last().cloned())) } } @@ -362,8 +464,14 @@ mod tests { async fn test_version_store() { let store: InMemoryVersionStore<String> = InMemoryVersionStore::new(); - let v1 = store.append("entity1", "data v1".to_string(), "alice", Some("initial")).await.unwrap(); - let v2 = store.append("entity1", "data v2".to_string(), "bob", Some("update")).await.unwrap(); + let v1 = store + .append("entity1", "data v1".to_string(), "alice", Some("initial")) + .await + .unwrap(); + let v2 = store + .append("entity1", "data v2".to_string(), "bob", Some("update")) + .await + .unwrap(); assert_eq!(v1, 1); assert_eq!(v2, 2); diff --git a/rust-core/verisim-temporal/src/persistent.rs b/rust-core/verisim-temporal/src/persistent.rs index 912dcdb..cfcbb42 100644 --- a/rust-core/verisim-temporal/src/persistent.rs +++ b/rust-core/verisim-temporal/src/persistent.rs @@ -127,10 +127,7 @@ impl<T: Serialize + DeserializeOwned + Clone + Send + Sync + 'static> TemporalSt Ok(next_version) } - async fn latest( - &self, - entity_id: &str, - ) -> Result<Option<Version<Self::Data>>, TemporalError> { + async fn latest(&self, entity_id: &str) -> Result<Option<Version<Self::Data>>, TemporalError> { let store = self .versions .read() @@ -220,7 +217,9 @@ mod tests { // Write data in one session. { - let store = RedbVersionStore::<serde_json::Value>::open(&path).await.unwrap(); + let store = RedbVersionStore::<serde_json::Value>::open(&path) + .await + .unwrap(); let v1 = store .append( "entity-1", @@ -246,7 +245,9 @@ mod tests { // Reopen and verify data survived. { - let store = RedbVersionStore::<serde_json::Value>::open(&path).await.unwrap(); + let store = RedbVersionStore::<serde_json::Value>::open(&path) + .await + .unwrap(); let latest = store.latest("entity-1").await.unwrap().unwrap(); assert_eq!(latest.version, 2); diff --git a/rust-core/verisim-temporal/tests/property_tests.rs b/rust-core/verisim-temporal/tests/property_tests.rs index ebf2102..2b7a432 100644 --- a/rust-core/verisim-temporal/tests/property_tests.rs +++ b/rust-core/verisim-temporal/tests/property_tests.rs @@ -3,7 +3,10 @@ use chrono::{Duration, Utc}; use proptest::prelude::*; -use verisim_temporal::{diff, InMemoryTimeSeriesStore, InMemoryVersionStore, TemporalStore, TimePoint, TimeRange, TimeSeriesStore}; +use verisim_temporal::{ + diff, InMemoryTimeSeriesStore, InMemoryVersionStore, TemporalStore, TimePoint, TimeRange, + TimeSeriesStore, +}; /// Generate arbitrary entity IDs fn arb_entity_id() -> impl Strategy<Value = String> { @@ -141,32 +144,50 @@ async fn test_time_travel_query() { // Create version 1 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let v1_time = Utc::now(); - store.append(entity_id, "version 1".to_string(), "alice", Some("initial")).await.unwrap(); + store + .append(entity_id, "version 1".to_string(), "alice", Some("initial")) + .await + .unwrap(); // Create version 2 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let v2_time = Utc::now(); - store.append(entity_id, "version 2".to_string(), "bob", Some("update")).await.unwrap(); + store + .append(entity_id, "version 2".to_string(), "bob", Some("update")) + .await + .unwrap(); // Create version 3 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; let v3_time = Utc::now(); - store.append(entity_id, "version 3".to_string(), "charlie", Some("final")).await.unwrap(); + store + .append(entity_id, "version 3".to_string(), "charlie", Some("final")) + .await + .unwrap(); // Query before any versions let before = store.at_time(entity_id, now).await.unwrap(); assert!(before.is_none(), "Should have no version before creation"); // Query at v1 time - let at_v1 = store.at_time(entity_id, v1_time + Duration::milliseconds(1)).await.unwrap(); + let at_v1 = store + .at_time(entity_id, v1_time + Duration::milliseconds(1)) + .await + .unwrap(); assert_eq!(at_v1.unwrap().data, "version 1"); // Query at v2 time - let at_v2 = store.at_time(entity_id, v2_time + Duration::milliseconds(1)).await.unwrap(); + let at_v2 = store + .at_time(entity_id, v2_time + Duration::milliseconds(1)) + .await + .unwrap(); assert_eq!(at_v2.unwrap().data, "version 2"); // Query at v3 time - let at_v3 = store.at_time(entity_id, v3_time + Duration::milliseconds(1)).await.unwrap(); + let at_v3 = store + .at_time(entity_id, v3_time + Duration::milliseconds(1)) + .await + .unwrap(); assert_eq!(at_v3.unwrap().data, "version 3"); // Query after all versions @@ -185,12 +206,15 @@ async fn test_time_range_query() { // Create 5 versions with small delays for i in 1..=5 { tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - store.append( - entity_id, - format!("version {}", i), - "author", - Some(&format!("v{}", i)) - ).await.unwrap(); + store + .append( + entity_id, + format!("version {}", i), + "author", + Some(&format!("v{}", i)), + ) + .await + .unwrap(); } let end_time = Utc::now(); @@ -215,9 +239,18 @@ async fn test_diff_versions() { let entity_id = "doc-789"; // Create versions - store.append(entity_id, "first".to_string(), "alice", None).await.unwrap(); - store.append(entity_id, "second".to_string(), "bob", None).await.unwrap(); - store.append(entity_id, "third".to_string(), "charlie", None).await.unwrap(); + store + .append(entity_id, "first".to_string(), "alice", None) + .await + .unwrap(); + store + .append(entity_id, "second".to_string(), "bob", None) + .await + .unwrap(); + store + .append(entity_id, "third".to_string(), "charlie", None) + .await + .unwrap(); // Diff v1 and v2 let diff_1_2 = store.diff(entity_id, 1, 2).await.unwrap(); diff --git a/rust-core/verisim-tensor/src/lib.rs b/rust-core/verisim-tensor/src/lib.rs index 9c6276f..df221e2 100644 --- a/rust-core/verisim-tensor/src/lib.rs +++ b/rust-core/verisim-tensor/src/lib.rs @@ -7,10 +7,10 @@ #![forbid(unsafe_code)] #[cfg(feature = "redb-backend")] pub mod persistent; -#[cfg(feature = "redb-backend")] -pub use persistent::*; use async_trait::async_trait; use ndarray::{Array, ArrayD, IxDyn}; +#[cfg(feature = "redb-backend")] +pub use persistent::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; @@ -20,7 +20,10 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum TensorError { #[error("Shape mismatch: expected {expected:?}, got {actual:?}")] - ShapeMismatch { expected: Vec<usize>, actual: Vec<usize> }, + ShapeMismatch { + expected: Vec<usize>, + actual: Vec<usize>, + }, #[error("Tensor not found: {0}")] NotFound(String), @@ -62,7 +65,11 @@ pub struct Tensor { impl Tensor { /// Create a new tensor from shape and data - pub fn new(id: impl Into<String>, shape: Vec<usize>, data: Vec<f64>) -> Result<Self, TensorError> { + pub fn new( + id: impl Into<String>, + shape: Vec<usize>, + data: Vec<f64>, + ) -> Result<Self, TensorError> { let expected_len: usize = shape.iter().product(); if data.len() != expected_len { return Err(TensorError::InvalidOperation(format!( @@ -109,8 +116,7 @@ impl Tensor { pub fn to_ndarray(&self) -> ArrayD<f64> { let shape = IxDyn(&self.shape); // Use C order (row-major) to match data layout documented on line 49 - Array::from_shape_vec(shape, self.data.clone()) - .expect("Shape should match data length") + Array::from_shape_vec(shape, self.data.clone()).expect("Shape should match data length") } /// Create from ndarray @@ -195,25 +201,45 @@ impl Default for InMemoryTensorStore { #[async_trait] impl TensorStore for InMemoryTensorStore { async fn put(&self, tensor: &Tensor) -> Result<(), TensorError> { - self.tensors.write().map_err(|_| TensorError::LockPoisoned)?.insert(tensor.id.clone(), tensor.clone()); + self.tensors + .write() + .map_err(|_| TensorError::LockPoisoned)? + .insert(tensor.id.clone(), tensor.clone()); Ok(()) } async fn get(&self, id: &str) -> Result<Option<Tensor>, TensorError> { - Ok(self.tensors.read().map_err(|_| TensorError::LockPoisoned)?.get(id).cloned()) + Ok(self + .tensors + .read() + .map_err(|_| TensorError::LockPoisoned)? + .get(id) + .cloned()) } async fn delete(&self, id: &str) -> Result<(), TensorError> { - self.tensors.write().map_err(|_| TensorError::LockPoisoned)?.remove(id); + self.tensors + .write() + .map_err(|_| TensorError::LockPoisoned)? + .remove(id); Ok(()) } async fn list(&self) -> Result<Vec<String>, TensorError> { - Ok(self.tensors.read().map_err(|_| TensorError::LockPoisoned)?.keys().cloned().collect()) + Ok(self + .tensors + .read() + .map_err(|_| TensorError::LockPoisoned)? + .keys() + .cloned() + .collect()) } async fn map(&self, id: &str, op: fn(f64) -> f64) -> Result<Tensor, TensorError> { - let tensor = self.tensors.read().map_err(|_| TensorError::LockPoisoned)? + let tensor = self + .tensors + .read() + .map_err(|_| TensorError::LockPoisoned)? .get(id) .cloned() .ok_or_else(|| TensorError::NotFound(id.to_string()))?; @@ -229,7 +255,10 @@ impl TensorStore for InMemoryTensorStore { } async fn reduce(&self, id: &str, axis: usize, op: ReduceOp) -> Result<Tensor, TensorError> { - let tensor = self.tensors.read().map_err(|_| TensorError::LockPoisoned)? + let tensor = self + .tensors + .read() + .map_err(|_| TensorError::LockPoisoned)? .get(id) .cloned() .ok_or_else(|| TensorError::NotFound(id.to_string()))?; @@ -246,24 +275,21 @@ impl TensorStore for InMemoryTensorStore { let reduced = match op { ReduceOp::Sum => arr.sum_axis(ndarray::Axis(axis)), ReduceOp::Mean => arr.mean_axis(ndarray::Axis(axis)).expect("non-empty axis"), - ReduceOp::Max => { - arr.map_axis(ndarray::Axis(axis), |lane| { - lane.iter().copied().fold(f64::NEG_INFINITY, f64::max) - }) - } - ReduceOp::Min => { - arr.map_axis(ndarray::Axis(axis), |lane| { - lane.iter().copied().fold(f64::INFINITY, f64::min) - }) - } + ReduceOp::Max => arr.map_axis(ndarray::Axis(axis), |lane| { + lane.iter().copied().fold(f64::NEG_INFINITY, f64::max) + }), + ReduceOp::Min => arr.map_axis(ndarray::Axis(axis), |lane| { + lane.iter().copied().fold(f64::INFINITY, f64::min) + }), ReduceOp::Prod => { - arr.map_axis(ndarray::Axis(axis), |lane| { - lane.iter().copied().product() - }) + arr.map_axis(ndarray::Axis(axis), |lane| lane.iter().copied().product()) } }; - Ok(Tensor::from_ndarray(format!("{}_reduced", tensor.id), &reduced)) + Ok(Tensor::from_ndarray( + format!("{}_reduced", tensor.id), + &reduced, + )) } } diff --git a/rust-core/verisim-tensor/src/persistent.rs b/rust-core/verisim-tensor/src/persistent.rs index d9a84aa..5123d97 100644 --- a/rust-core/verisim-tensor/src/persistent.rs +++ b/rust-core/verisim-tensor/src/persistent.rs @@ -37,14 +37,19 @@ impl RedbTensorStore { } info!(count = cache.len(), "Loaded tensor store from redb"); - Ok(Self { store, cache: Arc::new(RwLock::new(cache)) }) + Ok(Self { + store, + cache: Arc::new(RwLock::new(cache)), + }) } } #[async_trait] impl TensorStore for RedbTensorStore { async fn put(&self, tensor: &Tensor) -> Result<(), TensorError> { - self.store.put(&tensor.id, tensor).await + self.store + .put(&tensor.id, tensor) + .await .map_err(|e| TensorError::SerializationError(format!("put: {}", e)))?; let mut c = self.cache.write().map_err(|_| TensorError::LockPoisoned)?; c.insert(tensor.id.clone(), tensor.clone()); @@ -57,7 +62,9 @@ impl TensorStore for RedbTensorStore { } async fn delete(&self, id: &str) -> Result<(), TensorError> { - self.store.delete(id).await + self.store + .delete(id) + .await .map_err(|e| TensorError::SerializationError(format!("delete: {}", e)))?; let mut c = self.cache.write().map_err(|_| TensorError::LockPoisoned)?; c.remove(id); @@ -71,18 +78,23 @@ impl TensorStore for RedbTensorStore { async fn map(&self, id: &str, op: fn(f64) -> f64) -> Result<Tensor, TensorError> { let c = self.cache.read().map_err(|_| TensorError::LockPoisoned)?; - let tensor = c.get(id).ok_or_else(|| TensorError::NotFound(id.to_string()))?; + let tensor = c + .get(id) + .ok_or_else(|| TensorError::NotFound(id.to_string()))?; let new_data: Vec<f64> = tensor.data.iter().map(|&v| op(v)).collect(); Tensor::new(format!("{}_mapped", id), tensor.shape.clone(), new_data) } async fn reduce(&self, id: &str, axis: usize, op: ReduceOp) -> Result<Tensor, TensorError> { let c = self.cache.read().map_err(|_| TensorError::LockPoisoned)?; - let tensor = c.get(id).ok_or_else(|| TensorError::NotFound(id.to_string()))?; + let tensor = c + .get(id) + .ok_or_else(|| TensorError::NotFound(id.to_string()))?; let arr = tensor.to_ndarray(); let reduced = match op { ReduceOp::Sum => arr.sum_axis(ndarray::Axis(axis)), - ReduceOp::Mean => arr.mean_axis(ndarray::Axis(axis)) + ReduceOp::Mean => arr + .mean_axis(ndarray::Axis(axis)) .ok_or_else(|| TensorError::InvalidOperation("mean on empty axis".into()))?, ReduceOp::Max => arr.map_axis(ndarray::Axis(axis), |lane| { lane.iter().copied().fold(f64::NEG_INFINITY, f64::max) @@ -90,11 +102,14 @@ impl TensorStore for RedbTensorStore { ReduceOp::Min => arr.map_axis(ndarray::Axis(axis), |lane| { lane.iter().copied().fold(f64::INFINITY, f64::min) }), - ReduceOp::Prod => arr.map_axis(ndarray::Axis(axis), |lane| { - lane.iter().copied().product() - }), + ReduceOp::Prod => { + arr.map_axis(ndarray::Axis(axis), |lane| lane.iter().copied().product()) + } }; - Ok(Tensor::from_ndarray(format!("{}_reduced", id), &reduced.into_dyn())) + Ok(Tensor::from_ndarray( + format!("{}_reduced", id), + &reduced.into_dyn(), + )) } } diff --git a/rust-core/verisim-vector/src/hnsw.rs b/rust-core/verisim-vector/src/hnsw.rs index bdb1f49..d0d14b8 100644 --- a/rust-core/verisim-vector/src/hnsw.rs +++ b/rust-core/verisim-vector/src/hnsw.rs @@ -82,9 +82,8 @@ struct Node { vector: Vec<f32>, metadata: HashMap<String, String>, /// Neighbors per layer (layer index -> vec of node indices). + /// `neighbors.len() - 1` is the node's assigned level. neighbors: Vec<Vec<usize>>, - /// Assigned level for this node. - level: usize, /// Soft-delete flag. deleted: bool, } @@ -263,7 +262,6 @@ impl Graph { vector, metadata, neighbors: (0..=level).map(|_| Vec::new()).collect(), - level, deleted: false, }; self.nodes.push(node); @@ -283,13 +281,8 @@ impl Graph { let top = self.current_max_level; if top > level { for l in (level + 1..=top).rev() { - let nearest = self.search_layer( - &self.nodes[node_idx].vector, - ¤t_ep, - 1, - l, - metric, - ); + let nearest = + self.search_layer(&self.nodes[node_idx].vector, ¤t_ep, 1, l, metric); if let Some(&(_, idx)) = nearest.first() { current_ep = vec![idx]; } @@ -566,9 +559,7 @@ mod tests { #[tokio::test] async fn test_hnsw_dimension_mismatch() { let store = HnswVectorStore::with_defaults(3, DistanceMetric::Cosine); - let result = store - .upsert(&Embedding::new("e1", vec![1.0, 0.0])) - .await; + let result = store.upsert(&Embedding::new("e1", vec![1.0, 0.0])).await; assert!(result.is_err()); } diff --git a/rust-core/verisim-vector/src/lib.rs b/rust-core/verisim-vector/src/lib.rs index 5050c3b..6a7e546 100644 --- a/rust-core/verisim-vector/src/lib.rs +++ b/rust-core/verisim-vector/src/lib.rs @@ -152,9 +152,7 @@ impl BruteForceVectorStore { let b_norm = Self::normalize(b); a_norm.iter().zip(b_norm.iter()).map(|(x, y)| x * y).sum() } - DistanceMetric::DotProduct => { - a.iter().zip(b.iter()).map(|(x, y)| x * y).sum() - } + DistanceMetric::DotProduct => a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(), DistanceMetric::Euclidean => { let dist_sq: f32 = a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum(); 1.0 / (1.0 + dist_sq.sqrt()) // Convert distance to similarity @@ -189,7 +187,10 @@ impl VectorStore for BruteForceVectorStore { }); } - let embeddings = self.embeddings.read().map_err(|_| VectorError::LockPoisoned)?; + let embeddings = self + .embeddings + .read() + .map_err(|_| VectorError::LockPoisoned)?; // Compute similarities for all embeddings (brute-force) let mut scored: Vec<_> = embeddings @@ -204,7 +205,11 @@ impl VectorStore for BruteForceVectorStore { .collect(); // Sort by similarity descending - scored.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal)); + scored.sort_by(|a, b| { + b.score + .partial_cmp(&a.score) + .unwrap_or(std::cmp::Ordering::Equal) + }); // Return top k scored.truncate(k); @@ -212,11 +217,19 @@ impl VectorStore for BruteForceVectorStore { } async fn get(&self, id: &str) -> Result<Option<Embedding>, VectorError> { - Ok(self.embeddings.read().map_err(|_| VectorError::LockPoisoned)?.get(id).cloned()) + Ok(self + .embeddings + .read() + .map_err(|_| VectorError::LockPoisoned)? + .get(id) + .cloned()) } async fn delete(&self, id: &str) -> Result<(), VectorError> { - self.embeddings.write().map_err(|_| VectorError::LockPoisoned)?.remove(id); + self.embeddings + .write() + .map_err(|_| VectorError::LockPoisoned)? + .remove(id); Ok(()) } diff --git a/rust-core/verisim-vector/src/persistent.rs b/rust-core/verisim-vector/src/persistent.rs index f464ecd..35f653d 100644 --- a/rust-core/verisim-vector/src/persistent.rs +++ b/rust-core/verisim-vector/src/persistent.rs @@ -51,9 +51,8 @@ impl RedbVectorStore { dimension: usize, metric: DistanceMetric, ) -> Result<Self, VectorError> { - let backend = RedbBackend::open(path.as_ref()).map_err(|e| { - VectorError::IndexError(format!("Failed to open redb: {}", e)) - })?; + let backend = RedbBackend::open(path.as_ref()) + .map_err(|e| VectorError::IndexError(format!("Failed to open redb: {}", e)))?; let store = TypedStore::new(backend, "vec"); let mut index = HashMap::new(); @@ -111,15 +110,9 @@ impl RedbVectorStore { let b_norm = Self::normalize(b); a_norm.iter().zip(b_norm.iter()).map(|(x, y)| x * y).sum() } - DistanceMetric::DotProduct => { - a.iter().zip(b.iter()).map(|(x, y)| x * y).sum() - } + DistanceMetric::DotProduct => a.iter().zip(b.iter()).map(|(x, y)| x * y).sum(), DistanceMetric::Euclidean => { - let dist_sq: f32 = a - .iter() - .zip(b.iter()) - .map(|(x, y)| (x - y).powi(2)) - .sum(); + let dist_sq: f32 = a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum(); 1.0 / (1.0 + dist_sq.sqrt()) } } @@ -167,7 +160,11 @@ impl VectorStore for RedbVectorStore { }) .collect(); - results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal)); + results.sort_by(|a, b| { + b.score + .partial_cmp(&a.score) + .unwrap_or(std::cmp::Ordering::Equal) + }); results.truncate(k); Ok(results) diff --git a/rust-core/verisim-wal/src/entry.rs b/rust-core/verisim-wal/src/entry.rs index a644672..cce7690 100644 --- a/rust-core/verisim-wal/src/entry.rs +++ b/rust-core/verisim-wal/src/entry.rs @@ -158,11 +158,7 @@ impl WalEntry { pub fn serialize(&self) -> Vec<u8> { // Build the inner content (everything after entry_length and crc32). let entity_id_bytes = self.entity_id.as_bytes(); - let inner_size = FIXED_FIELDS_SIZE - + 4 - + entity_id_bytes.len() - + 4 - + self.payload.len(); + let inner_size = FIXED_FIELDS_SIZE + 4 + entity_id_bytes.len() + 4 + self.payload.len(); let mut inner = Vec::with_capacity(inner_size); @@ -416,7 +412,10 @@ mod tests { WalModality::Spatial, WalModality::All, ] { - assert_eq!(WalModality::from_byte(modality.to_byte()).unwrap(), modality); + assert_eq!( + WalModality::from_byte(modality.to_byte()).unwrap(), + modality + ); } } diff --git a/rust-core/verisim-wal/src/reader.rs b/rust-core/verisim-wal/src/reader.rs index 20ffffb..4e9d9ee 100644 --- a/rust-core/verisim-wal/src/reader.rs +++ b/rust-core/verisim-wal/src/reader.rs @@ -37,9 +37,7 @@ impl WalReader { pub fn open(wal_dir: impl AsRef<Path>) -> WalResult<Self> { let wal_dir = wal_dir.as_ref().to_path_buf(); if !wal_dir.is_dir() { - return Err(WalError::DirectoryNotFound( - wal_dir.display().to_string(), - )); + return Err(WalError::DirectoryNotFound(wal_dir.display().to_string())); } Ok(Self { wal_dir }) } @@ -74,8 +72,7 @@ impl WalReader { debug!( count = all_entries.len(), - from_sequence, - "Replaying WAL entries" + from_sequence, "Replaying WAL entries" ); Ok(WalEntryIterator { @@ -189,14 +186,13 @@ fn read_segment_entries(path: &Path) -> WalResult<Vec<WalEntry>> { while offset + 4 <= data.len() { // Read entry_length (u32 LE). - let entry_length = u32::from_le_bytes( - data[offset..offset + 4] - .try_into() - .map_err(|_| WalError::TruncatedEntry { + let entry_length = + u32::from_le_bytes(data[offset..offset + 4].try_into().map_err(|_| { + WalError::TruncatedEntry { segment: segment_name.clone(), offset: offset as u64, - })?, - ); + } + })?); // Validate entry_length. if entry_length == 0 { @@ -427,8 +423,7 @@ mod tests { // Find the second entry. The first entry starts at offset 0. // Read the first entry's length to find the second entry's offset. - let first_len = - u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize; + let first_len = u32::from_le_bytes(data[0..4].try_into().unwrap()) as usize; let second_entry_offset = 4 + first_len; // The CRC is at bytes [offset+4..offset+8] (after entry_length). @@ -530,9 +525,7 @@ mod tests { { let mut writer = WalWriter::open(dir.path(), SyncMode::Fsync).unwrap(); for _ in 0..7 { - writer - .append(test_entry("e", WalModality::Graph)) - .unwrap(); + writer.append(test_entry("e", WalModality::Graph)).unwrap(); } } @@ -550,10 +543,7 @@ mod tests { WalWriter::open_with_max_size(dir.path(), SyncMode::Fsync, 100).unwrap(); for i in 0..20 { writer - .append(test_entry( - &format!("entity-{i}"), - WalModality::Graph, - )) + .append(test_entry(&format!("entity-{i}"), WalModality::Graph)) .unwrap(); } } @@ -581,9 +571,7 @@ mod tests { { let mut writer = WalWriter::open(dir.path(), SyncMode::Fsync).unwrap(); for _ in 0..5 { - writer - .append(test_entry("e", WalModality::Graph)) - .unwrap(); + writer.append(test_entry("e", WalModality::Graph)).unwrap(); } } diff --git a/rust-core/verisim-wal/src/segment.rs b/rust-core/verisim-wal/src/segment.rs index 09c2ebe..24c8ea5 100644 --- a/rust-core/verisim-wal/src/segment.rs +++ b/rust-core/verisim-wal/src/segment.rs @@ -86,9 +86,7 @@ pub fn parse_segment_filename(name: &str) -> Option<u64> { /// Non-segment files in the directory are silently ignored. pub fn list_segments(wal_dir: &Path) -> WalResult<Vec<SegmentInfo>> { if !wal_dir.is_dir() { - return Err(WalError::DirectoryNotFound( - wal_dir.display().to_string(), - )); + return Err(WalError::DirectoryNotFound(wal_dir.display().to_string())); } let mut segments = Vec::new(); @@ -199,14 +197,8 @@ mod tests { #[test] fn test_parse_segment_filename_valid() { - assert_eq!( - parse_segment_filename("wal-0000000000000042.log"), - Some(42) - ); - assert_eq!( - parse_segment_filename("wal-0000000000000000.log"), - Some(0) - ); + assert_eq!(parse_segment_filename("wal-0000000000000042.log"), Some(42)); + assert_eq!(parse_segment_filename("wal-0000000000000000.log"), Some(0)); } #[test] diff --git a/rust-core/verisim-wal/src/writer.rs b/rust-core/verisim-wal/src/writer.rs index 965901d..7c2b4d0 100644 --- a/rust-core/verisim-wal/src/writer.rs +++ b/rust-core/verisim-wal/src/writer.rs @@ -17,9 +17,7 @@ use tracing::{debug, info}; use crate::entry::{WalEntry, WalModality, WalOperation}; use crate::error::{WalError, WalResult}; -use crate::segment::{ - list_segments, segment_path, DEFAULT_MAX_SEGMENT_SIZE, SegmentInfo, -}; +use crate::segment::{list_segments, segment_path, SegmentInfo, DEFAULT_MAX_SEGMENT_SIZE}; // --------------------------------------------------------------------------- // SyncMode @@ -300,14 +298,13 @@ impl WalWriter { .unwrap_or_else(|| "<unknown>".to_string()); while offset + 4 <= data.len() { - let entry_length = u32::from_le_bytes( - data[offset..offset + 4] - .try_into() - .map_err(|_| WalError::TruncatedEntry { + let entry_length = + u32::from_le_bytes(data[offset..offset + 4].try_into().map_err(|_| { + WalError::TruncatedEntry { segment: segment_name.clone(), offset: offset as u64, - })?, - ); + } + })?); // Sanity check: the entry must fit within the remaining data. if offset + 4 + entry_length as usize > data.len() { @@ -319,14 +316,13 @@ impl WalWriter { // Layout: [4 bytes crc][8 bytes sequence][...] let inner_start = offset + 4 + 4; // skip entry_length + crc if inner_start + 8 <= data.len() { - let seq = u64::from_le_bytes( - data[inner_start..inner_start + 8] - .try_into() - .map_err(|_| WalError::TruncatedEntry { + let seq = + u64::from_le_bytes(data[inner_start..inner_start + 8].try_into().map_err( + |_| WalError::TruncatedEntry { segment: segment_name.clone(), offset: inner_start as u64, - })?, - ); + }, + )?); if seq >= last_sequence { last_sequence = seq; } @@ -396,8 +392,7 @@ mod tests { fn test_segment_rotation() { let dir = TempDir::new().unwrap(); // Use a tiny max segment size to force rotation. - let mut writer = - WalWriter::open_with_max_size(dir.path(), SyncMode::Async, 100).unwrap(); + let mut writer = WalWriter::open_with_max_size(dir.path(), SyncMode::Async, 100).unwrap(); // Write entries until rotation occurs. for _ in 0..10 { @@ -456,11 +451,8 @@ mod tests { #[test] fn test_periodic_sync_mode() { let dir = TempDir::new().unwrap(); - let mut writer = WalWriter::open( - dir.path(), - SyncMode::Periodic(Duration::from_millis(10)), - ) - .unwrap(); + let mut writer = + WalWriter::open(dir.path(), SyncMode::Periodic(Duration::from_millis(10))).unwrap(); for _ in 0..5 { writer.append(test_entry(WalModality::Temporal)).unwrap();