diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index d7eaf92f..c2d107f2 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -77,6 +77,24 @@ jobs: - name: clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: Trust-surface — loomweave-core must not link an HTTP client + # PRD-0001 / clarion-141e9c08c8: the plugin-supervisor + SEI crate must + # not carry an outbound HTTP client; provider HTTP lives in loomweave-llm. + # `--prefix none` flattens the tree so the anchor matches an indented dep + # (a plain `^reqwest` against the tree output never matches and would pass + # vacuously). `reqwest` stays legitimate in federation/cli — this is a + # per-crate ban cargo-deny's [bans] cannot express. + run: | + set -euo pipefail + # Capture first so a `cargo tree` failure exits the step (under `set -e`) + # instead of being swallowed by the pipe and read as "no reqwest". + deps="$(cargo tree -p loomweave-core --edges normal --prefix none)" + if grep -qE '^reqwest v' <<<"$deps"; then + echo "::error::loomweave-core links reqwest; the provider HTTP must stay in loomweave-llm (PRD-0001)" + exit 1 + fi + echo "OK: loomweave-core has no reqwest in its dependency tree" + - name: install cargo-nextest uses: taiki-e/install-action@e310bff3ef77234d477d6bb655da153a5c49d1db diff --git a/Cargo.lock b/Cargo.lock index 683c1081..766cc3db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1081,6 +1081,7 @@ dependencies = [ "loomweave-analysis", "loomweave-core", "loomweave-federation", + "loomweave-llm", "loomweave-mcp", "loomweave-plugin-fixture", "loomweave-scanner", @@ -1108,10 +1109,7 @@ dependencies = [ name = "loomweave-core" version = "1.3.1" dependencies = [ - "async-trait", - "fs2", "nix", - "reqwest", "serde", "serde_json", "tempfile", @@ -1136,6 +1134,22 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "loomweave-llm" +version = "1.3.1" +dependencies = [ + "async-trait", + "fs2", + "reqwest", + "serde", + "serde_json", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tracing", + "which", +] + [[package]] name = "loomweave-mcp" version = "1.3.1" @@ -1144,6 +1158,7 @@ dependencies = [ "blake3", "loomweave-core", "loomweave-federation", + "loomweave-llm", "loomweave-storage", "nix", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index b8bbd8cb..8d9c7890 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "3" members = [ "crates/loomweave-analysis", "crates/loomweave-core", + "crates/loomweave-llm", "crates/loomweave-federation", "crates/loomweave-storage", "crates/loomweave-cli", diff --git a/crates/loomweave-cli/Cargo.toml b/crates/loomweave-cli/Cargo.toml index a9c43976..cb3f8edd 100644 --- a/crates/loomweave-cli/Cargo.toml +++ b/crates/loomweave-cli/Cargo.toml @@ -20,6 +20,7 @@ axum.workspace = true blake3.workspace = true clap.workspace = true loomweave-core = { path = "../loomweave-core", version = "1.3.1" } +loomweave-llm = { path = "../loomweave-llm", version = "1.3.1" } loomweave-analysis = { path = "../loomweave-analysis", version = "1.3.1" } loomweave-federation = { path = "../loomweave-federation", version = "1.3.1" } loomweave-mcp = { path = "../loomweave-mcp", version = "1.3.1" } diff --git a/crates/loomweave-cli/src/analyze.rs b/crates/loomweave-cli/src/analyze.rs index 48482635..520f9092 100644 --- a/crates/loomweave-cli/src/analyze.rs +++ b/crates/loomweave-cli/src/analyze.rs @@ -25,9 +25,10 @@ use uuid::Uuid; use loomweave_core::plugin::host::FINDING_PLUGIN_ABORTED; use loomweave_core::{ AcceptedEdge, AcceptedEntity, AnalyzeFileOutcome, CrashLoopBreaker, CrashLoopState, - DiscoveredPlugin, EmbeddingProvider, FINDING_DISABLED_CRASH_LOOP, HostError, HostFinding, - UnresolvedCallSite, discover, + DiscoveredPlugin, FINDING_DISABLED_CRASH_LOOP, HostError, HostFinding, UnresolvedCallSite, + discover, }; +use loomweave_llm::EmbeddingProvider; use loomweave_storage::{ DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, EmbeddingKey, EmbeddingStore, GitRename, NewEntityDescriptor, PriorIndexEntry, SeiBindingRecord, SeiDecision, SeiLineageEntry, @@ -8061,8 +8062,8 @@ mod tests { async fn semantic_embedding_population_skips_fresh_sidecar_rows() { use std::sync::Arc; - use loomweave_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; use loomweave_federation::config::SemanticSearchConfig; + use loomweave_llm::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; use loomweave_storage::{EmbeddingKey, EmbeddingStore, pragma, schema}; let project = tempfile::tempdir().unwrap(); @@ -8130,8 +8131,8 @@ mod tests { async fn semantic_embedding_population_skips_briefing_blocked_entities() { use std::sync::Arc; - use loomweave_core::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; use loomweave_federation::config::SemanticSearchConfig; + use loomweave_llm::{EmbeddingProvider, EmbeddingRecording, RecordingEmbeddingProvider}; use loomweave_storage::{pragma, schema}; let project = tempfile::tempdir().unwrap(); diff --git a/crates/loomweave-cli/src/serve.rs b/crates/loomweave-cli/src/serve.rs index 664f1fb8..87356a82 100644 --- a/crates/loomweave-cli/src/serve.rs +++ b/crates/loomweave-cli/src/serve.rs @@ -6,17 +6,17 @@ use std::thread; use std::time::Duration; use anyhow::{Context, Result, anyhow}; -use loomweave_core::{ - ApiEmbeddingProvider, ApiEmbeddingProviderConfig, ClaudeCliProvider, ClaudeCliProviderConfig, - CodexCliProvider, CodexCliProviderConfig, EmbeddingProvider, EmbeddingProviderError, - LlmProvider, OpenRouterProvider, OpenRouterProviderConfig, Recording, RecordingProvider, - TrafficLoggingProvider, -}; use loomweave_federation::config::{ LlmConfig, McpConfig, ProviderSelection, SemanticProviderKind, SemanticSearchConfig, select_provider_with_env, }; use loomweave_federation::filigree::FiligreeHttpClient; +use loomweave_llm::{ + ApiEmbeddingProvider, ApiEmbeddingProviderConfig, ClaudeCliProvider, ClaudeCliProviderConfig, + CodexCliProvider, CodexCliProviderConfig, EmbeddingProvider, EmbeddingProviderError, + LlmProvider, OpenRouterProvider, OpenRouterProviderConfig, Recording, RecordingProvider, + TrafficLoggingProvider, +}; use loomweave_storage::{DEFAULT_BATCH_SIZE, DEFAULT_CHANNEL_CAPACITY, ReaderPool, Writer}; pub fn run(path: &Path, config_path: Option<&Path>) -> Result<()> { diff --git a/crates/loomweave-cli/tests/serve.rs b/crates/loomweave-cli/tests/serve.rs index 9d6712d1..00d165ee 100644 --- a/crates/loomweave-cli/tests/serve.rs +++ b/crates/loomweave-cli/tests/serve.rs @@ -10,10 +10,8 @@ use std::time::{Duration, Instant}; use assert_cmd::Command; use hmac::{Hmac, Mac}; -use loomweave_core::{ - LEAF_SUMMARY_PROMPT_TEMPLATE_ID, - plugin::{ContentLengthCeiling, Frame, read_frame, write_frame}, -}; +use loomweave_core::plugin::{ContentLengthCeiling, Frame, read_frame, write_frame}; +use loomweave_llm::LEAF_SUMMARY_PROMPT_TEMPLATE_ID; use rusqlite::{Connection, params}; use serde::Deserialize; use serde_json::Value; diff --git a/crates/loomweave-core/Cargo.toml b/crates/loomweave-core/Cargo.toml index 1bc5a4e8..db1cb835 100644 --- a/crates/loomweave-core/Cargo.toml +++ b/crates/loomweave-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "loomweave-core" -description = "Loomweave core: entity-ID assembler, sandboxed JSON-RPC plugin host, manifest parser, and LLM/embedding provider traits." +description = "Loomweave core: entity-ID assembler, sandboxed JSON-RPC plugin host, and manifest parser." version.workspace = true edition.workspace = true license.workspace = true @@ -11,9 +11,6 @@ rust-version.workspace = true workspace = true [dependencies] -async-trait.workspace = true -fs2.workspace = true -reqwest.workspace = true serde.workspace = true serde_json.workspace = true tempfile.workspace = true diff --git a/crates/loomweave-core/src/lib.rs b/crates/loomweave-core/src/lib.rs index a3ce6582..e038906f 100644 --- a/crates/loomweave-core/src/lib.rs +++ b/crates/loomweave-core/src/lib.rs @@ -1,4 +1,4 @@ -//! loomweave-core — domain types, identifiers, and provider traits. +//! loomweave-core — domain types, identifiers, and the sandboxed plugin host. //! //! # Re-export policy (ticket clarion-29acbcd042) //! @@ -6,29 +6,15 @@ //! root. Implementation types (`Frame`, `TransportError`, `RequestEnvelope`, etc.) //! remain accessible via `loomweave_core::plugin::transport::*` and siblings. -pub mod embedding_provider; pub mod entity_id; pub mod errors; pub mod hardened_git; -pub mod llm_provider; pub mod plugin; pub mod store; -pub use embedding_provider::{ - ApiEmbeddingProvider, ApiEmbeddingProviderConfig, EmbeddingProvider, EmbeddingProviderError, - EmbeddingRecording, RecordingEmbeddingProvider, -}; pub use entity_id::{EntityId, EntityIdError, entity_id}; pub use errors::{HttpErrorCode, McpErrorCode}; pub use hardened_git::{hardened_git_command, list_untracked_files}; -pub use llm_provider::{ - CachingModel, ClaudeCliProvider, ClaudeCliProviderConfig, CodexCliProvider, - CodexCliProviderConfig, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, - LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmProvider, LlmProviderError, - LlmPurpose, LlmRequest, LlmResponse, OpenRouterProvider, OpenRouterProviderConfig, - PromptTemplate, Recording, RecordingProvider, TrafficLoggingProvider, - build_coding_agent_provider_prompt, build_inferred_calls_prompt, build_leaf_summary_prompt, -}; pub use plugin::{ // host (Task 6) — facade for callers that spawn/connect plugins AcceptedEdge, diff --git a/crates/loomweave-llm/Cargo.toml b/crates/loomweave-llm/Cargo.toml new file mode 100644 index 00000000..988169ca --- /dev/null +++ b/crates/loomweave-llm/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "loomweave-llm" +description = "Loomweave LLM + embedding provider traits, concrete providers (OpenRouter / Codex CLI / Claude CLI), and the outbound HTTP/CLI transport for summaries and embeddings." +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +async-trait.workspace = true +fs2.workspace = true +reqwest.workspace = true +serde.workspace = true +serde_json.workspace = true +tempfile.workspace = true +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true +which.workspace = true diff --git a/crates/loomweave-core/src/embedding_provider.rs b/crates/loomweave-llm/src/embedding_provider.rs similarity index 100% rename from crates/loomweave-core/src/embedding_provider.rs rename to crates/loomweave-llm/src/embedding_provider.rs diff --git a/crates/loomweave-llm/src/lib.rs b/crates/loomweave-llm/src/lib.rs new file mode 100644 index 00000000..9da0efba --- /dev/null +++ b/crates/loomweave-llm/src/lib.rs @@ -0,0 +1,21 @@ +//! loomweave-llm — LLM + embedding provider traits, concrete providers, and the +//! outbound HTTP/CLI transport for Loomweave summaries and embeddings. +//! +//! Extracted from `loomweave-core` (PRD-0001, clarion-141e9c08c8) so the +//! plugin-supervisor + SEI crate does not link an outbound HTTP client. + +pub mod embedding_provider; +pub mod llm_provider; + +pub use embedding_provider::{ + ApiEmbeddingProvider, ApiEmbeddingProviderConfig, EmbeddingProvider, EmbeddingProviderError, + EmbeddingRecording, RecordingEmbeddingProvider, +}; +pub use llm_provider::{ + CachingModel, ClaudeCliProvider, ClaudeCliProviderConfig, CodexCliProvider, + CodexCliProviderConfig, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, + LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmProvider, LlmProviderError, + LlmPurpose, LlmRequest, LlmResponse, OpenRouterProvider, OpenRouterProviderConfig, + PromptTemplate, Recording, RecordingProvider, TrafficLoggingProvider, + build_coding_agent_provider_prompt, build_inferred_calls_prompt, build_leaf_summary_prompt, +}; diff --git a/crates/loomweave-core/src/llm_provider.rs b/crates/loomweave-llm/src/llm_provider.rs similarity index 100% rename from crates/loomweave-core/src/llm_provider.rs rename to crates/loomweave-llm/src/llm_provider.rs diff --git a/crates/loomweave-mcp/Cargo.toml b/crates/loomweave-mcp/Cargo.toml index 0cddcfa7..68c13c16 100644 --- a/crates/loomweave-mcp/Cargo.toml +++ b/crates/loomweave-mcp/Cargo.toml @@ -14,6 +14,7 @@ workspace = true async-trait.workspace = true blake3.workspace = true loomweave-core = { path = "../loomweave-core", version = "1.3.1" } +loomweave-llm = { path = "../loomweave-llm", version = "1.3.1" } loomweave-federation = { path = "../loomweave-federation", version = "1.3.1" } loomweave-storage = { path = "../loomweave-storage", version = "1.3.1" } reqwest.workspace = true diff --git a/crates/loomweave-mcp/src/lib.rs b/crates/loomweave-mcp/src/lib.rs index d7ca86b8..caf00cd8 100644 --- a/crates/loomweave-mcp/src/lib.rs +++ b/crates/loomweave-mcp/src/lib.rs @@ -15,10 +15,8 @@ use std::collections::{BTreeSet, HashMap}; use std::path::{Component, Path, PathBuf}; use std::sync::{Arc, Mutex}; -use loomweave_core::{ - EdgeConfidence, EmbeddingProvider, LlmProvider, LlmProviderError, LlmRequest, LlmResponse, - McpErrorCode, -}; +use loomweave_core::{EdgeConfidence, McpErrorCode}; +use loomweave_llm::{EmbeddingProvider, LlmProvider, LlmProviderError, LlmRequest, LlmResponse}; use rusqlite::{Connection, OpenFlags, OptionalExtension}; use serde::ser::SerializeStruct; use serde::{Deserialize, Serialize}; @@ -6128,7 +6126,7 @@ mod tests { use std::sync::Arc; use std::time::Duration; - use loomweave_core::{CachingModel, LlmProvider, LlmProviderError, LlmRequest, LlmResponse}; + use loomweave_llm::{CachingModel, LlmProvider, LlmProviderError, LlmRequest, LlmResponse}; use loomweave_storage::{ EntityRow, InferredEdgeCacheKey, ReaderPool, UnresolvedCallSiteRow, pragma, schema, }; diff --git a/crates/loomweave-mcp/src/tools/status.rs b/crates/loomweave-mcp/src/tools/status.rs index 90f2d936..b8f1bbd2 100644 --- a/crates/loomweave-mcp/src/tools/status.rs +++ b/crates/loomweave-mcp/src/tools/status.rs @@ -6,7 +6,8 @@ use std::collections::HashMap; -use loomweave_core::{LeafSummaryPromptInput, McpErrorCode, build_leaf_summary_prompt}; +use loomweave_core::McpErrorCode; +use loomweave_llm::{LeafSummaryPromptInput, build_leaf_summary_prompt}; use serde_json::{Value, json}; use loomweave_storage::{ diff --git a/crates/loomweave-mcp/src/tools/summary.rs b/crates/loomweave-mcp/src/tools/summary.rs index 3907bcb9..f43c81f9 100644 --- a/crates/loomweave-mcp/src/tools/summary.rs +++ b/crates/loomweave-mcp/src/tools/summary.rs @@ -8,10 +8,11 @@ use std::collections::HashSet; use std::path::Path; use std::sync::Arc; -use loomweave_core::{ - EdgeConfidence, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, - LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmPurpose, LlmRequest, McpErrorCode, - build_inferred_calls_prompt, build_leaf_summary_prompt, +use loomweave_core::{EdgeConfidence, McpErrorCode}; +use loomweave_llm::{ + INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, LEAF_SUMMARY_PROMPT_TEMPLATE_ID, + LeafSummaryPromptInput, LlmPurpose, LlmRequest, build_inferred_calls_prompt, + build_leaf_summary_prompt, }; use serde_json::{Value, json}; use tokio::sync::{broadcast, mpsc, oneshot}; diff --git a/crates/loomweave-mcp/tests/catalogue_tools.rs b/crates/loomweave-mcp/tests/catalogue_tools.rs index a9aea1c2..748f2236 100644 --- a/crates/loomweave-mcp/tests/catalogue_tools.rs +++ b/crates/loomweave-mcp/tests/catalogue_tools.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use loomweave_core::{EmbeddingRecording, RecordingEmbeddingProvider}; +use loomweave_llm::{EmbeddingRecording, RecordingEmbeddingProvider}; use loomweave_mcp::config::SemanticSearchConfig; use loomweave_mcp::filigree::{ EntityAssociationsResponse, FiligreeClientError, FiligreeLookup, WardlineFinding, diff --git a/crates/loomweave-mcp/tests/storage_tools.rs b/crates/loomweave-mcp/tests/storage_tools.rs index f9ee5f58..33d4b880 100644 --- a/crates/loomweave-mcp/tests/storage_tools.rs +++ b/crates/loomweave-mcp/tests/storage_tools.rs @@ -8,12 +8,6 @@ use std::{ }, }; -use loomweave_core::{ - CachingModel, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, - LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmProvider, LlmProviderError, - LlmPurpose, LlmRequest, LlmResponse, OpenRouterProvider, OpenRouterProviderConfig, Recording, - RecordingProvider, build_inferred_calls_prompt, build_leaf_summary_prompt, -}; use loomweave_federation::{ loomweave_port::publish_port, loomweave_url::{ @@ -21,6 +15,12 @@ use loomweave_federation::{ SOURCE_NONE as LOOMWEAVE_SOURCE_NONE, }, }; +use loomweave_llm::{ + CachingModel, INFERRED_CALLS_PROMPT_VERSION, InferredCallsPromptInput, + LEAF_SUMMARY_PROMPT_TEMPLATE_ID, LeafSummaryPromptInput, LlmProvider, LlmProviderError, + LlmPurpose, LlmRequest, LlmResponse, OpenRouterProvider, OpenRouterProviderConfig, Recording, + RecordingProvider, build_inferred_calls_prompt, build_leaf_summary_prompt, +}; use loomweave_mcp::{ DiagnosticsContext, LlmDiagnostics, McpToolPolicy, ServerState, config::{FiligreeConfig, LlmConfig, LlmProviderKind}, diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index c16b9de8..305f650e 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -117,7 +117,7 @@ const quickStart = - + @@ -338,6 +338,14 @@ const quickStart = + + Loomweave maps structure and mints identity; it does not scan for + vulnerabilities, render a SAST verdict, or decide who may touch what. The + SEI is a stable coordinate, not a credential — keying a fact on it + grants no authority. Loomweave is deconfliction-first, not security; never treat + its structural graph or its identities as an access-control or compliance boundary. + +
    {SEI_SPINE.facts.map((fact) =>
  • {fact}
  • )}