use mem_llm::{EmbeddingsClient, RerankClient}; use mem_store::VectorStore; use sqlx::postgres::PgPoolOptions; /// M3 Composition Gate Test /// /// Verifies M3.1 (L2 synthesis) + M3.2 (rerank) + M3.3 (mem query) work together. /// Tests that the system can: /// 1. Return correct L1 nodes on known-answer questions /// 2. Have precise provenance (cited sources contain the facts) /// 3. Walk L2→L1→L0 edges correctly /// 4. Produce consistent results with reranking #[tokio::test] #[ignore] // Requires live database async fn a1_known_answer_kong_buffer() -> anyhow::Result<()> { let db_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://app:poimen@localhost:5432/memory".to_string()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&db_url) .await?; let embeddings = EmbeddingsClient::from_env()?; let reranker = RerankClient::from_env()?; let vector_store = VectorStore::new(pool); let question = "why did requests over 10KB fail?"; let embedding = embeddings.embed(question).await?; // Search L1 let results = vector_store.search_l1("poimen", &embedding, 5).await?; // Should find Kong buffer issue assert!(!results.is_empty(), "Should find L1 nodes"); let top = &results[0]; assert!(top.item.query_id == "infra-root-causes" || top.item.content.contains("Kong"), "Top result should be about Kong buffer, got: {}", top.item.content); Ok(()) } #[tokio::test] #[ignore] // Requires live database async fn a2_known_answer_auth_header() -> anyhow::Result<()> { let db_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://app:poimen@localhost:5432/memory".to_string()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&db_url) .await?; let embeddings = EmbeddingsClient::from_env()?; let vector_store = VectorStore::new(pool); let question = "why does Authorization header fail?"; let embedding = embeddings.embed(question).await?; let results = vector_store.search_l1("poimen", &embedding, 5).await?; // Should find auth-related findings if !results.is_empty() { let found = results.iter().any(|r| r.item.content.contains("auth") || r.item.content.contains("key") || r.item.query_id == "infra-root-causes" ); assert!(found, "Should find auth-related content"); } Ok(()) } #[tokio::test] #[ignore] // Requires live database and L2 node async fn a3_l2_two_hop_provenance() -> anyhow::Result<()> { let db_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://app:poimen@localhost:5432/memory".to_string()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&db_url) .await?; let embeddings = EmbeddingsClient::from_env()?; let vector_store = VectorStore::new(pool); let question = "what is the current state of this project?"; let embedding = embeddings.embed(question).await?; // Search L2 if let Some(l2_result) = vector_store.search_l2("poimen", &embedding).await? { let l2_sha = &l2_result.item.id; // Walk L2 -> L1 let l1_parents: Vec<(String,)> = sqlx::query_as( "SELECT parent_sha FROM memory_edge WHERE child_sha = $1" ) .bind(l2_sha) .fetch_all(vector_store.pool()) .await?; if !l1_parents.is_empty() { let l1_sha = &l1_parents[0].0; // Walk L1 -> L0 let l0_parents: Vec<(String,)> = sqlx::query_as( "SELECT parent_sha FROM memory_edge WHERE child_sha = $1" ) .bind(l1_sha) .fetch_all(vector_store.pool()) .await?; // Should have at least one L0 parent assert!(!l0_parents.is_empty(), "L1 should have L0 parents"); // Verify L0 nodes exist for (parent_sha,) in l0_parents { let exists: Option<(String,)> = sqlx::query_as( "SELECT sha256 FROM memory_node WHERE sha256 = $1" ) .bind(&parent_sha) .fetch_optional(vector_store.pool()) .await?; assert!(exists.is_some(), "Parent {} should exist", parent_sha); } } } Ok(()) } #[tokio::test] #[ignore] // Requires live database async fn a4_rerank_improves_order() -> anyhow::Result<()> { let db_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://app:poimen@localhost:5432/memory".to_string()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&db_url) .await?; let embeddings = EmbeddingsClient::from_env()?; let reranker = RerankClient::from_env()?; let vector_store = VectorStore::new(pool); let question = "why did requests over 10KB fail?"; let embedding = embeddings.embed(question).await?; // Get candidates (as if before reranking) let candidates = vector_store.search_l1("poimen", &embedding, 50).await?; if candidates.len() > 1 { let texts: Vec<&str> = candidates.iter().map(|c| c.item.content.as_str()).collect(); // Rerank let reranked = reranker.rerank(question, &texts).await?; // Verify reranker returns results assert!(!reranked.is_empty(), "Reranker should return results"); // Verify indices are valid for (idx, _score) in &reranked { assert!(*idx < texts.len(), "Index {} out of range {}", idx, texts.len()); } // Verify scores are descending let scores: Vec = reranked.iter().map(|(_idx, score)| *score).collect(); for i in 1..scores.len() { assert!(scores[i-1] >= scores[i], "Scores should be descending"); } } Ok(()) } #[tokio::test] #[ignore] // Requires live database async fn a5_no_cross_project_leakage() -> anyhow::Result<()> { let db_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://app:poimen@localhost:5432/memory".to_string()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&db_url) .await?; let embeddings = EmbeddingsClient::from_env()?; let vector_store = VectorStore::new(pool); let question = "infrastructure issue"; let embedding = embeddings.embed(question).await?; // Query poimen project let results = vector_store.search_l1("poimen", &embedding, 10).await?; // Verify all results are from poimen, not other projects for result in results { assert_eq!(result.item.project, "poimen", "Cross-project leakage detected"); } Ok(()) } #[tokio::test] #[ignore] // Requires live database async fn a6_level_consistency() -> anyhow::Result<()> { let db_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgresql://app:poimen@localhost:5432/memory".to_string()); let pool = PgPoolOptions::new() .max_connections(5) .connect(&db_url) .await?; // Check level consistency: every L1 has at least one L0 parent let l1_without_parents: Vec<(String,)> = sqlx::query_as( "SELECT n.sha256 FROM memory_node n WHERE n.project = $1 AND n.level = $2 AND NOT EXISTS ( SELECT 1 FROM memory_edge e WHERE e.child_sha = n.sha256 )" ) .bind("poimen") .bind("L1") .fetch_all(&pool) .await?; // Some L1 nodes may not have edges yet (e.g., fresh L2 synthesis) // but we should document this in the gate output if !l1_without_parents.is_empty() { println!("⚠ {} L1 nodes without parents (may be fresh L2)", l1_without_parents.len()); } Ok(()) } #[tokio::test] async fn a7_query_command_exists() -> anyhow::Result<()> { // Verify the mem query command is available let output = std::process::Command::new("./target/debug/mem") .arg("--help") .output(); assert!(output.is_ok(), "mem binary should exist"); let help_text = String::from_utf8(output?.stdout)?; assert!(help_text.contains("query") || help_text.contains("Query"), "Help should mention query command"); Ok(()) } #[tokio::test] async fn a8_verify_command_works() -> anyhow::Result<()> { // Verify mem verify command works (basic smoke test) let output = std::process::Command::new("./target/debug/mem") .arg("verify") .arg("--project") .arg("nonexistent") .output(); // Should not panic, even on nonexistent project assert!(output.is_ok(), "mem verify should not panic"); Ok(()) }