2026-10-13 00:58:57 +02:00
mod incremental ;
pub ( crate ) use incremental ::SourceTask ;
2026-10-12 17:44:56 +02:00
use std ::{
collections ::{ HashMap , HashSet },
fs ::{ File , OpenOptions },
path ::{ Path , PathBuf },
sync ::Arc ,
};
use anyhow ::{ ensure , Context , Result };
use fs2 ::FileExt ;
use rusqlite ::{ params , Connection , OptionalExtension };
2026-10-12 19:16:23 +02:00
use searchi_core ::metrics ::{ MetricsMode , SpanToken , TraceCollector };
2026-10-12 17:44:56 +02:00
use searchi_core ::{ IndexedPassage , SearchOptions , SearchResult , SourceLocation };
use searchi_semantic ::TextWindow ;
use sha2 ::{ Digest , Sha256 };
use usearch ::{ Index , IndexOptions , MetricKind , ScalarKind };
pub ( crate ) const MAX_SCAN_CHUNKS : usize = 100_000 ;
pub ( crate ) const MIN_COSINE : f32 = 0.25 ;
#[derive(Clone, PartialEq, Eq)]
struct FilterKey {
folder : Option < String > ,
extension : Option < String > ,
min_size : Option < i64 > ,
max_size : Option < i64 > ,
modified_after : Option < i64 > ,
modified_before : Option < i64 > ,
2026-10-12 19:16:23 +02:00
min_duration : Option < u32 > ,
max_duration : Option < u32 > ,
source_type : Option < String > ,
2026-10-12 21:22:11 +02:00
code_only : bool ,
2026-10-12 17:44:56 +02:00
}
struct EligibilityCache {
generation : i64 ,
revision : i64 ,
filter : FilterKey ,
keys : Arc < HashSet < u64 >> ,
by_path : HashMap < String , Vec < u64 >> ,
}
pub ( crate ) struct Store {
connection : Connection ,
lock_path : PathBuf ,
dimensions : usize ,
ann_cache : Option < ( i64 , Index ) > ,
eligibility_cache : Option < EligibilityCache > ,
}
2026-10-13 00:58:57 +02:00
pub ( crate ) struct BuildLock ( File , PathBuf );
2026-10-12 17:44:56 +02:00
impl Drop for BuildLock {
fn drop ( & mut self ) {
let _ = FileExt ::unlock ( & self . 0 );
}
}
pub ( crate ) fn content_hash ( text : & str ) -> String {
#[cfg(test)]
HASH_CALLS . with ( | count | count . set ( count . get () + 1 ));
format! ( " {:x} " , Sha256 ::digest ( text . as_bytes ()))
}
#[cfg(test)]
thread_local! { static HASH_CALLS :std ::cell ::Cell < usize >= const { std ::cell ::Cell ::new ( 0 )}; }
2026-10-12 19:16:23 +02:00
#[cfg(test)]
thread_local! { static MEDIA_STAT_CALLS :std ::cell ::Cell < usize >= const { std ::cell ::Cell ::new ( 0 )}; }
2026-10-12 17:44:56 +02:00
impl Store {
2026-10-12 19:16:23 +02:00
pub ( crate ) fn active_fingerprint ( & self ) -> Result < Option < String >> {
Ok ( self . connection . query_row ( "SELECT g.fingerprint FROM semantic_meta m JOIN semantic_generations g ON g.id=m.active_generation WHERE m.singleton=1" ,[], | r | r . get ( 0 )). optional () ? )
}
2026-10-12 17:44:56 +02:00
pub ( crate ) fn open ( db : & Path , dimensions : usize ) -> Result < Self > {
ensure! (
( 1 ..= 4096 ). contains ( & dimensions ),
"Embedding dimensions must be between 1 and 4096"
);
ensure! (
db != Path ::new ( ":memory:" ),
"Hybrid search requires a persistent database path"
);
let mut connection = Connection ::open ( db ) ? ;
connection . busy_timeout ( std ::time ::Duration ::from_secs ( 5 )) ? ;
connection . execute_batch ( "PRAGMA foreign_keys=ON;" ) ? ;
let initialization =
connection . transaction_with_behavior ( rusqlite ::TransactionBehavior ::Immediate ) ? ;
initialization . execute_batch (
"CREATE TABLE IF NOT EXISTS semantic_meta (
singleton INTEGER PRIMARY KEY CHECK(singleton=1), schema_version INTEGER NOT NULL,
active_generation INTEGER, corpus_revision INTEGER NOT NULL DEFAULT 0, dirty_floor INTEGER NOT NULL DEFAULT 0);
INSERT OR IGNORE INTO semantic_meta(singleton,schema_version) VALUES(1,5);" ,
) ? ;
let version : i64 = initialization . query_row (
"SELECT schema_version FROM semantic_meta WHERE singleton=1" ,
[],
| row | row . get ( 0 ),
) ? ;
ensure! (
2026-10-13 00:58:57 +02:00
( 1 ..= 7 ). contains ( & version ),
2026-10-12 17:44:56 +02:00
"Unsupported semantic schema version {version}"
);
initialization . execute_batch ( "
CREATE TABLE IF NOT EXISTS semantic_generations(id INTEGER PRIMARY KEY, fingerprint TEXT NOT NULL, dimensions INTEGER NOT NULL, corpus_revision INTEGER NOT NULL DEFAULT 0, ann_blob BLOB, ann_sha256 TEXT);
CREATE TABLE IF NOT EXISTS semantic_chunks(
generation INTEGER NOT NULL REFERENCES semantic_generations(id) ON DELETE CASCADE,
passage_id INTEGER NOT NULL, ordinal INTEGER NOT NULL,
path TEXT NOT NULL, kind TEXT NOT NULL, number INTEGER NOT NULL,
content_hash TEXT NOT NULL, start_byte INTEGER NOT NULL, end_byte INTEGER NOT NULL,
snippet TEXT NOT NULL, vector BLOB NOT NULL,
PRIMARY KEY(generation,passage_id,ordinal));" ) ? ;
if version < 5 {
for ( table , column , alter ) in [
( "semantic_meta" , "corpus_revision" , "ALTER TABLE semantic_meta ADD COLUMN corpus_revision INTEGER NOT NULL DEFAULT 0" ),
( "semantic_meta" , "dirty_floor" , "ALTER TABLE semantic_meta ADD COLUMN dirty_floor INTEGER NOT NULL DEFAULT 0" ),
( "semantic_generations" , "dimensions" , "ALTER TABLE semantic_generations ADD COLUMN dimensions INTEGER NOT NULL DEFAULT 384" ),
( "semantic_generations" , "corpus_revision" , "ALTER TABLE semantic_generations ADD COLUMN corpus_revision INTEGER NOT NULL DEFAULT 0" ),
( "semantic_generations" , "ann_blob" , "ALTER TABLE semantic_generations ADD COLUMN ann_blob BLOB" ),
( "semantic_generations" , "ann_sha256" , "ALTER TABLE semantic_generations ADD COLUMN ann_sha256 TEXT" ),
] {
let exists :bool = initialization . query_row ( "SELECT EXISTS(SELECT 1 FROM pragma_table_info(?1) WHERE name=?2)" , params! [ table , column ], | row | row . get ( 0 )) ? ;
if ! exists { initialization . execute_batch ( alter ) ? ; }
}
initialization . execute_batch ( "DROP TRIGGER IF EXISTS semantic_files_insert;
DROP TRIGGER IF EXISTS semantic_files_update; DROP TRIGGER IF EXISTS semantic_files_delete;" ) ? ;
}
initialization . execute_batch (
"CREATE TABLE IF NOT EXISTS semantic_dirty_paths(path TEXT PRIMARY KEY, revision INTEGER NOT NULL);
CREATE INDEX IF NOT EXISTS semantic_dirty_revision ON semantic_dirty_paths(revision);
CREATE INDEX IF NOT EXISTS semantic_chunk_path ON semantic_chunks(generation,path);
CREATE TRIGGER IF NOT EXISTS semantic_files_insert AFTER INSERT ON files BEGIN
UPDATE semantic_meta SET corpus_revision=corpus_revision+1 WHERE singleton=1;
INSERT INTO semantic_dirty_paths(path,revision) VALUES(new.path,(SELECT corpus_revision FROM semantic_meta WHERE singleton=1))
ON CONFLICT(path) DO UPDATE SET revision=excluded.revision; END;
CREATE TRIGGER IF NOT EXISTS semantic_files_update AFTER UPDATE ON files BEGIN
UPDATE semantic_meta SET corpus_revision=corpus_revision+1 WHERE singleton=1;
INSERT INTO semantic_dirty_paths(path,revision) VALUES(old.path,(SELECT corpus_revision FROM semantic_meta WHERE singleton=1))
ON CONFLICT(path) DO UPDATE SET revision=excluded.revision;
INSERT INTO semantic_dirty_paths(path,revision) VALUES(new.path,(SELECT corpus_revision FROM semantic_meta WHERE singleton=1))
ON CONFLICT(path) DO UPDATE SET revision=excluded.revision; END;
CREATE TRIGGER IF NOT EXISTS semantic_files_delete AFTER DELETE ON files BEGIN
UPDATE semantic_meta SET corpus_revision=corpus_revision+1 WHERE singleton=1;
INSERT INTO semantic_dirty_paths(path,revision) VALUES(old.path,(SELECT corpus_revision FROM semantic_meta WHERE singleton=1))
ON CONFLICT(path) DO UPDATE SET revision=excluded.revision; END;
2026-10-12 19:16:23 +02:00
UPDATE semantic_meta SET schema_version=6 WHERE singleton=1;" ,
2026-10-12 17:44:56 +02:00
) ? ;
2026-10-12 19:16:23 +02:00
for ( column , declaration ) in [
( "source_type" , "TEXT NOT NULL DEFAULT 'text'" ),
( "file_size" , "INTEGER" ),
( "file_mtime" , "INTEGER" ),
] {
let exists : bool = initialization . query_row (
"SELECT EXISTS(SELECT 1 FROM pragma_table_info('semantic_chunks') WHERE name=?1)" ,
[ column ],
| r | r . get ( 0 ),
) ? ;
if ! exists {
initialization . execute_batch ( & format! (
"ALTER TABLE semantic_chunks ADD COLUMN {column} {declaration} "
)) ? ;
}
}
initialization . execute_batch ( "CREATE TABLE IF NOT EXISTS semantic_media_errors(generation INTEGER NOT NULL REFERENCES semantic_generations(id) ON DELETE CASCADE,path TEXT NOT NULL,message TEXT NOT NULL,PRIMARY KEY(generation,path))" ) ? ;
2026-10-13 00:58:57 +02:00
incremental ::migrate ( & initialization ) ? ;
2026-10-12 17:44:56 +02:00
initialization . commit () ? ;
let canonical = db . canonicalize () ? ;
let lock_path = PathBuf ::from ( format! ( " {} .semantic.lock" , canonical . to_string_lossy ()));
Ok ( Self {
connection ,
lock_path ,
dimensions ,
ann_cache : None ,
eligibility_cache : None ,
})
}
pub ( crate ) fn build_lock ( & self ) -> Result < BuildLock > {
let file = OpenOptions ::new ()
. read ( true )
. write ( true )
. create ( true )
. truncate ( false )
. open ( & self . lock_path ) ? ;
file . try_lock_exclusive ()
. context ( "Another semantic rebuild is running" ) ? ;
2026-10-13 00:58:57 +02:00
Ok ( BuildLock ( file , self . lock_path . clone ()))
2026-10-12 17:44:56 +02:00
}
2026-10-13 00:58:57 +02:00
#[cfg(test)]
2026-10-12 17:44:56 +02:00
pub ( crate ) fn begin ( & mut self , fingerprint : & str ) -> Result < i64 > {
let transaction = self . connection . transaction () ? ;
// Only called while holding the cross-process lock: abandoned staging
// generations may be removed safely after a crash.
transaction . execute ( "DELETE FROM semantic_generations WHERE id != COALESCE((SELECT active_generation FROM semantic_meta WHERE singleton=1),-1)" , []) ? ;
transaction . execute (
"INSERT INTO semantic_generations(fingerprint,dimensions,corpus_revision) SELECT ?1,?2,corpus_revision FROM semantic_meta WHERE singleton=1" ,
params! [ fingerprint , self . dimensions as i64 ],
) ? ;
let id = transaction . last_insert_rowid ();
transaction . commit () ? ;
Ok ( id )
}
2026-10-12 19:16:23 +02:00
pub ( crate ) fn media_batch ( & self , after : i64 ) -> Result < Vec < ( i64 , String , i64 , i64 ) >> {
let mut q = self . connection . prepare (
"SELECT rowid,path,size,mtime FROM files WHERE rowid>?1 ORDER BY rowid LIMIT 64" ,
) ? ;
let rows = q
. query_map ([ after ], | r | {
Ok (( r . get ( 0 ) ? , r . get ( 1 ) ? , r . get ( 2 ) ? , r . get ( 3 ) ? ))
}) ?
. collect ::< rusqlite ::Result < Vec < _ >>> () ? ;
Ok ( rows )
}
pub ( crate ) fn media_failure ( & self , generation : i64 , path : & str , message : & str ) -> Result < () > {
self . connection . execute (
"DELETE FROM semantic_chunks WHERE generation=?1 AND path=?2 AND source_type!='text'" ,
params! [ generation , path ],
) ? ;
self . connection . execute ( "INSERT OR REPLACE INTO semantic_media_errors(generation,path,message) VALUES(?1,?2,?3)" , params! [ generation , path , message . chars (). take ( 2048 ). collect ::< String > ()]) ? ;
Ok (())
}
#[allow(clippy::too_many_arguments)]
pub ( crate ) fn insert_media (
& self ,
generation : i64 ,
file_id : i64 ,
ordinal : usize ,
identity : & searchi_media ::MediaIdentity ,
source_type : & str ,
timestamp : u32 ,
vector : & [ f32 ],
) -> Result < () > {
ensure! (
vector . len () == self . dimensions && vector . iter (). all ( | v | v . is_finite ()),
"Invalid media embedding"
);
let norm = vector . iter (). map ( | v | v * v ). sum ::< f32 > (). sqrt ();
ensure! (
( norm - 1. ). abs () < 1e-3 ,
"Media embeddings must be normalized"
);
let path = identity . path . to_string_lossy ();
let valid :bool = self . connection . query_row ( "SELECT EXISTS(SELECT 1 FROM files WHERE rowid=?1 AND path=?2 AND size=?3 AND mtime=?4)" , params! [ file_id , path , identity . bytes as i64 , identity . modified_ns ], | r | r . get ( 0 )) ? ;
ensure! ( valid , "Media file changed since filesystem indexing" );
let kind = if source_type == "image" {
"image"
} else {
"timestamp_ms"
};
let snippet = if source_type == "image" {
"Image content" . to_string ()
} else {
format! (
" {} content at {:.3} seconds" ,
source_type ,
f64 ::from ( timestamp ) / 1000.
)
};
let bytes : Vec < u8 > = vector . iter (). flat_map ( | v | v . to_le_bytes ()). collect ();
self . connection . execute ( "INSERT INTO semantic_chunks(generation,passage_id,ordinal,path,kind,number,content_hash,start_byte,end_byte,snippet,vector,source_type,file_size,file_mtime) VALUES(?1,?2,?3,?4,?5,?6,?7,0,0,?8,?9,?10,?11,?12)" , params! [ generation , - file_id , ordinal as i64 , path , kind , timestamp , identity . sha256 , snippet , bytes , source_type , identity . bytes as i64 , identity . modified_ns ]) ? ;
Ok (())
}
2026-10-12 17:44:56 +02:00
pub ( crate ) fn insert (
& mut self ,
generation : i64 ,
passage : & IndexedPassage ,
first_ordinal : usize ,
windows : & [ TextWindow ],
vectors : & [ Vec < f32 > ],
) -> Result < () > {
ensure! (
windows . len () == vectors . len (),
"Window and embedding counts differ"
);
let hash = content_hash ( & passage . text );
let transaction = self . connection . transaction () ? ;
for ( i , ( window , vector )) in windows . iter (). zip ( vectors ). enumerate () {
ensure! (
vector . len () == self . dimensions && vector . iter (). all ( | v | v . is_finite ()),
"Invalid persisted vector"
);
let norm = vector . iter (). map ( | value | value * value ). sum ::< f32 > (). sqrt ();
ensure! (
norm . is_finite () && ( norm - 1.0 ). abs () < 1e-3 ,
"Persisted embeddings must be L2 normalized"
);
ensure! (
passage . text . get ( window . start_byte .. window . end_byte ) == Some ( window . text . as_str ()),
"Window does not match passage source range"
);
let bytes : Vec < u8 > = vector
. iter ()
. flat_map ( | value | value . to_le_bytes ())
. collect ();
transaction . execute ( "INSERT INTO semantic_chunks(generation,passage_id,ordinal,path,kind,number,content_hash,start_byte,end_byte,snippet,vector)
VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11)" , params! [ generation , passage . id ,
( first_ordinal + i ) as i64 , passage . path , passage . source . kind , passage . source . number ,
hash , window . start_byte as i64 , window . end_byte as i64 , window . text , bytes ]) ? ;
}
transaction . commit () ? ;
Ok (())
}
2026-10-13 00:58:57 +02:00
#[cfg(test)]
2026-10-12 17:44:56 +02:00
pub ( crate ) fn activate ( & mut self , generation : i64 ) -> Result < () > {
2026-10-13 01:14:40 +02:00
self . activate_revision ( generation , None , || Ok (()))
2026-10-13 00:58:57 +02:00
}
2026-10-13 01:14:40 +02:00
fn activate_revision (
& mut self ,
generation : i64 ,
revision : Option < i64 > ,
validate_sources : impl FnOnce () -> Result < () > ,
) -> Result < () > {
2026-10-12 17:44:56 +02:00
// A failed graph build or serialization leaves the old generation active.
let ann = self . build_ann ( generation ) ? ;
let length = ann . serialized_length ();
ensure! (
length <= 512 * 1024 * 1024 ,
"Serialized ANN exceeds 512 MiB generation budget"
);
let mut serialized = vec! [ 0_ u8 ; length ];
ann . save_to_buffer ( & mut serialized ) ? ;
let checksum = format! ( " {:x} " , Sha256 ::digest ( & serialized ));
2026-10-13 01:14:40 +02:00
// Hashing/probing sources can be slow: run after graph preparation but
// before acquiring the write transaction. The revision check below
// still closes races with indexed changes after this validation.
validate_sources () ? ;
2026-10-13 00:58:57 +02:00
let transaction = self
. connection
. transaction_with_behavior ( rusqlite ::TransactionBehavior ::Immediate ) ? ;
if let Some ( expected ) = revision {
let current : i64 = transaction . query_row (
"SELECT corpus_revision FROM semantic_meta WHERE singleton=1" ,
[],
| r | r . get ( 0 ),
) ? ;
ensure! (
current == expected ,
"Indexed corpus changed during semantic rebuild; retry required"
);
incremental ::validate_completion ( & transaction , generation ) ? ;
}
2026-10-12 17:44:56 +02:00
ensure! (
transaction . query_row (
"SELECT EXISTS(SELECT 1 FROM semantic_generations WHERE id=?1)" ,
[ generation ],
| row | row . get ::< _ , bool > ( 0 )
) ? ,
"Missing staging generation"
);
transaction . execute (
"UPDATE semantic_generations SET ann_blob=?1,ann_sha256=?2 WHERE id=?3" ,
params! [ serialized , checksum , generation ],
) ? ;
transaction . execute (
"UPDATE semantic_meta SET active_generation=?1 WHERE singleton=1" ,
[ generation ],
) ? ;
transaction . execute (
"DELETE FROM semantic_generations WHERE id!=?1" ,
[ generation ],
) ? ;
transaction . execute_batch ( "UPDATE semantic_meta SET dirty_floor=max(dirty_floor,corpus_revision-100000) WHERE singleton=1;
DELETE FROM semantic_dirty_paths WHERE revision<(SELECT dirty_floor FROM semantic_meta WHERE singleton=1);" ) ? ;
transaction . commit () ? ;
self . ann_cache = Some (( generation , ann ));
self . eligibility_cache = None ;
Ok (())
}
fn new_ann ( & self ) -> Result < Index > {
Ok ( Index ::new ( & IndexOptions {
dimensions : self . dimensions ,
metric : MetricKind ::Cos ,
quantization : ScalarKind ::F32 ,
connectivity : 16 ,
expansion_add : 128 ,
expansion_search : 64 ,
multi : false ,
}) ? )
}
fn build_ann ( & self , generation : i64 ) -> Result < Index > {
let count : usize = self . connection . query_row (
"SELECT count(*) FROM semantic_chunks WHERE generation=?1" ,
[ generation ],
| row | row . get ( 0 ),
) ? ;
ensure! (
count <= MAX_SCAN_CHUNKS ,
"ANN generation exceeds {MAX_SCAN_CHUNKS} chunks"
);
let ann = self . new_ann () ? ;
ann . reserve_capacity_and_threads ( count . max ( 1 ), 1 ) ? ;
let mut statement = self . connection . prepare (
"SELECT rowid,vector FROM semantic_chunks WHERE generation=?1 ORDER BY rowid" ,
) ? ;
let mut rows = statement . query ([ generation ]) ? ;
while let Some ( row ) = rows . next () ? {
let key : i64 = row . get ( 0 ) ? ;
let bytes : Vec < u8 > = row . get ( 1 ) ? ;
ensure! (
bytes . len () == self . dimensions * 4 ,
"Invalid staging embedding length"
);
let vector : Vec < f32 > = bytes
. as_chunks ::< 4 > ()
. 0
. iter ()
. map ( | bytes | f32 ::from_le_bytes ( * bytes ))
. collect ();
ensure! (
vector . iter (). all ( | v | v . is_finite ()),
"Invalid staging embedding values"
);
ann . add ( u64 ::try_from ( key ) ? , & vector ) ? ;
}
Ok ( ann )
}
2026-10-13 00:58:57 +02:00
#[cfg(test)]
2026-10-12 17:44:56 +02:00
pub ( crate ) fn abandon ( & mut self , generation : i64 ) -> Result < () > {
self . connection . execute ( "DELETE FROM semantic_generations WHERE id=?1 AND id!=COALESCE((SELECT active_generation FROM semantic_meta WHERE singleton=1),-1)" , [ generation ]) ? ;
Ok (())
}
pub ( crate ) fn search (
& mut self ,
query : & [ f32 ],
fingerprint : & str ,
limit : usize ,
options : & SearchOptions ,
2026-10-12 19:16:23 +02:00
) -> Result < Vec < SearchResult >> {
self . search_origin ( query , fingerprint , limit , options , None )
}
pub ( crate ) fn search_origin (
& mut self ,
query : & [ f32 ],
fingerprint : & str ,
limit : usize ,
options : & SearchOptions ,
source_type : Option <& str > ,
) -> Result < Vec < SearchResult >> {
let mut trace = TraceCollector ::new ( MetricsMode ::Basic );
self . search_origin_traced (
query ,
fingerprint ,
limit ,
options ,
source_type ,
& mut trace ,
None ,
)
}
#[allow(clippy::too_many_arguments)]
pub ( crate ) fn search_origin_traced (
& mut self ,
query : & [ f32 ],
fingerprint : & str ,
limit : usize ,
options : & SearchOptions ,
source_type : Option <& str > ,
trace : & mut TraceCollector ,
parent : Option < SpanToken > ,
) -> Result < Vec < SearchResult >> {
trace . timed ( "semantic.retrieval" , parent , | trace , parent | {
self . search_inner (
query ,
fingerprint ,
limit ,
options ,
source_type ,
trace ,
parent ,
)
})
}
#[allow(clippy::too_many_arguments)]
fn search_inner (
& mut self ,
query : & [ f32 ],
fingerprint : & str ,
limit : usize ,
options : & SearchOptions ,
source_type : Option <& str > ,
trace : & mut TraceCollector ,
parent : Option < SpanToken > ,
2026-10-12 17:44:56 +02:00
) -> Result < Vec < SearchResult >> {
ensure! (
query . len () == self . dimensions && query . iter (). all ( | v | v . is_finite ()),
"Invalid query vector"
);
if limit == 0 {
return Ok ( Vec ::new ());
}
// A consistent read snapshot covers eligibility and materialization.
2026-10-12 19:16:23 +02:00
let generation_span = trace . begin ( "semantic.generation_snapshot" , parent );
2026-10-12 17:44:56 +02:00
let snapshot = self . connection . unchecked_transaction () ? ;
let active :Option < i64 >= snapshot . query_row ( "SELECT g.id FROM semantic_generations g JOIN semantic_meta m ON m.active_generation=g.id WHERE g.fingerprint=?1 AND g.dimensions=?2" ,
params! [ fingerprint , self . dimensions as i64 ], | row | row . get ( 0 )). optional () ? ;
let Some ( active ) = active else {
return Ok ( Vec ::new ());
};
2026-10-12 19:16:23 +02:00
trace . end ( generation_span , Default ::default ());
let ann_load = trace . begin ( "semantic.ann_load" , parent );
trace . counter (
ann_load ,
"cache_hit" ,
u64 ::from (
self . ann_cache
. as_ref ()
. is_some_and ( | ( generation , _ ) | * generation == active ),
),
);
2026-10-12 17:44:56 +02:00
if ! self
. ann_cache
. as_ref ()
. is_some_and ( | ( generation , _ ) | * generation == active )
{
2026-10-12 19:16:23 +02:00
let blob_span = trace . begin ( "semantic.ann_blob_read" , ann_load );
2026-10-12 17:44:56 +02:00
let length : Option < usize > = snapshot . query_row (
"SELECT length(ann_blob) FROM semantic_generations WHERE id=?1" ,
[ active ],
| row | row . get ( 0 ),
) ? ;
ensure! (
length . is_some_and ( | bytes | bytes <= 512 * 1024 * 1024 ),
"Missing or oversized ANN generation; rebuild embeddings explicitly"
);
let ( serialized , checksum ) : ( Vec < u8 > , String ) = snapshot . query_row (
"SELECT ann_blob,ann_sha256 FROM semantic_generations WHERE id=?1" ,
[ active ],
| row | Ok (( row . get ( 0 ) ? , row . get ( 1 ) ? )),
) ? ;
2026-10-12 19:16:23 +02:00
trace . counter ( blob_span , "bytes" , serialized . len () as u64 );
trace . end ( blob_span , Default ::default ());
let checksum_span = trace . begin ( "semantic.ann_checksum" , ann_load );
2026-10-12 17:44:56 +02:00
ensure! (
format! ( " {:x} " , Sha256 ::digest ( & serialized )) == checksum ,
"ANN generation SHA256 mismatch; rebuild embeddings explicitly"
);
2026-10-12 19:16:23 +02:00
trace . end ( checksum_span , Default ::default ());
let deserialize_span = trace . begin ( "semantic.ann_deserialize" , ann_load );
2026-10-12 17:44:56 +02:00
let ann = self . new_ann () ? ;
ann . load_from_buffer ( & serialized ) ? ;
ensure! (
ann . dimensions () == self . dimensions ,
"ANN dimensions differ from embedding provider"
);
ann . reserve_capacity_and_threads ( ann . size (). max ( 1 ), 1 ) ? ;
2026-10-12 19:16:23 +02:00
trace . counter ( deserialize_span , "chunks" , ann . size () as u64 );
trace . end ( deserialize_span , Default ::default ());
2026-10-12 17:44:56 +02:00
self . ann_cache = Some (( active , ann ));
}
2026-10-12 19:16:23 +02:00
trace . end ( ann_load , Default ::default ());
let filter_span = trace . begin ( "semantic.filter_normalization" , parent );
2026-10-12 17:44:56 +02:00
let folder = options
. folder
. as_ref ()
. map ( std ::fs ::canonicalize )
. transpose () ?
. map ( | p | {
format! (
" {}{} " ,
p . to_string_lossy (). trim_end_matches ([ '/' , '\\' ]),
std ::path ::MAIN_SEPARATOR
)
});
let extension = options
. extension
. as_ref ()
. map ( | ext | ext . trim_start_matches ( '.' ). to_ascii_lowercase ());
let min_size = options . min_size . map ( i64 ::try_from ). transpose () ? ;
let max_size = options . max_size . map ( i64 ::try_from ). transpose () ? ;
let filter = FilterKey {
folder ,
extension ,
min_size ,
max_size ,
modified_after : options . modified_after_unix_ms ,
modified_before : options . modified_before_unix_ms ,
2026-10-12 19:16:23 +02:00
min_duration : options . min_duration_ms ,
max_duration : options . max_duration_ms ,
source_type : source_type . map ( str ::to_owned ),
2026-10-12 21:22:11 +02:00
code_only : options . code_only ,
2026-10-12 17:44:56 +02:00
};
2026-10-12 19:16:23 +02:00
trace . end ( filter_span , Default ::default ());
let eligibility_span = trace . begin ( "semantic.eligibility" , parent );
2026-10-12 17:44:56 +02:00
let ( revision , floor ) : ( i64 , i64 ) = snapshot . query_row (
"SELECT corpus_revision,dirty_floor FROM semantic_meta WHERE singleton=1" ,
[],
| row | Ok (( row . get ( 0 ) ? , row . get ( 1 ) ? )),
) ? ;
let existing = self . eligibility_cache . take ();
2026-10-12 19:16:23 +02:00
trace . counter (
eligibility_span ,
"cache_hit" ,
u64 ::from ( existing . as_ref (). is_some_and ( | c | {
c . generation == active && c . filter == filter && c . revision == revision
})),
);
2026-10-12 17:44:56 +02:00
let mut cache = match existing {
Some ( mut cache )
if cache . generation == active
&& cache . filter == filter
&& cache . revision >= floor =>
{
if cache . revision != revision {
let mut statement = snapshot . prepare ( "SELECT path FROM semantic_dirty_paths WHERE revision>?1 AND revision<=?2 ORDER BY path LIMIT 1001" ) ? ;
let paths = statement
. query_map ( params! [ cache . revision , revision ], | row | {
row . get ::< _ , String > ( 0 )
}) ?
. collect ::< rusqlite ::Result < Vec < _ >>> () ? ;
2026-10-12 19:16:23 +02:00
trace . counter ( eligibility_span , "dirty_paths" , paths . len () as u64 );
2026-10-12 17:44:56 +02:00
if paths . len () > 1000 {
2026-10-12 19:16:23 +02:00
cache = collect_eligibility_traced (
& snapshot ,
active ,
revision ,
& filter ,
None ,
trace ,
eligibility_span ,
) ? ;
2026-10-12 17:44:56 +02:00
} else {
for path in paths {
if let Some ( old ) = cache . by_path . remove ( & path ) {
for key in old {
Arc ::make_mut ( & mut cache . keys ). remove ( & key );
}
}
2026-10-12 19:16:23 +02:00
let changed = collect_eligibility_traced (
2026-10-12 17:44:56 +02:00
& snapshot ,
active ,
revision ,
& filter ,
Some ( & path ),
2026-10-12 19:16:23 +02:00
trace ,
eligibility_span ,
2026-10-12 17:44:56 +02:00
) ? ;
Arc ::make_mut ( & mut cache . keys ). extend ( changed . keys . iter (). copied ());
cache . by_path . extend ( changed . by_path );
}
cache . revision = revision ;
}
}
cache
}
2026-10-12 19:16:23 +02:00
_ => collect_eligibility_traced (
& snapshot ,
active ,
revision ,
& filter ,
None ,
trace ,
eligibility_span ,
) ? ,
2026-10-12 17:44:56 +02:00
};
ensure! (
cache . keys . len () <= MAX_SCAN_CHUNKS ,
"Eligibility cache exceeds chunk resource budget"
);
2026-10-12 19:16:23 +02:00
trace . counter ( eligibility_span , "eligible_chunks" , cache . keys . len () as u64 );
trace . end ( eligibility_span , Default ::default ());
2026-10-12 17:44:56 +02:00
cache . revision = revision ;
let eligible = Arc ::clone ( & cache . keys );
self . eligibility_cache = Some ( cache );
if eligible . is_empty () {
return Ok ( Vec ::new ());
}
let ann = & self . ann_cache . as_ref (). expect ( "loaded ANN cache" ). 1 ;
let mut candidate_count = limit . saturating_mul ( 4 ). min ( eligible . len ()). max ( 1 );
let mut results ;
2026-10-12 19:16:23 +02:00
let mut filesystem_validity = HashMap ::< String , bool > ::new ();
let mut passes = 0 u64 ;
2026-10-12 17:44:56 +02:00
loop {
2026-10-12 19:16:23 +02:00
passes += 1 ;
let pass_span = trace . begin ( "semantic.ann_pass" , parent );
trace . counter ( pass_span , "requested_chunks" , candidate_count as u64 );
let ann_span = trace . begin ( "semantic.ann_search" , pass_span );
2026-10-12 17:44:56 +02:00
let matches =
ann . filtered_search ( query , candidate_count , | key | eligible . contains ( & key )) ? ;
2026-10-12 19:16:23 +02:00
trace . counter ( ann_span , "returned_chunks" , matches . keys . len () as u64 );
trace . end ( ann_span , Default ::default ());
let materialization_span = trace . begin ( "semantic.parent_materialization" , pass_span );
2026-10-12 17:44:56 +02:00
let mut ranked : Vec < _ > = matches . keys . into_iter (). zip ( matches . distances ). collect ();
ranked . sort_by ( | a , b | a . 1. total_cmp ( & b . 1 ). then_with ( || a . 0. cmp ( & b . 0 )));
let mut best = HashMap ::< String , SearchResult > ::new ();
2026-10-12 19:16:23 +02:00
let mut materialize = snapshot . prepare ( "SELECT c.path,f.filename,c.snippet,c.kind,c.number,c.source_type,c.file_size,c.file_mtime FROM semantic_chunks c JOIN files f ON f.path=c.path WHERE c.rowid=?1 AND c.generation=?2" ) ? ;
2026-10-12 17:44:56 +02:00
for ( key , distance ) in ranked {
let cosine = 1.0 - distance ;
ensure! ( cosine . is_finite (), "ANN produced nonfinite distance" );
if cosine < MIN_COSINE {
continue ;
}
2026-10-12 19:16:23 +02:00
let ( result , size , mtime ) =
2026-10-12 17:44:56 +02:00
materialize . query_row ( params! [ i64 ::try_from ( key ) ? , active ], | row | {
2026-10-12 19:16:23 +02:00
Ok ((
SearchResult {
path : row . get ( 0 ) ? ,
filename : row . get ( 1 ) ? ,
snippet : row . get ( 2 ) ? ,
score : f64 ::from ( cosine ),
match_kind : match row . get ::< _ , String > ( 5 ) ? . as_str () {
"text" => "semantic" . into (),
other => format! ( "semantic_ {other} " ),
},
source : Some ( SourceLocation {
kind : row . get ( 3 ) ? ,
number : row . get ( 4 ) ? ,
}),
extraction_error : None ,
},
row . get ::< _ , Option < i64 >> ( 6 ) ? ,
row . get ::< _ , Option < i64 >> ( 7 ) ? ,
))
2026-10-12 17:44:56 +02:00
}) ? ;
2026-10-12 19:16:23 +02:00
if let ( Some ( size ), Some ( mtime )) = ( size , mtime ) {
let valid = * filesystem_validity
. entry ( result . path . clone ())
. or_insert_with ( || {
#[cfg(test)]
MEDIA_STAT_CALLS . with ( | count | count . set ( count . get () + 1 ));
let validation_span = trace . begin (
"semantic.parent_filesystem_validation" ,
materialization_span ,
);
let valid =
std ::fs ::metadata ( & result . path )
. ok ()
. is_some_and ( | metadata | {
metadata . is_file ()
&& i64 ::try_from ( metadata . len ()). ok () == Some ( size )
&& metadata
. modified ()
. ok ()
. and_then ( | t | {
t . duration_since ( std ::time ::UNIX_EPOCH ). ok ()
})
. and_then ( | d | i64 ::try_from ( d . as_nanos ()). ok ())
== Some ( mtime )
});
trace . counter ( validation_span , "valid" , u64 ::from ( valid ));
trace . end ( validation_span , Default ::default ());
valid
});
if ! valid {
continue ;
}
}
2026-10-12 17:44:56 +02:00
if best . contains_key ( & result . path ) {
continue ;
}
best . insert ( result . path . clone (), result );
}
results = best . into_values (). collect ::< Vec < _ >> ();
2026-10-12 19:16:23 +02:00
trace . counter ( materialization_span , "unique_parents" , results . len () as u64 );
trace . counter (
materialization_span ,
"filesystem_checks" ,
filesystem_validity . len () as u64 ,
);
trace . end ( materialization_span , Default ::default ());
trace . end ( pass_span , Default ::default ());
2026-10-12 17:44:56 +02:00
if results . len () >= limit || candidate_count == eligible . len () {
break ;
}
candidate_count = candidate_count . saturating_mul ( 2 ). min ( eligible . len ());
}
results . sort_by ( | a , b | {
b . score
. total_cmp ( & a . score )
. then_with ( || a . path . cmp ( & b . path ))
});
results . truncate ( limit );
2026-10-12 19:16:23 +02:00
trace . counter ( parent , "ann_passes" , passes );
trace . counter ( parent , "result_files" , results . len () as u64 );
trace . counter (
parent ,
"filesystem_checks" ,
filesystem_validity . len () as u64 ,
);
2026-10-12 17:44:56 +02:00
snapshot . commit () ? ;
Ok ( results )
}
#[cfg(test)]
fn exact_search (
& self ,
query : & [ f32 ],
fingerprint : & str ,
limit : usize ,
options : & SearchOptions ,
) -> Result < Vec < SearchResult >> {
ensure! (
query . len () == self . dimensions && query . iter (). all ( | v | v . is_finite ()),
"Invalid query vector"
);
let active : Option < i64 > = self . connection . query_row ( "SELECT g.id FROM semantic_generations g JOIN semantic_meta m ON m.active_generation=g.id WHERE g.fingerprint=?1 AND g.dimensions=?2" , params! [ fingerprint , self . dimensions as i64 ], | row | row . get ( 0 )). optional () ? ;
let Some ( active ) = active else {
return Ok ( Vec ::new ());
};
let folder = options
. folder
. as_ref ()
. map ( std ::fs ::canonicalize )
. transpose () ?
. map ( | p | {
format! (
" {}{} " ,
p . to_string_lossy (). trim_end_matches ([ '/' , '\\' ]),
std ::path ::MAIN_SEPARATOR
)
});
let extension = options
. extension
. as_ref ()
. map ( | ext | ext . trim_start_matches ( '.' ). to_ascii_lowercase ());
let min_size = options . min_size . map ( i64 ::try_from ). transpose () ? ;
let max_size = options . max_size . map ( i64 ::try_from ). transpose () ? ;
let mut statement = self . connection . prepare ( "SELECT c.passage_id,c.path,f.filename,c.snippet,c.vector,c.content_hash,p.text,c.kind,c.number
FROM semantic_chunks c JOIN passages p ON p.rowid=c.passage_id AND p.file_path=c.path AND p.kind=c.kind AND p.number=c.number
JOIN files f ON f.path=c.path WHERE c.generation=?1
AND (?2 IS NULL OR substr(f.path,1,length(?2))=?2)
AND (?3 IS NULL OR f.extension=?3)
AND (?4 IS NULL OR f.size>=?4) AND (?5 IS NULL OR f.size<=?5)
AND (?6 IS NULL OR f.mtime/1000000>=?6) AND (?7 IS NULL OR f.mtime/1000000<=?7)
ORDER BY c.passage_id,c.ordinal LIMIT ?8" ) ? ;
// Fetch one extra to detect the baseline cap instead of silently hiding
// candidates. Metadata filtering precedes this cap and all top-k limits.
let mut rows = statement . query ( params! [
active ,
folder ,
extension ,
min_size ,
max_size ,
options . modified_after_unix_ms ,
options . modified_before_unix_ms ,
MAX_SCAN_CHUNKS as i64 + 1
]) ? ;
let mut hashes = HashMap ::< i64 , String > ::new ();
let mut best = HashMap ::< String , SearchResult > ::new ();
let mut scanned = 0 ;
while let Some ( row ) = rows . next () ? {
scanned += 1 ;
ensure! (
scanned <= MAX_SCAN_CHUNKS ,
"Exact-vector baseline exceeds {MAX_SCAN_CHUNKS} chunks; ANN integration required"
);
let passage_id : i64 = row . get ( 0 ) ? ;
let current_hash = if let Some ( hash ) = hashes . get ( & passage_id ) {
hash . clone ()
} else {
let hash = content_hash ( & row . get ::< _ , String > ( 6 ) ? );
hashes . insert ( passage_id , hash . clone ());
hash
};
if current_hash != row . get ::< _ , String > ( 5 ) ? {
continue ;
}
let bytes : Vec < u8 > = row . get ( 4 ) ? ;
ensure! (
bytes . len () == self . dimensions * 4 ,
"Corrupt stored embedding length"
);
let mut cosine = 0.0 ;
for ( dimension , value ) in bytes . as_chunks ::< 4 > (). 0. iter (). enumerate () {
let value = f32 ::from_le_bytes ( * value );
ensure! ( value . is_finite (), "Corrupt stored embedding value" );
cosine += value * query [ dimension ];
}
if cosine < MIN_COSINE {
continue ;
}
let path : String = row . get ( 1 ) ? ;
if best
. get ( & path )
. is_some_and ( | result | result . score >= f64 ::from ( cosine ))
{
continue ;
}
best . insert (
path . clone (),
SearchResult {
path ,
filename : row . get ( 2 ) ? ,
snippet : row . get ( 3 ) ? ,
score : f64 ::from ( cosine ),
match_kind : "semantic" . into (),
source : Some ( SourceLocation {
kind : row . get ( 7 ) ? ,
number : row . get ( 8 ) ? ,
}),
extraction_error : None ,
},
);
}
let mut results : Vec < _ > = best . into_values (). collect ();
results . sort_by ( | a , b | {
b . score
. total_cmp ( & a . score )
. then_with ( || a . path . cmp ( & b . path ))
});
results . truncate ( limit );
Ok ( results )
}
}
2026-10-12 19:16:23 +02:00
#[allow(clippy::too_many_arguments)]
fn collect_eligibility_traced (
connection : & Connection ,
generation : i64 ,
revision : i64 ,
filter : & FilterKey ,
path : Option <& str > ,
trace : & mut TraceCollector ,
parent : Option < SpanToken > ,
) -> Result < EligibilityCache > {
trace . timed (
"semantic.eligibility_filtered_scan_and_hash" ,
parent ,
| trace , span | {
let result = collect_eligibility ( connection , generation , revision , filter , path );
if let Ok ( cache ) = & result {
trace . counter ( span , "eligible_chunks" , cache . keys . len () as u64 );
}
trace . counter ( span , "single_dirty_file" , u64 ::from ( path . is_some ()));
result
},
)
}
2026-10-12 17:44:56 +02:00
fn collect_eligibility (
connection : & Connection ,
generation : i64 ,
revision : i64 ,
filter : & FilterKey ,
path : Option <& str > ,
) -> Result < EligibilityCache > {
2026-10-12 21:22:11 +02:00
let code = if filter . code_only {
format! ( " AND {} " , searchi_core ::code_file_predicate ())
} else {
String ::new ()
};
let mut statement = connection . prepare ( & format! ( "SELECT c.rowid,c.passage_id,c.content_hash,p.text,c.path,c.source_type FROM semantic_chunks c
2026-10-12 19:16:23 +02:00
LEFT JOIN passages p ON c.source_type='text' AND p.rowid=c.passage_id AND p.file_path=c.path AND p.kind=c.kind AND p.number=c.number
JOIN files f ON f.path=c.path WHERE c.generation=?1 AND ((c.source_type='text' AND p.rowid IS NOT NULL) OR (c.source_type!='text' AND c.file_size=f.size AND c.file_mtime=f.mtime))
2026-10-12 17:44:56 +02:00
AND (?2 IS NULL OR substr(f.path,1,length(?2))=?2) AND (?3 IS NULL OR f.extension=?3)
AND (?4 IS NULL OR f.size>=?4) AND (?5 IS NULL OR f.size<=?5)
AND (?6 IS NULL OR f.mtime/1000000>=?6) AND (?7 IS NULL OR f.mtime/1000000<=?7)
2026-10-12 19:16:23 +02:00
AND (?10 IS NULL OR EXISTS(SELECT 1 FROM media_metadata m WHERE m.path=f.path AND m.duration_ms>=?10))
AND (?11 IS NULL OR EXISTS(SELECT 1 FROM media_metadata m WHERE m.path=f.path AND m.duration_ms<=?11))
AND (?12 IS NULL OR c.source_type=?12)
2026-10-12 21:22:11 +02:00
AND (?9 IS NULL OR c.path=?9) {code} ORDER BY c.passage_id,c.ordinal LIMIT ?8" )) ? ;
2026-10-12 17:44:56 +02:00
let mut rows = statement . query ( params! [
generation ,
filter . folder ,
filter . extension ,
filter . min_size ,
filter . max_size ,
filter . modified_after ,
filter . modified_before ,
MAX_SCAN_CHUNKS as i64 + 1 ,
2026-10-12 19:16:23 +02:00
path ,
filter . min_duration ,
filter . max_duration ,
filter . source_type
2026-10-12 17:44:56 +02:00
]) ? ;
let mut hashes = HashMap ::< i64 , String > ::new ();
let mut keys = HashSet ::new ();
let mut by_path = HashMap ::< String , Vec < u64 >> ::new ();
let mut count = 0 ;
while let Some ( row ) = rows . next () ? {
count += 1 ;
ensure! (
count <= MAX_SCAN_CHUNKS ,
"Corpus exceeds {MAX_SCAN_CHUNKS} chunk resource budget"
);
let passage_id : i64 = row . get ( 1 ) ? ;
2026-10-12 19:16:23 +02:00
let hash = if row . get ::< _ , String > ( 5 ) ? != "text" {
row . get ::< _ , String > ( 2 ) ?
} else if let Some ( hash ) = hashes . get ( & passage_id ) {
2026-10-12 17:44:56 +02:00
hash . clone ()
} else {
let hash = content_hash ( & row . get ::< _ , String > ( 3 ) ? );
hashes . insert ( passage_id , hash . clone ());
hash
};
if hash == row . get ::< _ , String > ( 2 ) ? {
let key = u64 ::try_from ( row . get ::< _ , i64 > ( 0 ) ? ) ? ;
keys . insert ( key );
by_path . entry ( row . get ( 4 ) ? ). or_default (). push ( key );
}
}
Ok ( EligibilityCache {
generation ,
revision ,
filter : filter . clone (),
keys : Arc ::new ( keys ),
by_path ,
})
}
#[cfg(test)]
mod tests {
use super ::* ;
use searchi_core ::SearchIndex ;
const DIMENSIONS : usize = 768 ;
2026-10-12 21:22:11 +02:00
#[test]
fn code_filter_precedes_ann_top_k_and_cache_distinguishes_scope () -> Result < () > {
// Retrieval geometry only: production vectors come from Google.
let directory = tempfile ::tempdir () ? ;
let root = directory . path (). join ( "sources" );
std ::fs ::create_dir ( & root ) ? ;
for n in 0 .. 40 {
std ::fs ::write (
root . join ( format! ( "document- {n} .md" )),
"parse JSON and retry requests" ,
) ? ;
}
std ::fs ::write (
root . join ( "network.rs" ),
"fn retry_request() { parse_json(); }" ,
) ? ;
std ::fs ::write ( root . join ( "Makefile" ), "build: compile_sources \n " ) ? ;
let db = directory . path (). join ( "index.db" );
let mut lexical = SearchIndex ::open ( & db ) ? ;
lexical . index_folder ( & root ) ? ;
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let generation = store . begin ( "code-filter-proof" ) ? ;
let mut query = vec! [ 0. ; DIMENSIONS ];
query [ 0 ] = 1. ;
let mut weaker = query . clone ();
weaker [ 0 ] = 0.9 ;
weaker [ 1 ] = ( 1.0_ f32 - 0.9_ f32 . powi ( 2 )). sqrt ();
for passage in lexical . passage_batch ( 0 , 100 ) ? {
let vector = if searchi_core ::is_code_path ( Path ::new ( & passage . path )) {
weaker . clone ()
} else {
query . clone ()
};
let window = TextWindow {
text : passage . text . clone (),
start_byte : 0 ,
end_byte : passage . text . len (),
};
store . insert ( generation , & passage , 0 , & [ window ], & [ vector ]) ? ;
}
store . activate ( generation ) ? ;
assert! (
store . search ( & query , "code-filter-proof" , 1 , & SearchOptions ::default ()) ? [ 0 ]
. filename
. ends_with ( ".md" )
);
let code = SearchOptions {
code_only : true ,
.. Default ::default ()
};
let hits = store . search ( & query , "code-filter-proof" , 2 , & code ) ? ;
assert_eq! ( hits . len (), 2 );
assert! ( hits
. iter ()
. all ( | h | searchi_core ::is_code_path ( Path ::new ( & h . path ))));
let incompatible = SearchOptions {
code_only : true ,
extension : Some ( "md" . into ()),
.. Default ::default ()
};
assert! ( store
. search ( & query , "code-filter-proof" , 2 , & incompatible ) ?
. is_empty ());
std ::fs ::remove_file ( root . join ( "network.rs" )) ? ;
lexical . index_folder ( & root ) ? ;
assert_eq! (
store . search ( & query , "code-filter-proof" , 2 , & code ) ? . len (),
1
);
Ok (())
}
2026-10-12 19:16:23 +02:00
#[test]
fn many_video_windows_do_not_crowd_out_other_parent_files () -> Result < () > {
// Geometry verifies grouped retrieval, not model inference or codecs.
let directory = tempfile ::tempdir () ? ;
let root = directory . path (). join ( "media" );
std ::fs ::create_dir ( & root ) ? ;
for name in [ "long.mp4" , "other.mp4" , "excluded.png" ] {
std ::fs ::write ( root . join ( name ), b "parent-grouping identity fixture" ) ? ;
}
let db = directory . path (). join ( "index.db" );
let mut lexical = SearchIndex ::open ( & db ) ? ;
lexical . index_folder ( & root ) ? ;
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let generation = store . begin ( "grouped-retrieval-proof" ) ? ;
let mut query = vec! [ 0. ; DIMENSIONS ];
query [ 0 ] = 1. ;
let mut second = query . clone ();
second [ 0 ] = 0.95 ;
second [ 1 ] = ( 1.0_ f32 - 0.95_ f32 . powi ( 2 )). sqrt ();
for ( id , path , _ , _ ) in store . media_batch ( 0 ) ? {
let identity = searchi_media ::MediaIdentity ::read ( Path ::new ( & path ), 1024 ) ? ;
if path . ends_with ( "long.mp4" ) {
for ordinal in 0 .. 96 {
store . insert_media (
generation ,
id ,
ordinal ,
& identity ,
"video" ,
ordinal as u32 * 2000 ,
& query ,
) ? ;
}
} else if path . ends_with ( "other.mp4" ) {
store . insert_media ( generation , id , 0 , & identity , "video" , 4000 , & second ) ? ;
} else {
store . insert_media ( generation , id , 0 , & identity , "image" , 0 , & query ) ? ;
}
}
store . activate ( generation ) ? ;
let options = SearchOptions {
extension : Some ( "mp4" . into ()),
.. Default ::default ()
};
MEDIA_STAT_CALLS . with ( | count | count . set ( 0 ));
let mut trace = TraceCollector ::new ( MetricsMode ::Debug );
let results = store . search_origin_traced (
& query ,
"grouped-retrieval-proof" ,
2 ,
& options ,
None ,
& mut trace ,
None ,
) ? ;
assert_eq! ( results . len (), 2 );
assert! ( results [ 0 ]. path . ends_with ( "long.mp4" ));
assert! ( results [ 1 ]. path . ends_with ( "other.mp4" ));
assert_eq! ( results [ 1 ]. source . as_ref (). unwrap (). number , 4000 );
assert_eq! (
MEDIA_STAT_CALLS . with ( std ::cell ::Cell ::get ),
2 ,
"Each parent is stat-checked once despite repeated ANN expansions"
);
let metrics = trace . finish ( results . len ());
let spans = metrics . trace . unwrap ();
assert! (
spans
. iter ()
. filter ( | s | s . name == "semantic.ann_pass" )
. count ()
> 1
);
assert_eq! (
spans
. iter ()
. filter ( | s | s . name == "semantic.parent_filesystem_validation" )
. count (),
2
);
assert! (
spans
. iter ()
. find ( | s | s . name == "semantic.retrieval" )
. unwrap ()
. counters [ "ann_passes" ]
> 1
);
for span in & spans {
if let Some ( parent ) = span . parent {
let parent = & spans [ parent ];
assert! ( span . start_ms >= parent . start_ms );
assert! (
span . start_ms + span . duration_ms <= parent . start_ms + parent . duration_ms + 1e-6
);
}
}
Ok (())
}
#[test]
fn media_origins_persist_filters_and_changes_remove_stale_candidates () -> Result < () > {
let directory = tempfile ::tempdir () ? ;
let root = directory . path (). join ( "media" );
std ::fs ::create_dir ( & root ) ? ;
let file = root . join ( "clip.mp4" );
std ::fs ::write ( & file , b "storage identity fixture; not an inference test" ) ? ;
let db = directory . path (). join ( "index.db" );
let mut lexical = SearchIndex ::open ( & db ) ? ;
lexical . index_folder ( & root ) ? ;
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let ( id , path , _ , _ ) = store . media_batch ( 0 ) ? . remove ( 0 );
let identity = searchi_media ::MediaIdentity ::read ( Path ::new ( & path ), 1024 ) ? ;
lexical . set_media_metadata (
& path ,
"artist: real extracted metadata fixture" ,
65000 ,
identity . bytes ,
identity . modified_ns ,
) ? ;
let mut vector = vec! [ 0. ; DIMENSIONS ];
vector [ 0 ] = 1. ;
let generation = store . begin ( "media-storage-proof" ) ? ;
store . insert_media ( generation , id , 0 , & identity , "video" , 2345 , & vector ) ? ;
store . insert_media ( generation , id , 1 , & identity , "audio" , 28000 , & vector ) ? ;
store . activate ( generation ) ? ;
drop ( store );
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let mut cold = TraceCollector ::new ( MetricsMode ::Debug );
let results = store . search_origin_traced (
& vector ,
"media-storage-proof" ,
5 ,
& SearchOptions ::default (),
None ,
& mut cold ,
None ,
) ? ;
let cold = cold . finish ( results . len ()). trace . unwrap ();
assert_eq! (
cold . iter ()
. find ( | s | s . name == "semantic.ann_load" )
. unwrap ()
. counters [ "cache_hit" ],
0
);
assert! ( cold . iter (). any ( | s | s . name == "semantic.ann_checksum" ));
let mut warm = TraceCollector ::new ( MetricsMode ::Debug );
let warm_results = store . search_origin_traced (
& vector ,
"media-storage-proof" ,
5 ,
& SearchOptions ::default (),
None ,
& mut warm ,
None ,
) ? ;
assert_eq! (
serde_json ::to_value ( & results ) ? ,
serde_json ::to_value ( & warm_results ) ?
);
let warm = warm . finish ( warm_results . len ()). trace . unwrap ();
assert_eq! (
warm . iter ()
. find ( | s | s . name == "semantic.ann_load" )
. unwrap ()
. counters [ "cache_hit" ],
1
);
assert_eq! (
warm . iter ()
. find ( | s | s . name == "semantic.eligibility" )
. unwrap ()
. counters [ "cache_hit" ],
1
);
assert! ( ! warm . iter (). any ( | s | s . name == "semantic.ann_blob_read" ));
assert_eq! ( results . len (), 1 );
assert_eq! ( results [ 0 ]. source . as_ref (). unwrap (). kind , "timestamp_ms" );
assert! ([ 2345 , 28000 ]. contains ( & results [ 0 ]. source . as_ref (). unwrap (). number ));
let audio = store . search_origin (
& vector ,
"media-storage-proof" ,
5 ,
& SearchOptions ::default (),
Some ( "audio" ),
) ? ;
assert_eq! ( audio . len (), 1 );
assert_eq! ( audio [ 0 ]. match_kind , "semantic_audio" );
assert_eq! ( audio [ 0 ]. source . as_ref (). unwrap (). number , 28000 );
let video = store . search_origin (
& vector ,
"media-storage-proof" ,
5 ,
& SearchOptions ::default (),
Some ( "video" ),
) ? ;
assert_eq! ( video . len (), 1 );
assert_eq! ( video [ 0 ]. match_kind , "semantic_video" );
assert_eq! ( video [ 0 ]. source . as_ref (). unwrap (). number , 2345 );
assert! ( store
. search_origin (
& vector ,
"media-storage-proof" ,
5 ,
& SearchOptions ::default (),
Some ( "image" )
) ?
. is_empty ());
assert! (
results [ 0 ]. match_kind == "semantic_video" || results [ 0 ]. match_kind == "semantic_audio"
);
let filter = SearchOptions {
extension : Some ( "png" . into ()),
.. SearchOptions ::default ()
};
assert! ( store
. search ( & vector , "media-storage-proof" , 5 , & filter ) ?
. is_empty ());
let duration_filter = SearchOptions {
min_duration_ms : Some ( 66000 ),
.. Default ::default ()
};
assert! ( store
. search ( & vector , "media-storage-proof" , 5 , & duration_filter ) ?
. is_empty ());
let duration_filter = SearchOptions {
min_duration_ms : Some ( 64000 ),
max_duration_ms : Some ( 66000 ),
.. Default ::default ()
};
assert_eq! (
store
. search ( & vector , "media-storage-proof" , 5 , & duration_filter ) ?
. len (),
1
);
std ::fs ::write ( & file , b "changed" ) ? ;
// Foreground hits check only actual candidate metadata, before watcher
// reconciliation; they never rehash the complete filesystem corpus.
assert! ( store
. search ( & vector , "media-storage-proof" , 5 , & SearchOptions ::default ()) ?
. is_empty ());
lexical . index_folder ( & root ) ? ;
assert! ( store
. search ( & vector , "media-storage-proof" , 5 , & SearchOptions ::default ()) ?
. is_empty ());
assert! ( store
. insert_media ( generation , id , 2 , & identity , "video" , 0 , & vector )
. is_err ());
std ::fs ::remove_file ( & file ) ? ;
lexical . index_folder ( & root ) ? ;
assert! ( store
. search ( & vector , "media-storage-proof" , 5 , & SearchOptions ::default ()) ?
. is_empty ());
Ok (())
}
#[test]
fn media_failure_removes_partial_vectors_and_persists_reason () -> Result < () > {
let directory = tempfile ::tempdir () ? ;
let file = directory . path (). join ( "picture.png" );
std ::fs ::write ( & file , b "identity fixture" ) ? ;
let db = directory . path (). join ( "index.db" );
let mut lexical = SearchIndex ::open ( & db ) ? ;
lexical . index_folder ( directory . path ()) ? ;
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let ( id , path , _ , _ ) = store
. media_batch ( 0 ) ?
. into_iter ()
. find ( | ( _ , p , _ , _ ) | p . ends_with ( "picture.png" ))
. unwrap ();
let identity = searchi_media ::MediaIdentity ::read ( Path ::new ( & path ), 1024 ) ? ;
let mut vector = vec! [ 0. ; DIMENSIONS ];
vector [ 0 ] = 1. ;
let generation = store . begin ( "failure-proof" ) ? ;
store . insert_media ( generation , id , 0 , & identity , "image" , 0 , & vector ) ? ;
store . media_failure ( generation , & path , "Decoder rejected corrupt image" ) ? ;
let chunks : usize = store . connection . query_row (
"SELECT count(*) FROM semantic_chunks WHERE generation=?1" ,
[ generation ],
| r | r . get ( 0 ),
) ? ;
assert_eq! ( chunks , 0 );
let reason : String = store . connection . query_row (
"SELECT message FROM semantic_media_errors WHERE generation=?1" ,
[ generation ],
| r | r . get ( 0 ),
) ? ;
assert! ( reason . contains ( "corrupt" ));
Ok (())
}
#[test]
fn deleted_media_without_reconciliation_does_not_hide_later_valid_top_k () -> Result < () > {
let directory = tempfile ::tempdir () ? ;
let root = directory . path (). join ( "images" );
std ::fs ::create_dir ( & root ) ? ;
for name in [ "a.png" , "b.png" ] {
std ::fs ::write ( root . join ( name ), b "storage fixture" ) ? ;
}
let db = directory . path (). join ( "index.db" );
let mut lexical = SearchIndex ::open ( & db ) ? ;
lexical . index_folder ( & root ) ? ;
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let generation = store . begin ( "candidate-stat-proof" ) ? ;
let mut vector = vec! [ 0. ; DIMENSIONS ];
vector [ 0 ] = 1. ;
for ( id , path , _ , _ ) in store . media_batch ( 0 ) ? {
let identity = searchi_media ::MediaIdentity ::read ( Path ::new ( & path ), 1024 ) ? ;
store . insert_media ( generation , id , 0 , & identity , "image" , 0 , & vector ) ? ;
}
store . activate ( generation ) ? ;
std ::fs ::remove_file ( root . join ( "a.png" )) ? ;
let results = store . search (
& vector ,
"candidate-stat-proof" ,
1 ,
& SearchOptions ::default (),
) ? ;
assert_eq! ( results . len (), 1 );
assert! ( results [ 0 ]. path . ends_with ( "b.png" ));
std ::fs ::remove_file ( root . join ( "b.png" )) ? ;
assert! ( store
. search (
& vector ,
"candidate-stat-proof" ,
1 ,
& SearchOptions ::default ()
) ?
. is_empty ());
Ok (())
}
2026-10-12 17:44:56 +02:00
#[test]
fn generation_persists_failed_staging_preserves_active_and_changes_hide_stale_vectors (
) -> Result < () > {
let directory = tempfile ::tempdir () ? ;
let root = directory . path (). join ( "docs" );
std ::fs ::create_dir ( & root ) ? ;
let db = directory . path (). join ( "index.db" );
std ::fs ::write ( root . join ( "notes.txt" ), "original relevant content" ) ? ;
let mut index = SearchIndex ::open ( & db ) ? ;
index . index_folder ( & root ) ? ;
let passage = index . passage_batch ( 0 , 1 ) ? . remove ( 0 );
let mut vector = vec! [ 0.0 ; DIMENSIONS ];
vector [ 0 ] = 1.0 ;
let windows = [ TextWindow {
text : passage . text . clone (),
start_byte : 0 ,
end_byte : passage . text . len (),
}];
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let generation = store . begin ( "test-storage-vectors" ) ? ;
store . insert ( generation , & passage , 0 , & windows , & [ vector . clone ()]) ? ;
store . activate ( generation ) ? ;
assert_eq! (
store . exact_search (
& vector ,
"test-storage-vectors" ,
10 ,
& SearchOptions ::default ()
) ? [ 0 ]
. path ,
store . search (
& vector ,
"test-storage-vectors" ,
10 ,
& SearchOptions ::default ()
) ? [ 0 ]
. path
);
drop ( store );
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
assert_eq! (
store
. search (
& vector ,
"test-storage-vectors" ,
10 ,
& SearchOptions ::default ()
) ?
. len (),
1
);
assert! ( store
. search ( & vector , "different-model" , 10 , & SearchOptions ::default ()) ?
. is_empty ());
let staging = store . begin ( "new-model" ) ? ;
assert_eq! (
store
. search (
& vector ,
"test-storage-vectors" ,
10 ,
& SearchOptions ::default ()
) ?
. len (),
1
);
store . abandon ( staging ) ? ;
std ::fs ::write (
root . join ( "notes.txt" ),
"replacement unrelated content longer than before" ,
) ? ;
index . index_folder ( & root ) ? ;
assert! ( store
. search (
& vector ,
"test-storage-vectors" ,
10 ,
& SearchOptions ::default ()
) ?
. is_empty ());
std ::fs ::remove_file ( root . join ( "notes.txt" )) ? ;
index . index_folder ( & root ) ? ;
assert! ( store
. search (
& vector ,
"test-storage-vectors" ,
10 ,
& SearchOptions ::default ()
) ?
. is_empty ());
Ok (())
}
#[test]
fn ann_filters_before_top_k_checks_checksum_and_status_tracks_corpus_changes () -> Result < () > {
let directory = tempfile ::tempdir () ? ;
let root = directory . path (). join ( "docs" );
std ::fs ::create_dir ( & root ) ? ;
let db = directory . path (). join ( "index.db" );
std ::fs ::write ( root . join ( "closest.md" ), "closest metadata excluded source" ) ? ;
std ::fs ::write ( root . join ( "allowed.txt" ), "allowed metadata source" ) ? ;
let mut lexical = SearchIndex ::open ( & db ) ? ;
lexical . index_folder ( & root ) ? ;
let passages = lexical . passage_batch ( 0 , 10 ) ? ;
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
let fingerprint = crate ::generation_fingerprint ( searchi_semantic ::GEMMA_FINGERPRINT );
let generation = store . begin ( & fingerprint ) ? ;
let mut query = vec! [ 0.0 ; DIMENSIONS ];
query [ 0 ] = 1.0 ;
for passage in & passages {
let mut vector = query . clone ();
if passage . filename == "allowed.txt" {
vector [ 0 ] = 0.8 ;
vector [ 1 ] = 0.6 ;
}
let windows = [ TextWindow {
text : passage . text . clone (),
start_byte : 0 ,
end_byte : passage . text . len (),
}];
store . insert ( generation , passage , 0 , & windows , & [ vector ]) ? ;
}
store . activate ( generation ) ? ;
let options = SearchOptions {
extension : Some ( "txt" . into ()),
.. Default ::default ()
};
assert_eq! (
store . search ( & query , & fingerprint , 1 , & options ) ? [ 0 ]. filename ,
"allowed.txt"
);
let warmed_hashes = HASH_CALLS . with ( std ::cell ::Cell ::get );
let _ = store . search ( & query , & fingerprint , 1 , & options ) ? ;
assert_eq! (
HASH_CALLS . with ( std ::cell ::Cell ::get ),
warmed_hashes ,
"Warm query rehashed unchanged corpus"
);
assert_eq! (
store . exact_search ( & query , & fingerprint , 1 , & options ) ? [ 0 ]. filename ,
"allowed.txt"
);
assert! ( ! crate ::HybridSearch ::embedding_status ( & db ) ? . stale );
std ::fs ::write (
root . join ( "allowed.txt" ),
"replacement changes the generation revision" ,
) ? ;
lexical . index_folder ( & root ) ? ;
let status = crate ::HybridSearch ::embedding_status ( & db ) ? ;
let before_delta = HASH_CALLS . with ( std ::cell ::Cell ::get );
assert! ( store . search ( & query , & fingerprint , 1 , & options ) ? . is_empty ());
assert! (
HASH_CALLS . with ( std ::cell ::Cell ::get ) - before_delta <= 1 ,
"Single file change rehashed unrelated passages"
);
assert! ( status . stale );
assert_eq! ( status . pending , 2 );
store . connection . execute (
"UPDATE semantic_generations SET ann_blob=x'010203' WHERE id=?1" ,
[ generation ],
) ? ;
drop ( store );
let mut store = Store ::open ( & db , DIMENSIONS ) ? ;
assert! ( store
. search ( & query , & fingerprint , 1 , & SearchOptions ::default ())
. unwrap_err ()
. to_string ()
. contains ( "SHA256 mismatch" ));
Ok (())
}
}
#[cfg(test)]
#[path = "ann_benchmark.rs" ]
mod ann_benchmark ;