diff --git a/src/haiku/rag/store/engine.py b/src/haiku/rag/store/engine.py index 4133b6dc..af7b27c4 100644 --- a/src/haiku/rag/store/engine.py +++ b/src/haiku/rag/store/engine.py @@ -13,6 +13,12 @@ class Store: self.db_path: Path | Literal[":memory:"] = db_path self._connection = self.create_db() + # Validate config compatibility after connection is established + from haiku.rag.store.repositories.settings import SettingsRepository + + settings_repo = SettingsRepository(self) + settings_repo.validate_config_compatibility() + def create_db(self) -> sqlite3.Connection: """Create the database and tables with sqlite-vec support for embeddings.""" db = sqlite3.connect(self.db_path) diff --git a/src/haiku/rag/store/repositories/settings.py b/src/haiku/rag/store/repositories/settings.py index d51cb0d5..add87fa5 100644 --- a/src/haiku/rag/store/repositories/settings.py +++ b/src/haiku/rag/store/repositories/settings.py @@ -4,6 +4,12 @@ from typing import Any from haiku.rag.store.engine import Store +class ConfigMismatchError(Exception): + """Raised when current config doesn't match stored settings.""" + + pass + + class SettingsRepository: def __init__(self, store: Store): self.store = store @@ -34,3 +40,39 @@ class SettingsRepository: ) self.store._connection.commit() + + def validate_config_compatibility(self) -> None: + """Check if current config is compatible with stored settings. + + Raises ConfigMismatchError if there are incompatible differences. + If no settings exist, saves current config. + """ + db_settings = self.get() + if not db_settings: + # No settings in DB, save current config + self.save() + return + + from haiku.rag.config import Config + + current_config = Config.model_dump(mode="json") + + # Critical settings that must match + critical_settings = [ + "EMBEDDINGS_PROVIDER", + "EMBEDDINGS_MODEL", + "EMBEDDINGS_VECTOR_DIM", + "CHUNK_SIZE", + "CHUNK_OVERLAP", + ] + + errors = [] + for setting in critical_settings: + if db_settings.get(setting) != current_config.get(setting): + errors.append( + f"{setting}: current={current_config.get(setting)}, stored={db_settings.get(setting)}" + ) + + if errors: + error_msg = f"Config mismatch detected: {'; '.join(errors)}. Consider rebuilding the database with the current configuration." + raise ConfigMismatchError(error_msg) diff --git a/tests/test_settings.py b/tests/test_settings.py index aaec6ac5..8f6f49d7 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,6 +1,14 @@ +import tempfile +from pathlib import Path + +import pytest + from haiku.rag.config import Config from haiku.rag.store.engine import Store -from haiku.rag.store.repositories.settings import SettingsRepository +from haiku.rag.store.repositories.settings import ( + ConfigMismatchError, + SettingsRepository, +) def test_settings_table_populated_on_store_init(): @@ -31,3 +39,35 @@ def test_settings_save_and_retrieve(): Config.CHUNK_SIZE = original_chunk_size store.close() + + +def test_config_validation_on_db_load(): + """Test that config validation fails when loading db with mismatched settings.""" + # Create a temporary database file + with tempfile.NamedTemporaryFile(suffix=".sqlite", delete=False) as tmp: + db_path = Path(tmp.name) + + try: + # Create store and save settings + store1 = Store(db_path) + SettingsRepository(store1) + store1.close() + + # Change config + original_chunk_size = Config.CHUNK_SIZE + Config.CHUNK_SIZE = 999 + + # Loading the database should raise ConfigMismatchError + with pytest.raises(ConfigMismatchError) as exc_info: + Store(db_path) + + assert "CHUNK_SIZE" in str(exc_info.value) + assert "Consider rebuilding" in str(exc_info.value) + + # Restore original config + Config.CHUNK_SIZE = original_chunk_size + + finally: + # Cleanup + if db_path.exists(): + db_path.unlink()