Open read CLI verbs read-only
This commit is contained in:
parent
0e3d53791f
commit
414be551fb
4 changed files with 70 additions and 10 deletions
|
|
@ -8,6 +8,7 @@
|
|||
### Fixed
|
||||
|
||||
- Opening a database no longer writes to it: reads no longer rewrite the stored embedding settings or change the stored version, and the version is never downgraded. Embedding provider/name drift (matching `vector_dim`) warns on read-only opens and raises `ConfigMismatchError` on writable opens; reconcile with `rebuild --set-embedder`.
|
||||
- Read CLI verbs (`list`, `get`, `search`, `visualize`, `ask`, `analyze`, `inspect`, `chat`, `info`, `history`) open the database read-only.
|
||||
|
||||
## [0.54.0] - 2026-06-04
|
||||
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
read_only=self.read_only,
|
||||
read_only=True,
|
||||
before=self.before,
|
||||
) as self.client:
|
||||
documents = await self.client.list_documents(filter=filter)
|
||||
|
|
@ -328,7 +328,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
read_only=self.read_only,
|
||||
read_only=True,
|
||||
before=self.before,
|
||||
) as self.client:
|
||||
doc = await self.client.get_document_by_id(doc_id)
|
||||
|
|
@ -385,7 +385,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
read_only=self.read_only,
|
||||
read_only=True,
|
||||
before=self.before,
|
||||
) as self.client:
|
||||
results = await self.client.search(
|
||||
|
|
@ -407,7 +407,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
read_only=self.read_only,
|
||||
read_only=True,
|
||||
before=self.before,
|
||||
) as self.client:
|
||||
chunk = await self.client.get_chunk_by_id(chunk_id)
|
||||
|
|
@ -451,7 +451,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
read_only=self.read_only,
|
||||
read_only=True,
|
||||
before=self.before,
|
||||
) as self.client:
|
||||
answer, citations = await self.client.ask(question, filter=filter)
|
||||
|
|
@ -479,7 +479,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
async with HaikuRAG(
|
||||
db_path=self.db_path,
|
||||
config=self.config,
|
||||
read_only=self.read_only,
|
||||
read_only=True,
|
||||
before=self.before,
|
||||
) as self.client:
|
||||
self.console.print(f"[bold blue]Question:[/bold blue] {question}")
|
||||
|
|
@ -566,6 +566,7 @@ class HaikuRAGApp: # pragma: no cover
|
|||
config=self.config,
|
||||
skip_validation=True,
|
||||
skip_migration_check=True,
|
||||
read_only=self.read_only,
|
||||
) as store:
|
||||
return await store.migrate()
|
||||
|
||||
|
|
|
|||
|
|
@ -406,7 +406,7 @@ def analyze( # pragma: no cover
|
|||
@_cli.command("settings", help="Display current configuration settings")
|
||||
def settings(): # pragma: no cover
|
||||
config = get_config()
|
||||
app = HaikuRAGApp(db_path=Path(), config=config)
|
||||
app = HaikuRAGApp(db_path=Path(), config=config, read_only=True)
|
||||
app.show_settings()
|
||||
|
||||
|
||||
|
|
@ -612,7 +612,7 @@ def history( # pragma: no cover
|
|||
|
||||
@_cli.command("download-models", help="Download Docling and Ollama models per config")
|
||||
def download_models_cmd(): # pragma: no cover
|
||||
app = HaikuRAGApp(db_path=Path(), config=get_config())
|
||||
app = HaikuRAGApp(db_path=Path(), config=get_config(), read_only=True)
|
||||
try:
|
||||
asyncio.run(app.download_models())
|
||||
except Exception as e:
|
||||
|
|
@ -636,7 +636,7 @@ def inspect( # pragma: no cover
|
|||
raise typer.Exit(1) from e
|
||||
|
||||
db_path = db if db else get_config().storage.data_dir / "haiku.rag.lancedb"
|
||||
run_inspector(db_path, read_only=_read_only, before=_before)
|
||||
run_inspector(db_path, read_only=True, before=_before)
|
||||
|
||||
|
||||
@_cli.command("chat", help="Launch interactive chat TUI for conversational RAG")
|
||||
|
|
@ -666,7 +666,7 @@ def chat( # pragma: no cover
|
|||
|
||||
run_chat(
|
||||
db_path,
|
||||
read_only=_read_only,
|
||||
read_only=True,
|
||||
before=_before,
|
||||
model=model,
|
||||
skills=skills,
|
||||
|
|
|
|||
|
|
@ -298,3 +298,61 @@ class TestClientReadOnly:
|
|||
async with HaikuRAG(temp_db_path, read_only=True) as client:
|
||||
docs = await client.list_documents()
|
||||
assert len(docs) == 1
|
||||
|
||||
|
||||
class TestAppReadVerbsDoNotWrite:
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_verb_leaves_settings_and_version_unchanged_on_drift(
|
||||
self, temp_db_path
|
||||
):
|
||||
"""A read CLI verb opens read-only: drift warns but never writes."""
|
||||
from haiku.rag.app import HaikuRAGApp
|
||||
from haiku.rag.config import AppConfig
|
||||
|
||||
async with Store(temp_db_path, create=True) as store:
|
||||
stored_name_before = (
|
||||
await SettingsRepository(store).get_current_settings()
|
||||
)["embeddings"]["model"]["name"]
|
||||
version_before = await store.get_haiku_version()
|
||||
|
||||
drift = AppConfig()
|
||||
drift.embeddings.model.name = "different-model"
|
||||
|
||||
# list is a read verb — must open read-only and not raise on drift
|
||||
app = HaikuRAGApp(db_path=temp_db_path, config=drift)
|
||||
await app.list_documents()
|
||||
|
||||
async with Store(temp_db_path, skip_validation=True, read_only=True) as store:
|
||||
stored_name_after = (
|
||||
await SettingsRepository(store).get_current_settings()
|
||||
)["embeddings"]["model"]["name"]
|
||||
version_after = await store.get_haiku_version()
|
||||
|
||||
assert stored_name_after == stored_name_before
|
||||
assert version_after == version_before
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write_verb_raises_on_drift_without_writing(self, temp_db_path):
|
||||
"""A write CLI verb opens writable: drift raises before any write."""
|
||||
from haiku.rag.app import HaikuRAGApp
|
||||
from haiku.rag.config import AppConfig
|
||||
from haiku.rag.store.repositories.settings import ConfigMismatchError
|
||||
|
||||
async with Store(temp_db_path, create=True) as store:
|
||||
stored_name_before = (
|
||||
await SettingsRepository(store).get_current_settings()
|
||||
)["embeddings"]["model"]["name"]
|
||||
|
||||
drift = AppConfig()
|
||||
drift.embeddings.model.name = "different-model"
|
||||
|
||||
app = HaikuRAGApp(db_path=temp_db_path, config=drift)
|
||||
with pytest.raises(ConfigMismatchError):
|
||||
await app.add_document_from_text("hello")
|
||||
|
||||
async with Store(temp_db_path, skip_validation=True, read_only=True) as store:
|
||||
stored_name_after = (
|
||||
await SettingsRepository(store).get_current_settings()
|
||||
)["embeddings"]["model"]["name"]
|
||||
|
||||
assert stored_name_after == stored_name_before
|
||||
|
|
|
|||
Loading…
Reference in a new issue