use std::any::Any;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use asupersync::Cx;
use serde::{Deserialize, Serialize};
use crate::error::{SearchError, SearchResult};
use crate::generation::{EmbeddingIdentityBundleV1, QuantizationFormat};
use crate::types::{
EmbeddingMetrics, IndexMetrics, IndexableDocument, ScoredResult, SearchMetrics,
};
pub type SearchFuture<'a, T> = Pin<Box<dyn Future<Output = SearchResult<T>> + Send + 'a>>;
fn bounded_embedder_diagnostic_id(id: &str) -> String {
if !id.is_empty()
&& id.len() <= 128
&& id
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'))
{
id.to_owned()
} else {
"<redacted-embedder-id>".to_owned()
}
}
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct IdentityBoundEmbedding {
pub values: Vec<f32>,
pub identity: EmbeddingIdentityBundleV1,
}
impl fmt::Debug for IdentityBoundEmbedding {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("IdentityBoundEmbedding")
.field("dimension", &self.values.len())
.field("identity", &self.identity.fingerprint())
.finish_non_exhaustive()
}
}
impl IdentityBoundEmbedding {
pub fn validate(&self) -> SearchResult<()> {
self.identity.validate()?;
let declared_dimension = usize::try_from(self.identity.space.dimension).map_err(|_| {
SearchError::InvalidConfig {
field: "identity_bound_embedding.dimension".to_owned(),
value: self.identity.space.dimension.to_string(),
reason: "dimension does not fit usize".to_owned(),
}
})?;
if self.values.len() != declared_dimension {
return Err(SearchError::InvalidConfig {
field: "identity_bound_embedding.values".to_owned(),
value: self.values.len().to_string(),
reason: format!("expected {declared_dimension} vector elements"),
});
}
if self.identity.storage.quantization != QuantizationFormat::F32 {
return Err(SearchError::InvalidConfig {
field: "identity_bound_embedding.storage.quantization".to_owned(),
value: format!("{:?}", self.identity.storage.quantization),
reason: "an in-process Vec<f32> output must carry an f32 storage identity"
.to_owned(),
});
}
if !self.identity.storage.format.starts_with("in-memory-") {
return Err(SearchError::InvalidConfig {
field: "identity_bound_embedding.storage.format".to_owned(),
value: self.identity.storage.format.clone(),
reason: "an in-process Vec<f32> output must carry an in-memory storage format"
.to_owned(),
});
}
if !matches!(
self.identity.storage.endianness.as_str(),
"native-f32-values" | "native-test-only"
) {
return Err(SearchError::InvalidConfig {
field: "identity_bound_embedding.storage.endianness".to_owned(),
value: self.identity.storage.endianness.clone(),
reason: "an in-process Vec<f32> output must carry a native-value contract"
.to_owned(),
});
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ModelCategory {
HashEmbedder,
StaticEmbedder,
TransformerEmbedder,
ApiEmbedder,
}
impl fmt::Display for ModelCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HashEmbedder => write!(f, "hash_embedder"),
Self::StaticEmbedder => write!(f, "static_embedder"),
Self::TransformerEmbedder => write!(f, "transformer_embedder"),
Self::ApiEmbedder => write!(f, "api_embedder"),
}
}
}
impl ModelCategory {
#[must_use]
pub const fn default_tier(self) -> ModelTier {
match self {
Self::HashEmbedder | Self::StaticEmbedder => ModelTier::Fast,
Self::TransformerEmbedder | Self::ApiEmbedder => ModelTier::Quality,
}
}
#[must_use]
pub const fn default_semantic_flag(self) -> bool {
!matches!(self, Self::HashEmbedder)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ModelTier {
Fast,
Quality,
}
impl fmt::Display for ModelTier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Fast => write!(f, "fast"),
Self::Quality => write!(f, "quality"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ModelInfo {
pub id: String,
pub name: String,
pub dimension: usize,
pub category: ModelCategory,
pub tier: ModelTier,
pub is_semantic: bool,
pub supports_mrl: bool,
pub huggingface_id: Option<String>,
pub size_bytes: Option<u64>,
pub license: Option<String>,
}
pub trait Embedder: Send + Sync {
fn embed<'a>(&'a self, cx: &'a Cx, text: &'a str) -> SearchFuture<'a, Vec<f32>>;
fn embed_batch<'a>(
&'a self,
cx: &'a Cx,
texts: &'a [&'a str],
) -> SearchFuture<'a, Vec<Vec<f32>>> {
Box::pin(async move {
let mut out = Vec::with_capacity(texts.len());
for text in texts {
out.push(self.embed(cx, text).await?);
}
Ok(out)
})
}
fn embed_bound<'a>(
&'a self,
cx: &'a Cx,
text: &'a str,
) -> SearchFuture<'a, IdentityBoundEmbedding> {
Box::pin(async move {
let bound = IdentityBoundEmbedding {
values: self.embed(cx, text).await?,
identity: self.identity()?.clone(),
};
bound.validate()?;
Ok(bound)
})
}
fn embed_batch_bound<'a>(
&'a self,
cx: &'a Cx,
texts: &'a [&'a str],
) -> SearchFuture<'a, Vec<IdentityBoundEmbedding>> {
Box::pin(async move {
let identity = self.identity()?.clone();
self.embed_batch(cx, texts)
.await?
.into_iter()
.map(|values| {
let bound = IdentityBoundEmbedding {
values,
identity: identity.clone(),
};
bound.validate()?;
Ok(bound)
})
.collect()
})
}
fn identity(&self) -> SearchResult<&EmbeddingIdentityBundleV1> {
Err(SearchError::InvalidConfig {
field: "embedder.identity".to_owned(),
value: bounded_embedder_diagnostic_id(self.id()),
reason: "embedder did not supply a complete immutable identity bundle".to_owned(),
})
}
fn dimension(&self) -> usize;
fn id(&self) -> &str;
fn model_name(&self) -> &str;
fn is_ready(&self) -> bool {
true
}
fn is_semantic(&self) -> bool;
fn category(&self) -> ModelCategory;
fn tier(&self) -> ModelTier {
self.category().default_tier()
}
fn supports_mrl(&self) -> bool {
false
}
fn truncate_embedding(&self, embedding: &[f32], target_dim: usize) -> SearchResult<Vec<f32>> {
if target_dim == 0 {
return Err(SearchError::InvalidConfig {
field: "target_dim".to_owned(),
value: "0".to_owned(),
reason: "target dimension must be at least 1".to_owned(),
});
}
if target_dim >= embedding.len() {
return Ok(embedding.to_vec());
}
Ok(l2_normalize(&embedding[..target_dim]))
}
}
pub trait SyncEmbed: Send + Sync {
fn embed_sync(&self, text: &str) -> SearchResult<Vec<f32>>;
fn embed_batch_sync(&self, texts: &[&str]) -> SearchResult<Vec<Vec<f32>>> {
texts.iter().map(|t| self.embed_sync(t)).collect()
}
fn embed_bound_sync(&self, text: &str) -> SearchResult<IdentityBoundEmbedding> {
let bound = IdentityBoundEmbedding {
values: self.embed_sync(text)?,
identity: self.identity()?.clone(),
};
bound.validate()?;
Ok(bound)
}
fn embed_batch_bound_sync(&self, texts: &[&str]) -> SearchResult<Vec<IdentityBoundEmbedding>> {
let identity = self.identity()?.clone();
self.embed_batch_sync(texts)?
.into_iter()
.map(|values| {
let bound = IdentityBoundEmbedding {
values,
identity: identity.clone(),
};
bound.validate()?;
Ok(bound)
})
.collect()
}
fn identity(&self) -> SearchResult<&EmbeddingIdentityBundleV1> {
Err(SearchError::InvalidConfig {
field: "sync_embedder.identity".to_owned(),
value: bounded_embedder_diagnostic_id(self.id()),
reason: "embedder did not supply a complete immutable identity bundle".to_owned(),
})
}
fn dimension(&self) -> usize;
fn id(&self) -> &str;
fn model_name(&self) -> &str {
self.id()
}
fn is_ready(&self) -> bool {
true
}
fn is_semantic(&self) -> bool;
fn category(&self) -> ModelCategory;
fn tier(&self) -> ModelTier {
self.category().default_tier()
}
fn supports_mrl(&self) -> bool {
false
}
}
pub struct SyncEmbedderAdapter<T: SyncEmbed>(pub T);
fn sync_embed_checkpoint(cx: &Cx, phase: &'static str) -> SearchResult<()> {
cx.checkpoint().map_err(|error| SearchError::Cancelled {
phase: phase.to_owned(),
reason: cx
.cancel_reason()
.map_or_else(|| error.to_string(), |reason| reason.to_string()),
})
}
impl<T: SyncEmbed + 'static> Embedder for SyncEmbedderAdapter<T> {
fn embed<'a>(&'a self, cx: &'a Cx, text: &'a str) -> SearchFuture<'a, Vec<f32>> {
Box::pin(async move {
sync_embed_checkpoint(cx, "sync_embed.embed")?;
self.0.embed_sync(text)
})
}
fn embed_batch<'a>(
&'a self,
cx: &'a Cx,
texts: &'a [&'a str],
) -> SearchFuture<'a, Vec<Vec<f32>>> {
Box::pin(async move {
sync_embed_checkpoint(cx, "sync_embed.embed_batch")?;
self.0.embed_batch_sync(texts)
})
}
fn identity(&self) -> SearchResult<&EmbeddingIdentityBundleV1> {
self.0.identity()
}
fn dimension(&self) -> usize {
self.0.dimension()
}
fn id(&self) -> &str {
self.0.id()
}
fn model_name(&self) -> &str {
self.0.model_name()
}
fn is_ready(&self) -> bool {
self.0.is_ready()
}
fn is_semantic(&self) -> bool {
self.0.is_semantic()
}
fn category(&self) -> ModelCategory {
self.0.category()
}
fn tier(&self) -> ModelTier {
self.0.tier()
}
fn supports_mrl(&self) -> bool {
self.0.supports_mrl()
}
}
#[must_use]
pub fn l2_normalize(vec: &[f32]) -> Vec<f32> {
let norm_sq: f32 = vec.iter().map(|x| x * x).sum();
if !norm_sq.is_finite() || norm_sq < f32::EPSILON {
return vec![0.0; vec.len()];
}
let inv_norm = 1.0 / norm_sq.sqrt();
vec.iter().map(|x| x * inv_norm).collect()
}
pub fn l2_normalize_in_place(vec: &mut [f32]) {
let norm_sq: f32 = vec.iter().map(|x| x * x).sum();
if !norm_sq.is_finite() || norm_sq < f32::EPSILON {
for x in vec.iter_mut() {
*x = 0.0;
}
return;
}
let inv_norm = 1.0 / norm_sq.sqrt();
crate::simd::scale_f32_in_place(vec, inv_norm);
}
#[must_use]
pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
if a.len() != b.len() {
return 0.0;
}
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
let denom = norm_a * norm_b;
if !denom.is_finite() || denom < f32::EPSILON {
return 0.0;
}
dot / denom
}
#[must_use]
pub fn truncate_embedding(embedding: &[f32], target_dim: usize) -> Vec<f32> {
if target_dim >= embedding.len() {
return embedding.to_vec();
}
l2_normalize(&embedding[..target_dim])
}
#[derive(Debug, Clone)]
pub struct RerankDocument {
pub doc_id: String,
pub text: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RerankScore {
pub doc_id: String,
pub score: f32,
pub original_rank: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub raw_logit: Option<f32>,
}
pub trait Reranker: Send + Sync {
fn rerank<'a>(
&'a self,
cx: &'a Cx,
query: &'a str,
documents: &'a [RerankDocument],
) -> SearchFuture<'a, Vec<RerankScore>>;
fn id(&self) -> &str;
fn model_name(&self) -> &str;
fn max_length(&self) -> usize {
512
}
fn is_available(&self) -> bool {
true
}
}
pub trait SyncRerank: Send + Sync {
fn rerank_sync(
&self,
query: &str,
documents: &[RerankDocument],
) -> SearchResult<Vec<RerankScore>>;
fn id(&self) -> &str;
fn model_name(&self) -> &str;
fn max_length(&self) -> usize {
512
}
fn is_available(&self) -> bool {
true
}
}
pub struct SyncRerankerAdapter<T: SyncRerank>(pub T);
impl<T: SyncRerank + 'static> Reranker for SyncRerankerAdapter<T> {
fn rerank<'a>(
&'a self,
_cx: &'a Cx,
query: &'a str,
documents: &'a [RerankDocument],
) -> SearchFuture<'a, Vec<RerankScore>> {
Box::pin(async move {
let mut scores = self.0.rerank_sync(query, documents)?;
scores.sort_by(|lhs, rhs| {
rhs.score
.total_cmp(&lhs.score)
.then_with(|| lhs.original_rank.cmp(&rhs.original_rank))
.then_with(|| lhs.doc_id.cmp(&rhs.doc_id))
});
Ok(scores)
})
}
fn id(&self) -> &str {
self.0.id()
}
fn model_name(&self) -> &str {
self.0.model_name()
}
fn max_length(&self) -> usize {
self.0.max_length()
}
fn is_available(&self) -> bool {
self.0.is_available()
}
}
pub struct LexicalHydrationContext {
backend: &'static str,
inner: Box<dyn Any + Send + Sync>,
}
impl LexicalHydrationContext {
#[must_use]
pub fn new(backend: &'static str, inner: Box<dyn Any + Send + Sync>) -> Self {
Self { backend, inner }
}
#[must_use]
pub const fn backend(&self) -> &'static str {
self.backend
}
#[must_use]
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.inner.downcast_ref::<T>()
}
}
impl fmt::Debug for LexicalHydrationContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LexicalHydrationContext")
.field("backend", &self.backend)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct LexicalCandidateBatch {
results: Vec<ScoredResult>,
context: Option<LexicalHydrationContext>,
}
impl LexicalCandidateBatch {
#[must_use]
pub const fn eager(results: Vec<ScoredResult>) -> Self {
Self {
results,
context: None,
}
}
#[must_use]
pub const fn deferred(results: Vec<ScoredResult>, context: LexicalHydrationContext) -> Self {
Self {
results,
context: Some(context),
}
}
#[must_use]
pub fn results(&self) -> &[ScoredResult] {
&self.results
}
#[must_use]
pub const fn context(&self) -> Option<&LexicalHydrationContext> {
self.context.as_ref()
}
#[must_use]
pub const fn is_deferred(&self) -> bool {
self.context.is_some()
}
#[must_use]
pub fn into_parts(self) -> (Vec<ScoredResult>, Option<LexicalHydrationContext>) {
(self.results, self.context)
}
}
pub trait LexicalRead: Send + Sync {
fn search<'a>(
&'a self,
cx: &'a Cx,
query: &'a str,
limit: usize,
) -> SearchFuture<'a, Vec<ScoredResult>>;
fn search_candidates<'a>(
&'a self,
cx: &'a Cx,
query: &'a str,
limit: usize,
) -> SearchFuture<'a, LexicalCandidateBatch> {
Box::pin(async move {
Ok(LexicalCandidateBatch::eager(
self.search(cx, query, limit).await?,
))
})
}
fn hydrate_candidates<'a>(
&'a self,
_cx: &'a Cx,
context: Option<&'a LexicalHydrationContext>,
_results: &'a mut [ScoredResult],
) -> SearchFuture<'a, ()> {
let foreign_backend = context.map(LexicalHydrationContext::backend);
Box::pin(async move {
foreign_backend.map_or(Ok(()), |backend| {
Err(SearchError::SubsystemError {
subsystem: "lexical.hydration",
source: format!(
"hydration context from backend {backend:?} was supplied to a backend \
that issues only eager candidate batches; refusing cross-engine or \
cross-generation hydration"
)
.into(),
})
})
})
}
fn doc_count(&self) -> SearchResult<usize>;
}
pub trait LexicalWrite: Send + Sync {
fn index_document<'a>(&'a self, cx: &'a Cx, doc: &'a IndexableDocument)
-> SearchFuture<'a, ()>;
fn index_documents<'a>(
&'a self,
cx: &'a Cx,
docs: &'a [IndexableDocument],
) -> SearchFuture<'a, ()> {
Box::pin(async move {
for doc in docs {
self.index_document(cx, doc).await?;
}
Ok(())
})
}
fn commit<'a>(&'a self, cx: &'a Cx) -> SearchFuture<'a, ()>;
}
pub trait MetricsExporter: fmt::Debug + Send + Sync {
fn on_search_completed(&self, metrics: &SearchMetrics);
fn on_embedding_completed(&self, metrics: &EmbeddingMetrics);
fn on_index_updated(&self, metrics: &IndexMetrics);
fn on_error(&self, error: &SearchError);
}
pub type SharedMetricsExporter = Arc<dyn MetricsExporter>;
#[derive(Debug, Default, Clone, Copy)]
pub struct NoOpMetricsExporter;
impl MetricsExporter for NoOpMetricsExporter {
fn on_search_completed(&self, _: &SearchMetrics) {}
fn on_embedding_completed(&self, _: &EmbeddingMetrics) {}
fn on_index_updated(&self, _: &IndexMetrics) {}
fn on_error(&self, _: &SearchError) {}
}
#[cfg(test)]
mod tests {
use asupersync::test_utils::run_test_with_cx;
use super::*;
struct BoundSyncEmbedder {
identity: EmbeddingIdentityBundleV1,
output_dimension: usize,
}
impl SyncEmbed for BoundSyncEmbedder {
fn embed_sync(&self, _text: &str) -> SearchResult<Vec<f32>> {
Ok(vec![1.0; self.output_dimension])
}
fn identity(&self) -> SearchResult<&EmbeddingIdentityBundleV1> {
Ok(&self.identity)
}
fn dimension(&self) -> usize {
self.output_dimension
}
fn id(&self) -> &'static str {
"bound-sync-fixture"
}
fn is_semantic(&self) -> bool {
false
}
fn category(&self) -> ModelCategory {
ModelCategory::HashEmbedder
}
}
struct UnsortedSyncReranker;
impl SyncRerank for UnsortedSyncReranker {
fn rerank_sync(
&self,
_query: &str,
_documents: &[RerankDocument],
) -> SearchResult<Vec<RerankScore>> {
Ok(vec![
RerankScore {
doc_id: "doc-a".into(),
score: 0.8,
original_rank: 2,
raw_logit: None,
},
RerankScore {
doc_id: "doc-b".into(),
score: 0.8,
original_rank: 1,
raw_logit: None,
},
RerankScore {
doc_id: "doc-c".into(),
score: 0.3,
original_rank: 0,
raw_logit: None,
},
])
}
fn id(&self) -> &'static str {
"unsorted-sync-reranker"
}
fn model_name(&self) -> &'static str {
"Unsorted Sync Reranker"
}
}
struct UnboundSyncEmbedder;
impl SyncEmbed for UnboundSyncEmbedder {
fn embed_sync(&self, _text: &str) -> SearchResult<Vec<f32>> {
Ok(vec![0.0])
}
fn dimension(&self) -> usize {
1
}
fn id(&self) -> &'static str {
"legacy\nforged-log-line"
}
fn is_semantic(&self) -> bool {
false
}
fn category(&self) -> ModelCategory {
ModelCategory::HashEmbedder
}
}
#[test]
fn model_category_display() {
assert_eq!(ModelCategory::HashEmbedder.to_string(), "hash_embedder");
assert_eq!(ModelCategory::StaticEmbedder.to_string(), "static_embedder");
assert_eq!(
ModelCategory::TransformerEmbedder.to_string(),
"transformer_embedder"
);
}
#[test]
fn model_category_serialization() {
let json = serde_json::to_string(&ModelCategory::StaticEmbedder).unwrap();
let decoded: ModelCategory = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, ModelCategory::StaticEmbedder);
}
#[test]
fn model_category_equality() {
assert_eq!(ModelCategory::HashEmbedder, ModelCategory::HashEmbedder);
assert_ne!(ModelCategory::HashEmbedder, ModelCategory::StaticEmbedder);
assert_ne!(
ModelCategory::StaticEmbedder,
ModelCategory::TransformerEmbedder
);
}
#[test]
fn model_category_default_tier() {
assert_eq!(ModelCategory::HashEmbedder.default_tier(), ModelTier::Fast);
assert_eq!(
ModelCategory::StaticEmbedder.default_tier(),
ModelTier::Fast
);
assert_eq!(
ModelCategory::TransformerEmbedder.default_tier(),
ModelTier::Quality
);
}
#[test]
fn model_tier_display() {
assert_eq!(ModelTier::Fast.to_string(), "fast");
assert_eq!(ModelTier::Quality.to_string(), "quality");
}
#[test]
fn model_info_roundtrip() {
let info = ModelInfo {
id: "potion-multilingual-128M".to_owned(),
name: "Potion 128M".to_owned(),
dimension: 256,
category: ModelCategory::StaticEmbedder,
tier: ModelTier::Fast,
is_semantic: true,
supports_mrl: false,
huggingface_id: Some("minishlab/potion-multilingual-128M".to_owned()),
size_bytes: Some(128_000_000),
license: Some("apache-2.0".to_owned()),
};
let json = serde_json::to_string(&info).unwrap();
let decoded: ModelInfo = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, info);
}
#[test]
fn rerank_document_construction() {
let doc = RerankDocument {
doc_id: "doc-1".into(),
text: "Some content".into(),
};
assert_eq!(doc.doc_id, "doc-1");
assert_eq!(doc.text, "Some content");
}
#[test]
fn rerank_score_serialization() {
let score = RerankScore {
doc_id: "doc-1".into(),
score: 0.92,
original_rank: 3,
raw_logit: None,
};
let json = serde_json::to_string(&score).unwrap();
let decoded: RerankScore = serde_json::from_str(&json).unwrap();
assert_eq!(decoded.doc_id, "doc-1");
assert!((decoded.score - 0.92).abs() < 1e-6);
assert_eq!(decoded.original_rank, 3);
}
#[test]
fn embedder_trait_is_object_safe() {
fn _takes_dyn_embedder(_: &dyn Embedder) {}
}
#[test]
fn sync_bound_outputs_carry_identity_and_fail_on_shape_drift() {
let embedder = BoundSyncEmbedder {
identity: EmbeddingIdentityBundleV1::explicit_test_model("bound-sync-fixture", 3),
output_dimension: 3,
};
let bound = embedder.embed_bound_sync("text").unwrap();
assert_eq!(bound.values, vec![1.0; 3]);
assert_eq!(bound.identity, embedder.identity);
assert_eq!(
embedder.embed_batch_bound_sync(&["a", "b"]).unwrap().len(),
2
);
let drifted = BoundSyncEmbedder {
identity: EmbeddingIdentityBundleV1::explicit_test_model("bound-sync-fixture", 2),
output_dimension: 3,
};
assert!(drifted.embed_bound_sync("text").is_err());
}
#[test]
fn missing_identity_diagnostic_redacts_untrusted_embedder_id() {
let error = UnboundSyncEmbedder.identity().unwrap_err();
assert!(error.to_string().contains("<redacted-embedder-id>"));
assert!(!error.to_string().contains("forged-log-line"));
}
#[test]
fn identity_bound_debug_redacts_vector_values() {
let bound = IdentityBoundEmbedding {
values: vec![12_345.5, -9_876.25],
identity: EmbeddingIdentityBundleV1::explicit_test_model("debug-redaction", 2),
};
let debug = format!("{bound:?}");
assert!(debug.contains("dimension"));
assert!(debug.contains(&bound.identity.fingerprint()));
assert!(!debug.contains("12345"));
assert!(!debug.contains("9876"));
}
#[test]
fn identity_bound_output_rejects_non_memory_f32_storage_claims() {
let mut identity =
EmbeddingIdentityBundleV1::explicit_test_model("bound-storage-fixture", 2);
identity.storage.quantization = QuantizationFormat::F16;
let bound = IdentityBoundEmbedding {
values: vec![1.0, 2.0],
identity,
};
assert!(bound.validate().is_err());
let mut identity =
EmbeddingIdentityBundleV1::explicit_test_model("bound-storage-fixture", 2);
identity.storage.format = "fsvi-v2".to_owned();
identity.storage.endianness = "little-endian".to_owned();
let bound = IdentityBoundEmbedding {
values: vec![1.0, 2.0],
identity,
};
assert!(bound.validate().is_err());
let mut identity =
EmbeddingIdentityBundleV1::explicit_test_model("bound-storage-fixture", 2);
identity.storage.endianness = "little-endian".to_owned();
let bound = IdentityBoundEmbedding {
values: vec![1.0, 2.0],
identity,
};
assert!(bound.validate().is_err());
}
#[test]
fn sync_embed_adapter_observes_cancel_before_blocking_work() {
run_test_with_cx(|cx| async move {
cx.cancel_fast(asupersync::CancelKind::User);
let adapter = SyncEmbedderAdapter(BoundSyncEmbedder {
identity: EmbeddingIdentityBundleV1::explicit_test_model("cancel-sync-fixture", 3),
output_dimension: 3,
});
let error = adapter
.embed(&cx, "text")
.await
.expect_err("cancelled sync adapter must fail closed");
match error {
SearchError::Cancelled { phase, .. } => {
assert_eq!(phase, "sync_embed.embed");
}
other => panic!("expected Cancelled, got {other:?}"),
}
});
}
#[test]
fn async_bound_outputs_carry_forwarded_identity() {
run_test_with_cx(|cx| async move {
let identity = EmbeddingIdentityBundleV1::explicit_test_model("bound-async-fixture", 3);
let adapter = SyncEmbedderAdapter(BoundSyncEmbedder {
identity: identity.clone(),
output_dimension: 3,
});
let bound = adapter.embed_bound(&cx, "text").await.unwrap();
assert_eq!(bound.values, vec![1.0; 3]);
assert_eq!(bound.identity, identity);
assert_eq!(
adapter
.embed_batch_bound(&cx, &["a", "b"])
.await
.unwrap()
.len(),
2
);
});
}
#[test]
fn reranker_trait_is_object_safe() {
fn _takes_dyn_reranker(_: &dyn Reranker) {}
}
#[test]
fn split_lexical_traits_are_object_safe() {
fn _takes_dyn_read(_: &dyn LexicalRead) {}
fn _takes_dyn_write(_: &dyn LexicalWrite) {}
}
#[test]
fn metrics_exporter_trait_is_object_safe() {
fn _takes_dyn_metrics_exporter(_: &dyn MetricsExporter) {}
}
#[test]
fn sync_reranker_adapter_sorts_descending_for_trait_contract() {
run_test_with_cx(|cx| async move {
let adapter = SyncRerankerAdapter(UnsortedSyncReranker);
let docs = vec![
RerankDocument {
doc_id: "doc-a".into(),
text: "alpha".to_owned(),
},
RerankDocument {
doc_id: "doc-b".into(),
text: "beta".to_owned(),
},
RerankDocument {
doc_id: "doc-c".into(),
text: "gamma".to_owned(),
},
];
let scores = adapter
.rerank(&cx, "query", &docs)
.await
.expect("adapter rerank should succeed");
let ids = scores
.iter()
.map(|score| score.doc_id.as_str())
.collect::<Vec<_>>();
assert_eq!(ids, vec!["doc-b", "doc-a", "doc-c"]);
});
}
#[test]
fn noop_metrics_exporter_callbacks_are_noops() {
let exporter = NoOpMetricsExporter;
let search_metrics = SearchMetrics {
mode: crate::types::SearchMode::Hybrid,
query_class: None,
total_latency_ms: 10.0,
phase1_latency_ms: Some(4.0),
phase2_latency_ms: Some(6.0),
result_count: 8,
lexical_candidates: 30,
semantic_candidates: 25,
hash_control_candidates: 0,
refined: true,
};
let embedding_metrics = EmbeddingMetrics {
embedder_id: "fnv-hash-384".into(),
batch_size: 1,
duration_ms: 0.07,
dimension: 384,
is_semantic: false,
};
let index_metrics = IndexMetrics {
doc_count: 100,
index_size_bytes: 4096,
updated_docs: 1,
staleness_detected: false,
};
exporter.on_search_completed(&search_metrics);
exporter.on_embedding_completed(&embedding_metrics);
exporter.on_index_updated(&index_metrics);
exporter.on_error(&SearchError::SearchTimeout {
elapsed_ms: 11,
budget_ms: 10,
});
}
#[test]
fn l2_normalize_produces_unit_vector() {
let v = vec![3.0, 4.0];
let normalized = l2_normalize(&v);
let norm: f32 = normalized.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((norm - 1.0).abs() < 1e-6);
}
#[test]
fn l2_normalize_zero_vector() {
let v = vec![0.0, 0.0, 0.0];
let normalized = l2_normalize(&v);
assert!(normalized.iter().all(|&x| x == 0.0));
}
#[test]
fn l2_normalize_in_place_matches_allocating() {
let cases: &[Vec<f32>] = &[
vec![],
vec![0.0, 0.0, 0.0],
vec![3.0, 4.0],
vec![1.0, 2.0, 3.0, 4.0, 5.0],
vec![-1.5, 0.25, 1e-3, 7.0, -7.0],
vec![1e-30, 1e-30], vec![f32::MAX, f32::MAX], ];
for v in cases {
let allocating = l2_normalize(v);
let mut in_place = v.clone();
l2_normalize_in_place(&mut in_place);
assert_eq!(in_place, allocating, "input={v:?}");
}
}
#[test]
fn cosine_similarity_identical() {
let v = vec![1.0, 2.0, 3.0];
let sim = cosine_similarity(&v, &v);
assert!((sim - 1.0).abs() < 1e-6);
}
#[test]
fn cosine_similarity_orthogonal() {
let a = vec![1.0, 0.0];
let b = vec![0.0, 1.0];
assert!(cosine_similarity(&a, &b).abs() < 1e-6);
}
#[test]
fn cosine_similarity_zero_vector() {
let a = vec![1.0, 2.0];
let b = vec![0.0, 0.0];
assert!(cosine_similarity(&a, &b).abs() < f32::EPSILON);
}
#[test]
fn truncate_embedding_reduces_dim() {
let v = vec![1.0, 2.0, 3.0, 4.0];
let t = truncate_embedding(&v, 2);
assert_eq!(t.len(), 2);
let norm: f32 = t.iter().map(|x| x * x).sum::<f32>().sqrt();
assert!((norm - 1.0).abs() < 1e-6);
}
#[test]
fn truncate_embedding_noop_when_larger() {
let v = vec![1.0, 2.0];
assert_eq!(truncate_embedding(&v, 10), v);
}
#[test]
fn model_category_default_semantic_flag() {
assert!(!ModelCategory::HashEmbedder.default_semantic_flag());
assert!(ModelCategory::StaticEmbedder.default_semantic_flag());
assert!(ModelCategory::TransformerEmbedder.default_semantic_flag());
}
struct EagerOnlyLexical;
fn lexical_hit(id: &str, metadata: Option<serde_json::Value>) -> ScoredResult {
ScoredResult {
doc_id: compact_str::CompactString::from(id),
score: 1.0,
source: crate::types::ScoreSource::Lexical,
index: None,
fast_score: None,
quality_score: None,
lexical_score: Some(1.0),
rerank_score: None,
explanation: None,
metadata: metadata.map(std::sync::Arc::new),
}
}
impl LexicalRead for EagerOnlyLexical {
fn search<'a>(
&'a self,
_cx: &'a Cx,
_query: &'a str,
_limit: usize,
) -> SearchFuture<'a, Vec<ScoredResult>> {
Box::pin(async {
Ok(vec![lexical_hit(
"doc-a",
Some(serde_json::json!({"rev": "v1"})),
)])
})
}
fn doc_count(&self) -> SearchResult<usize> {
Ok(1)
}
}
#[test]
fn eager_candidates_hydrate_without_a_context() {
run_test_with_cx(|cx| async move {
let backend = EagerOnlyLexical;
let batch = backend
.search_candidates(&cx, "alpha", 10)
.await
.expect("eager candidates");
assert!(
!batch.is_deferred(),
"a backend that does not override search_candidates issues eager batches"
);
let (mut winners, context) = batch.into_parts();
assert!(context.is_none(), "an eager batch carries no snapshot pin");
assert!(
winners[0].metadata.is_some(),
"eager metadata is attached by the scoring search, so it is already \
from the scoring generation"
);
backend
.hydrate_candidates(&cx, context.as_ref(), &mut winners)
.await
.expect("hydrating an eager batch is a no-op, not an error");
});
}
#[test]
fn a_foreign_hydration_context_is_rejected_with_a_typed_error() {
run_test_with_cx(|cx| async move {
let backend = EagerOnlyLexical;
let foreign = LexicalHydrationContext::new("quill", Box::new(7_u64));
let mut winners = vec![lexical_hit("doc-a", None)];
let error = backend
.hydrate_candidates(&cx, Some(&foreign), &mut winners)
.await
.expect_err("a backend that issues no context must refuse to receive one");
match error {
SearchError::SubsystemError { subsystem, source } => {
assert_eq!(subsystem, "lexical.hydration");
let message = source.to_string();
assert!(
message.contains("quill"),
"the rejection must name the foreign backend: {message}"
);
}
other => panic!("expected a typed subsystem error, got {other:?}"),
}
});
}
}