//! Validation tests for query IR, budgets, and owned port DTOs. #![allow(clippy::expect_used)] use context_core::{ ConfigurationRevision, GenerationId, OccurrenceId, ProfileId, SourceAuthority, SourceVersion, }; use context_core::{ DenseVector, PointId, SparseEntry, SparseVector, policy::{MAX_HNSW_CANDIDATE_MASK_POINTS, MAX_RECALL_CHECK_POINT_IDS, MAX_VECTOR_DIMENSIONS}, }; use context_query::{ Candidate, CandidateBranch, CandidateDiagnostics, CandidateProvenance, CandidateSourceKind, ExecutionBudget, Formula, Fusion, LexicalQuery, LexicalSourceName, LexicalText, MAX_LATE_INTERACTION_SCALAR_CELLS, QueryError, QueryIr, QueryKind, ScoreOrder, }; #[test] fn execution_budget_rejects_every_zero_dimension() { let budgets = [ (0, 1, 1, 1, 1, 1), (1, 0, 1, 1, 1, 1), (1, 1, 0, 1, 1, 1), (1, 1, 1, 0, 1, 1), (1, 1, 1, 1, 0, 1), (1, 1, 1, 1, 1, 0), ]; for (candidates, filters, rechecks, stages, expansions, results) in budgets { assert!(matches!( ExecutionBudget::new(candidates, filters, rechecks, stages, expansions, results), Err(QueryError::InvalidInput { .. }) )); } } #[test] fn direct_late_interaction_ir_rejects_oversized_scalar_cells() { let vector = DenseVector::new(vec![1.0; MAX_VECTOR_DIMENSIONS]).expect("bounded vector"); let vector_count = MAX_LATE_INTERACTION_SCALAR_CELLS .checked_div(MAX_VECTOR_DIMENSIONS) .unwrap_or_default() .saturating_add(1); assert!(matches!( QueryIr::new( QueryKind::LateInteraction { vectors: vec![vector; vector_count], candidates_per_query: context_core::SearchLimit::new(1).expect("one"), }, ScoreOrder::HigherIsBetter, None, 1, ), Err(QueryError::InvalidInput { field: "query_vectors", .. }) )); } #[test] fn direct_ir_rejects_contradictory_fixed_score_orders() { let lexical = QueryIr::new( QueryKind::Lexical { source: LexicalSourceName::new("body").expect("lexical source"), query: LexicalQuery::Plain(LexicalText::new("rust").expect("lexical text")), }, ScoreOrder::LowerIsBetter, None, 1, ); assert!(matches!( lexical, Err(QueryError::InvalidInput { field: "score_order", .. }) )); let child = QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::LowerIsBetter, None, 1) .expect("nearest child"); let wrapper = QueryIr::new( QueryKind::Rerank { query: Box::new(child), }, ScoreOrder::HigherIsBetter, None, 1, ); assert!(matches!( wrapper, Err(QueryError::InvalidInput { field: "score_order", .. }) )); } #[test] fn direct_ir_rejects_duplicate_lookup_points() { let lookup = QueryIr::new( QueryKind::Lookup { point_ids: vec![PointId::new(7), PointId::new(7)], }, ScoreOrder::HigherIsBetter, None, 2, ); assert!(matches!( lookup, Err(QueryError::InvalidInput { field: "point_ids", .. }) )); } #[test] fn query_ir_rejects_invalid_recursive_semantics() { let nearest = QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, None, 2) .expect("nearest query should be valid"); assert!(matches!( QueryIr::new( QueryKind::Weighted { query: Box::new(nearest.clone()), weight: -1.0, }, ScoreOrder::HigherIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "weight", .. }) )); assert!(matches!( QueryIr::new( QueryKind::ScoreThreshold { query: Box::new(nearest), minimum: Some(2.0), maximum: Some(1.0), }, ScoreOrder::HigherIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "score_threshold", .. }) )); } #[test] fn query_ir_requires_filters_on_executable_leaf_branches() { let nearest = QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, None, 2) .expect("nearest query should be valid"); assert!(matches!( QueryIr::new( QueryKind::Rerank { query: Box::new(nearest), }, ScoreOrder::HigherIsBetter, Some(serde_json::json!({ "must": [{"key": "tenant", "match": {"value": "acme"}}] })), 2, ), Err(QueryError::InvalidInput { field: "filter", .. }) )); } #[test] fn prefetch_requires_higher_is_better_fusion_order() { let branch = QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, None, 2) .expect("nearest query should be valid"); assert!(matches!( QueryIr::new( QueryKind::Prefetch { branches: vec![branch], fusion: Fusion::STANDARD_RRF, }, ScoreOrder::LowerIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "score_order", .. }) )); } #[test] fn composite_tree_reports_the_largest_descendant_limit() { let branch = QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, None, 8) .expect("nearest query should be valid"); let query = QueryIr::new( QueryKind::Rerank { query: Box::new(branch), }, ScoreOrder::HigherIsBetter, None, 2, ) .expect("rerank query should be valid"); assert_eq!(query.max_node_limit(), 8); } #[test] fn named_source_leaves_validate_lexical_and_late_interaction_inputs() { let lexical = QueryIr::lexical( LexicalSourceName::new("body").expect("lexical source"), LexicalQuery::Plain(LexicalText::new("rust postgres").expect("lexical text")), None, 5, ) .expect("lexical leaf should be valid"); assert!(matches!(lexical.kind(), QueryKind::Lexical { .. })); let late = QueryIr::late_interaction(vec![vec![1.0, 0.0], vec![0.0, 1.0]], 8, 3) .expect("late-interaction leaf should be valid"); assert!(matches!(late.kind(), QueryKind::LateInteraction { .. })); assert!(LexicalSourceName::new("body;drop").is_err()); assert!(QueryIr::late_interaction(vec![vec![1.0], vec![1.0, 2.0]], 2, 1).is_err()); } #[test] fn candidate_scores_must_be_finite() { let provenance = CandidateProvenance::new( OccurrenceId::new(1).expect("non-zero occurrence"), CandidateBranch::DenseAnn, CandidateSourceKind::Hnsw, ScoreOrder::LowerIsBetter, SourceAuthority::DerivedArtifact, ) .with_generation(GenerationId::new(3).expect("non-zero generation")) .with_configuration(ConfigurationRevision::new(4).expect("non-zero configuration")) .with_profile(ProfileId::new(5).expect("non-zero profile")); assert!(matches!( Candidate::new(PointId::new(1), f64::NAN, provenance), Err(QueryError::InvalidInput { field: "candidate_score", .. }) )); } #[test] fn candidate_branch_and_source_registries_are_exhaustive() { let branches = [ CandidateBranch::DenseExact, CandidateBranch::DenseAnn, CandidateBranch::Lexical, CandidateBranch::Sparse, CandidateBranch::MultiVector, CandidateBranch::Quantized, CandidateBranch::Recommend, CandidateBranch::Discover, CandidateBranch::Lookup, CandidateBranch::Topology, CandidateBranch::UserProvided, CandidateBranch::Fuzzy, CandidateBranch::MultiProfile, ]; let sources = [ CandidateSourceKind::Exact, CandidateSourceKind::Hnsw, CandidateSourceKind::IvfFlat, CandidateSourceKind::Lexical, CandidateSourceKind::Sparse, CandidateSourceKind::MultiVector, CandidateSourceKind::Quantized, CandidateSourceKind::Recommendation, CandidateSourceKind::Discovery, CandidateSourceKind::Lookup, CandidateSourceKind::UserProvided, CandidateSourceKind::Topology, CandidateSourceKind::Fuzzy, ]; assert_eq!( branches.map(CandidateBranch::stable_name), [ "dense_exact", "dense_ann", "lexical", "sparse", "multi_vector", "quantized", "recommend", "discover", "lookup", "topology", "user_provided", "fuzzy", "multi_profile", ] ); assert_eq!( branches.map(CandidateBranch::stable_code), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ); assert_eq!( sources.map(CandidateSourceKind::stable_name), [ "exact", "hnsw", "ivf_flat", "lexical", "sparse", "multi_vector", "quantized", "recommendation", "discovery", "lookup", "user_provided", "topology", "fuzzy", ] ); assert_eq!( sources.map(CandidateSourceKind::stable_code), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] ); } #[test] fn candidate_envelope_preserves_typed_provenance_and_scores() -> Result<(), QueryError> { let provenance = CandidateProvenance::new( OccurrenceId::new(11).expect("non-zero occurrence"), CandidateBranch::DenseAnn, CandidateSourceKind::Hnsw, ScoreOrder::LowerIsBetter, SourceAuthority::DerivedArtifact, ) .with_generation(GenerationId::new(12).expect("non-zero generation")) .with_configuration(ConfigurationRevision::new(13).expect("non-zero configuration")) .with_profile(ProfileId::new(14).expect("non-zero profile")) .with_source_version(SourceVersion::new(15).expect("non-zero source version")); let diagnostics = CandidateDiagnostics::new(3, 21); let candidate = Candidate::new(PointId::new(7), 0.25, provenance)? .with_exact_score(0.2)? .with_diagnostics(diagnostics); assert_eq!(candidate.approximate_score(), 0.25); assert_eq!(candidate.exact_score(), Some(0.2)); assert_eq!(candidate.provenance().occurrence_id().get(), 11); assert_eq!( candidate.provenance().generation().map(GenerationId::get), Some(12) ); assert_eq!( candidate .provenance() .configuration() .map(ConfigurationRevision::get), Some(13) ); assert_eq!( candidate.provenance().profile().map(ProfileId::get), Some(14) ); assert_eq!( candidate .provenance() .source_version() .map(SourceVersion::get), Some(15) ); assert_eq!(candidate.diagnostics(), diagnostics); assert_eq!(candidate.diagnostics().source_rank(), 3); assert_eq!(candidate.diagnostics().work_units(), 21); Ok(()) } #[test] fn sparse_nearest_ir_preserves_named_sparse_vector() { let vector = SparseVector::new( 8, vec![ SparseEntry::new(1, 0.5).expect("entry should be valid"), SparseEntry::new(6, 1.0).expect("entry should be valid"), ], ) .expect("sparse vector should be valid"); let query = QueryIr::sparse_nearest( "keywords".to_owned(), vector.clone(), ScoreOrder::LowerIsBetter, None, 3, ) .expect("sparse nearest query should be valid"); assert!(matches!( query.kind(), QueryKind::SparseNearest { vector: stored, .. } if stored == &vector )); } #[test] fn query_ir_rejects_unbounded_recursive_depth() { let mut query = QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, None, 2) .expect("nearest query should be valid"); for _ in 0..31 { query = QueryIr::new( QueryKind::Weighted { query: Box::new(query), weight: 1.0, }, ScoreOrder::HigherIsBetter, None, 2, ) .expect("query at or below the depth limit should be valid"); } assert!(matches!( QueryIr::new( QueryKind::Weighted { query: Box::new(query), weight: 1.0, }, ScoreOrder::HigherIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "query", .. }) )); } #[test] fn execution_budget_rejects_values_above_policy_ceilings() { assert!(matches!( ExecutionBudget::new(usize::MAX, 1, 1, 1, 1, 1), Err(QueryError::InvalidInput { field: "max_candidates", .. }) )); assert!(matches!( ExecutionBudget::new( 1, MAX_HNSW_CANDIDATE_MASK_POINTS.saturating_add(1), 1, 1, 1, 1, ), Err(QueryError::InvalidInput { field: "max_filter_candidates", .. }) )); assert!( ExecutionBudget::new(1, MAX_HNSW_CANDIDATE_MASK_POINTS, 1, 1, 1, 1).is_ok(), "the query budget must admit the configured HNSW mask ceiling" ); assert!(matches!( ExecutionBudget::new(1, 1, 1, usize::MAX, 1, 1), Err(QueryError::InvalidInput { field: "max_stages", .. }) )); assert!(matches!( ExecutionBudget::new(1, 1, 1, 1, usize::MAX, 1), Err(QueryError::InvalidInput { field: "max_expansions", .. }) )); } #[test] fn query_ir_rejects_oversized_point_lists_and_filters_before_encoding() { let points = (0..=MAX_RECALL_CHECK_POINT_IDS) .map(|point_id| PointId::new(point_id as u64)) .collect::>(); assert!(matches!( QueryIr::new( QueryKind::Recommend { positive: points, negative: Vec::new(), }, ScoreOrder::LowerIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "recommend", .. }) )); let oversized_filter = serde_json::json!({ "must": [{ "key": "tenant", "match": {"any": (0..300).collect::>()} }] }); assert!(matches!( QueryIr::nearest( None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, Some(oversized_filter), 2, ), Err(QueryError::InvalidInput { field: "filter", reason, }) if reason == "exceeds maximum node count" )); let oversized_scalar_filter = serde_json::json!({ "must": [{ "key": "tenant", "match": {"value": "x".repeat(64 * 1024 + 1)} }] }); assert!(matches!( QueryIr::nearest( None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, Some(oversized_scalar_filter), 2, ), Err(QueryError::InvalidInput { field: "filter", reason, }) if reason == "scalar bytes exceed policy maximum" )); } #[test] fn query_ir_owns_ordered_lookup_and_formula_shapes() { assert!(matches!( QueryIr::new( QueryKind::Lookup { point_ids: Vec::new(), }, ScoreOrder::HigherIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "point_ids", .. }) )); let lookup = QueryIr::new( QueryKind::Lookup { point_ids: vec![PointId::new(7), PointId::new(8)], }, ScoreOrder::HigherIsBetter, None, 2, ) .expect("ordered lookup should be valid"); QueryIr::new( QueryKind::Formula { query: Box::new(lookup), formula: Formula::new("$score * 0.5").expect("formula should be valid"), }, ScoreOrder::HigherIsBetter, None, 2, ) .expect("formula query should be valid"); } #[test] fn weighted_rrf_requires_a_finite_positive_total_weight() { let weighted = |weight| { QueryIr::new( QueryKind::Weighted { query: Box::new( QueryIr::nearest(None, vec![1.0, 0.0], ScoreOrder::HigherIsBetter, None, 2) .expect("leaf query"), ), weight, }, ScoreOrder::HigherIsBetter, None, 2, ) .expect("individual weight") }; for branches in [ vec![weighted(0.0), weighted(0.0)], vec![weighted(f64::MAX), weighted(f64::MAX)], ] { assert!(matches!( QueryIr::new( QueryKind::Prefetch { branches, fusion: Fusion::WeightedRrf { rank_constant: 60 }, }, ScoreOrder::HigherIsBetter, None, 2, ), Err(QueryError::InvalidInput { field: "branches", .. }) )); } }