use std::path::PathBuf;
use std::sync::Arc;
use trusty_common::migrations::{
file_stamp::{read_version_from_file, write_version_to_file},
MigrationRunner, SchemaVersion,
};
use crate::core::{
corpus::CorpusStore,
embed::Embedder,
indexer::{migrations::JsonCorpusToRedbMigration, CodeIndexer},
store::{UsearchStore, VectorStore},
};
use crate::service::persistence;
pub async fn build_indexer_with_persisted_state(
index_id: &str,
root_path: PathBuf,
embedder: &Arc<dyn Embedder>,
) -> CodeIndexer {
let dim = embedder.dimension();
let store: Arc<dyn VectorStore> = build_store(index_id, dim).await;
let mut indexer =
CodeIndexer::new(index_id, root_path).with_components(Arc::clone(embedder), store);
match persistence::corpus_redb_path(index_id) {
Ok(redb_path) => match CorpusStore::open(&redb_path) {
Ok(corpus) => indexer.set_corpus_store(Arc::new(corpus)),
Err(e) => tracing::warn!(
"warm-boot: could not open redb corpus for '{index_id}' at {} ({e}) — \
running without durable corpus store",
redb_path.display()
),
},
Err(e) => tracing::warn!("cannot resolve redb corpus path for '{index_id}': {e}"),
}
restore_corpus(&mut indexer, index_id).await;
indexer
}
async fn restore_corpus(indexer: &mut CodeIndexer, index_id: &str) {
match indexer.load_chunks_from_redb().await {
Ok(n) if n > 0 => {
tracing::info!("warm-boot: restored {n} chunks for index '{index_id}' from redb");
stamp_if_unversioned(index_id);
return;
}
Ok(_) => {} Err(e) => tracing::warn!(
"warm-boot: redb corpus load failed for '{index_id}' ({e}) — \
trying registered migrations"
),
}
run_migrations(indexer, index_id);
}
fn run_migrations(indexer: &mut CodeIndexer, index_id: &str) {
let stamp_path = match persistence::schema_version_path(index_id) {
Ok(p) => p,
Err(e) => {
tracing::warn!("cannot resolve schema version path for '{index_id}': {e}");
return;
}
};
let current = match read_version_from_file(&stamp_path) {
Ok(v) => v,
Err(e) => {
tracing::warn!(
"warm-boot: failed to read schema stamp at {} ({e}) — \
treating as UNVERSIONED",
stamp_path.display()
);
SchemaVersion::UNVERSIONED
}
};
let runner = MigrationRunner::new(vec![Box::new(JsonCorpusToRedbMigration)]);
if let Err(e) = runner.run(indexer, current, |v| write_version_to_file(&stamp_path, v)) {
tracing::warn!(
"warm-boot: migration runner failed for '{index_id}' ({e}) — \
starting with whatever state was restored"
);
}
}
fn stamp_if_unversioned(index_id: &str) {
use crate::core::indexer::migrations::TRUSTY_SEARCH_SCHEMA_TARGET;
let stamp_path = match persistence::schema_version_path(index_id) {
Ok(p) => p,
Err(e) => {
tracing::warn!("cannot resolve schema version path for '{index_id}': {e}");
return;
}
};
let current = read_version_from_file(&stamp_path).unwrap_or(SchemaVersion::UNVERSIONED);
if current >= TRUSTY_SEARCH_SCHEMA_TARGET {
return;
}
if let Err(e) = write_version_to_file(&stamp_path, TRUSTY_SEARCH_SCHEMA_TARGET) {
tracing::warn!(
"warm-boot: failed to bump schema stamp for '{index_id}' at {} ({e})",
stamp_path.display()
);
}
}
async fn build_store(index_id: &str, dim: usize) -> Arc<dyn VectorStore> {
let path = match persistence::hnsw_path(index_id) {
Ok(p) => p,
Err(e) => {
tracing::warn!("cannot resolve hnsw path for '{index_id}': {e}");
return fresh_store(dim);
}
};
if persistence::has_persisted_hnsw(&path) {
match UsearchStore::load_from(&path).await {
Ok(Some(store)) => {
if store.dim() == dim {
tracing::info!(
"warm-boot: restored HNSW snapshot for '{}' from {}",
index_id,
path.display()
);
return Arc::new(store);
}
tracing::warn!(
"warm-boot: hnsw snapshot for '{}' has dim {} but embedder is {} — starting fresh",
index_id,
store.dim(),
dim
);
}
Ok(None) => {
tracing::warn!(
"warm-boot: hnsw snapshot at {} could not be loaded — starting fresh",
path.display()
);
}
Err(e) => {
tracing::warn!(
"warm-boot: error loading hnsw snapshot at {}: {e} — starting fresh",
path.display()
);
}
}
}
fresh_store(dim)
}
fn fresh_store(dim: usize) -> Arc<dyn VectorStore> {
let s = UsearchStore::new(dim).unwrap_or_else(|e| {
tracing::error!(
"failed to allocate UsearchStore (dim={dim}): {e} — daemon cannot continue"
);
panic!("usearch alloc failure (OOM during HNSW init, dim={dim}): {e}");
});
Arc::new(s) as Arc<dyn VectorStore>
}