refactor(core): Rename GiError to LoreError and add search infrastructure

Mechanical rename of GiError -> LoreError across the core module to
match the project's rebranding from gitlab-inbox to gitlore/lore.
Updates the error enum name, all From impls, and the Result type alias.

Additionally introduces:

- New error variants for embedding pipeline: OllamaUnavailable,
  OllamaModelNotFound, EmbeddingFailed, EmbeddingsNotBuilt. Each
  includes actionable suggestions (e.g., "ollama serve", "ollama pull
  nomic-embed-text") to guide users through recovery.

- New error codes 14-16 for programmatic handling of Ollama failures.

- Savepoint-based migration execution in db.rs: each migration now
  runs inside a SQLite SAVEPOINT so a failed migration rolls back
  cleanly without corrupting the schema_version tracking. Previously
  a partial migration could leave the database in an inconsistent
  state.

- core::backoff module: exponential backoff with jitter utility for
  retry loops in the embedding pipeline and discussion queues.

- core::project module: helper for resolving project IDs and paths
  from the local database, used by the document regenerator and
  search filters.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Taylor Eernisse
2026-01-30 15:45:54 -05:00
parent 4270603da4
commit 6e22f120d0
8 changed files with 361 additions and 22 deletions

View File

@@ -8,7 +8,7 @@ use std::fs;
use std::path::Path;
use tracing::{debug, info};
use super::error::{GiError, Result};
use super::error::{LoreError, Result};
/// Embedded migrations - compiled into the binary.
const MIGRATIONS: &[(&str, &str)] = &[
@@ -27,6 +27,18 @@ const MIGRATIONS: &[(&str, &str)] = &[
"006",
include_str!("../../migrations/006_merge_requests.sql"),
),
(
"007",
include_str!("../../migrations/007_documents.sql"),
),
(
"008",
include_str!("../../migrations/008_fts5.sql"),
),
(
"009",
include_str!("../../migrations/009_embeddings.sql"),
),
];
/// Create a database connection with production-grade pragmas.
@@ -88,13 +100,36 @@ pub fn run_migrations(conn: &Connection) -> Result<()> {
continue;
}
conn.execute_batch(sql)
.map_err(|e| GiError::MigrationFailed {
// Wrap each migration in a transaction to prevent partial application.
// If the migration SQL already contains BEGIN/COMMIT, execute_batch handles
// it, but wrapping in a savepoint ensures atomicity for those that don't.
let savepoint_name = format!("migration_{}", version);
conn.execute_batch(&format!("SAVEPOINT {}", savepoint_name))
.map_err(|e| LoreError::MigrationFailed {
version,
message: e.to_string(),
message: format!("Failed to create savepoint: {}", e),
source: Some(e),
})?;
match conn.execute_batch(sql) {
Ok(()) => {
conn.execute_batch(&format!("RELEASE {}", savepoint_name))
.map_err(|e| LoreError::MigrationFailed {
version,
message: format!("Failed to release savepoint: {}", e),
source: Some(e),
})?;
}
Err(e) => {
let _ = conn.execute_batch(&format!("ROLLBACK TO {}", savepoint_name));
return Err(LoreError::MigrationFailed {
version,
message: e.to_string(),
source: Some(e),
});
}
}
info!(version, "Migration applied");
}
@@ -146,7 +181,7 @@ pub fn run_migrations_from_dir(conn: &Connection, migrations_dir: &Path) -> Resu
let sql = fs::read_to_string(entry.path())?;
conn.execute_batch(&sql)
.map_err(|e| GiError::MigrationFailed {
.map_err(|e| LoreError::MigrationFailed {
version,
message: e.to_string(),
source: Some(e),