Refuse a name the client does not cover, however many it covers
`reader_for` returned itself before looking at the name, so an "alpha" client
answered `reader_for("beta")` with alpha's reader. A citation naming another
database would have been read from the wrong one.
Both paths decide through `clients_covering` now, so one database refuses a
wrong name the way a set already did.
The chat citation test said mounting lends the client without showing it; a
chat test shows it and the docstring points there.
This commit is contained in:
parent
45a5e68da5
commit
d93cb5c891
4 changed files with 48 additions and 11 deletions
|
|
@ -198,15 +198,14 @@ class HaikuRAG:
|
||||||
database.
|
database.
|
||||||
|
|
||||||
None only when a client covering a set is given no name, as for evidence
|
None only when a client covering a set is given no name, as for evidence
|
||||||
recorded before databases could be named. A name outside the set raises
|
recorded before databases could be named. A name this client does not
|
||||||
`KeyError`, the same as `clients_for`: provenance naming a database this
|
cover raises `KeyError`, decided by `clients_covering` so that one
|
||||||
client does not cover is wrong rather than absent.
|
database answers a wrong name the same way a set does: provenance naming
|
||||||
|
another database is wrong rather than absent.
|
||||||
"""
|
"""
|
||||||
if not self.covers_multiple:
|
|
||||||
return self
|
|
||||||
if source is None:
|
if source is None:
|
||||||
return None
|
return None if self.covers_multiple else self
|
||||||
(owner,) = await self.clients_for([source])
|
(owner,) = await self.clients_covering([source])
|
||||||
return owner
|
return owner
|
||||||
|
|
||||||
def _single_session(self, operation: str) -> SingleDatabaseSession:
|
def _single_session(self, operation: str) -> SingleDatabaseSession:
|
||||||
|
|
|
||||||
|
|
@ -625,6 +625,25 @@ async def test_visual_grounding_uses_the_database_holding_the_citation(tmp_path)
|
||||||
assert push.await_args.args[0].client is owner
|
assert push.await_args.args[0].client is owner
|
||||||
|
|
||||||
|
|
||||||
|
class TestLendingTheClient:
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mounting_lends_its_client_to_every_capability(self, temp_db_path):
|
||||||
|
"""Capabilities are built before the client exists, so each reads through
|
||||||
|
the one the app opened rather than opening its own."""
|
||||||
|
client = _make_mock_client()
|
||||||
|
app, _ = _make_app(temp_db_path, client)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch("haiku.rag.chat.app.HaikuRAG") as stub_rag,
|
||||||
|
_covering_returns(stub_rag, client),
|
||||||
|
):
|
||||||
|
async with app.run_test():
|
||||||
|
borrowed = [c.borrowed_rag for c in app._capabilities]
|
||||||
|
|
||||||
|
assert borrowed == [client] * len(app._capabilities)
|
||||||
|
assert borrowed
|
||||||
|
|
||||||
|
|
||||||
class TestDocumentSelectionIdentity:
|
class TestDocumentSelectionIdentity:
|
||||||
"""Two documents can share a title, within a corpus and across databases, so
|
"""Two documents can share a title, within a corpus and across databases, so
|
||||||
the selection is by id and the label says which database."""
|
the selection is by id and the label says which database."""
|
||||||
|
|
|
||||||
|
|
@ -203,9 +203,9 @@ class TestCollectionIdentityForTheModel:
|
||||||
class TestLendingANamedClient:
|
class TestLendingANamedClient:
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_a_lent_named_client_names_the_citation(self, tmp_path):
|
async def test_a_lent_named_client_names_the_citation(self, tmp_path):
|
||||||
"""The chat TUI builds capabilities before its client exists and lends
|
"""What a citation records is the lent client's database, not the scope
|
||||||
it on mount, so what a citation records is the lent client's database
|
the capability was constructed with. That chat lends its client is
|
||||||
and not whatever scope the capability was constructed with."""
|
`TestLendingTheClient` in `tests/chat/test_chat_app.py`."""
|
||||||
from tests.capabilities.test_capabilities import Deps, make_context
|
from tests.capabilities.test_capabilities import Deps, make_context
|
||||||
|
|
||||||
config = _config(tmp_path, ["alpha", "beta"])
|
config = _config(tmp_path, ["alpha", "beta"])
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,26 @@ class TestPlacingADatabase:
|
||||||
async def test_a_client_reading_one_database_is_its_own_reader(self, temp_db_path):
|
async def test_a_client_reading_one_database_is_its_own_reader(self, temp_db_path):
|
||||||
async with HaikuRAG(temp_db_path, create=True) as rag:
|
async with HaikuRAG(temp_db_path, create=True) as rag:
|
||||||
assert await rag.reader_for(None) is rag
|
assert await rag.reader_for(None) is rag
|
||||||
assert await rag.reader_for("anything") is rag
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_one_database_refuses_a_name_it_does_not_cover(self, tmp_path):
|
||||||
|
"""Answering with itself would hand back the wrong database's reader for
|
||||||
|
a citation that named another."""
|
||||||
|
config = _config(tmp_path, ["alpha", "beta"])
|
||||||
|
await _seed(config, "alpha", ["alpha one"])
|
||||||
|
await _seed(config, "beta", ["beta one"])
|
||||||
|
|
||||||
|
async with HaikuRAG(config=config, sources=["alpha"]) as alpha:
|
||||||
|
assert await alpha.reader_for("alpha") is alpha
|
||||||
|
with pytest.raises(KeyError, match="beta"):
|
||||||
|
await alpha.reader_for("beta")
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_an_unnamed_database_refuses_any_name(self, temp_db_path):
|
||||||
|
"""Nothing names it, so no name can be the one it covers."""
|
||||||
|
async with HaikuRAG(temp_db_path, create=True) as rag:
|
||||||
|
with pytest.raises(KeyError, match="single unnamed database"):
|
||||||
|
await rag.reader_for("anything")
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_a_set_cannot_place_evidence_that_names_no_database(self, tmp_path):
|
async def test_a_set_cannot_place_evidence_that_names_no_database(self, tmp_path):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue