fix: close internal semantic review gaps

This commit is contained in:
2026-08-08 23:27:58 +02:00
parent 43d8063922
commit 2c4534d968
18 changed files with 806 additions and 77 deletions
+105
View File
@@ -212,6 +212,27 @@ def test_upsert_refuses_collection_dimension_or_distance_mismatch_without_recrea
assert creates == []
def test_health_fails_when_the_bound_collection_is_missing():
fake = FakeQdrantHttp()
health = _store(fake).health()
assert health.ok is False
assert health.read_reachable is False
assert health.write_reachable is False
assert "missing" in (health.detail or "").lower()
def test_health_fails_when_required_payload_indexes_are_missing_without_creating_them():
fake = FakeQdrantHttp()
fake.collection = {"vectors": {"size": 1024, "distance": "Cosine"}}
health = _store(fake).health()
assert health.ok is False
assert fake.payload_indexes == set()
@pytest.mark.parametrize(
("record", "semantic_kind"),
[
@@ -307,6 +328,90 @@ def test_existing_hashes_health_and_exact_generation_inventory_and_delete():
assert health.dimension_compatible is True
def test_metadata_search_rejects_a_workspace_id_different_from_the_bound_adapter():
fake = FakeQdrantHttp()
store = _store(fake)
generation = "gen:" + "1" * 32
store.upsert("evidence", [
_write_record(
f"demo:{generation}:chunk:1",
"evidence",
metadata={
"workspace_id": "demo", "vector_generation": generation,
"document_id": "doc:shared",
},
),
])
foreign = next(iter(fake.points.values())).copy()
foreign["id"] = point_id("other", "evidence", f"other:{generation}:chunk:1")
foreign["payload"] = {
**foreign["payload"],
"workspace_id": "other",
"record_key": f"other:{generation}:chunk:1",
"ref": "ref:foreign",
"title": "foreign",
"content": "foreign",
}
fake.points[foreign["id"]] = foreign
with pytest.raises(VectorStoreError, match="workspace namespace does not match"):
store.search(
["evidence"], [0.2] * 1024, limit=5, kinds=["evidence"],
metadata_filter={
"workspace_id": "other",
"vector_generation": generation,
"document_ids": ["doc:shared"],
},
)
def test_generation_inventory_rejects_a_workspace_id_different_from_the_bound_adapter():
fake = FakeQdrantHttp()
store = _store(fake)
with pytest.raises(VectorStoreError, match="workspace namespace does not match"):
store.list_evidence_generations("evidence", "other")
assert not any(call[1].endswith("/points/scroll") for call in fake.calls)
def test_generation_delete_cannot_mutate_foreign_workspace_or_non_evidence_points():
fake = FakeQdrantHttp()
store = _store(fake)
generation = "gen:" + "1" * 32
store.upsert("evidence", [
_write_record(
f"demo:{generation}:chunk:1",
"evidence",
metadata={
"workspace_id": "demo", "vector_generation": generation,
"document_id": "doc:demo",
},
),
])
demo = next(iter(fake.points.values()))
foreign = demo.copy()
foreign["id"] = point_id("other", "evidence", f"other:{generation}:chunk:1")
foreign["payload"] = {
**demo["payload"], "workspace_id": "other",
"record_key": f"other:{generation}:chunk:1",
}
fake.points[foreign["id"]] = foreign
memory = demo.copy()
memory["id"] = point_id("other", "memory", "memory:foreign")
memory["payload"] = {
**demo["payload"], "workspace_id": "other", "kind": "memory",
"record_kind": "memory", "record_key": "memory:foreign",
}
fake.points[memory["id"]] = memory
before = set(fake.points)
with pytest.raises(VectorStoreError, match="workspace namespace does not match"):
store.delete_generation("evidence", generation, "other")
assert set(fake.points) == before
assert not any(call[1].endswith("/points/delete?wait=true") for call in fake.calls)
def test_delete_kinds_is_workspace_scoped_and_preserves_other_semantic_kinds():
fake = FakeQdrantHttp()
store = _store(fake)
+25 -30
View File
@@ -94,14 +94,11 @@ class QdrantVectorStore:
expected_dimension=self._expected_dimension,
)
dimensions = ()
compatible = None
if info is not None:
dimension = info["config"]["params"]["vectors"]["size"]
dimensions = (dimension,)
compatible = (
None if self._expected_dimension is None else dimensions == (self._expected_dimension,)
)
dimension = info["config"]["params"]["vectors"]["size"]
dimensions = (dimension,)
compatible = (
None if self._expected_dimension is None else dimensions == (self._expected_dimension,)
)
return VectorHealth(
ok=compatible is not False,
read_configured=True,
@@ -142,12 +139,11 @@ class QdrantVectorStore:
or not isinstance(workspace_id, str)
):
raise VectorStoreError("Invalid vector metadata filter")
filter_must = [
{"key": "workspace_id", "match": {"value": workspace_id}},
{"key": "record_kind", "match": {"any": allowed_record_kinds}},
self._require_bound_workspace(workspace_id)
filter_must.extend([
{"key": "vector_generation", "match": {"value": generation}},
{"key": "document_id", "match": {"any": document_ids}},
]
])
response = self._call(
"POST",
f"/collections/{self._collection}/points/query",
@@ -232,27 +228,19 @@ class QdrantVectorStore:
raise VectorStoreError("Only exact Evidence generations may be deleted")
if _WORKSPACE.fullmatch(workspace_id) is None:
raise VectorStoreError("Invalid Evidence workspace namespace")
self._require_bound_workspace(workspace_id)
must = [
*self._workspace_filter(),
{"key": "record_kind", "match": {"any": ["evidence"]}},
{"key": "vector_generation", "match": {"value": generation}},
]
before = len(
self._scroll(
[
{"key": "workspace_id", "match": {"value": workspace_id}},
{"key": "record_kind", "match": {"any": ["evidence"]}},
{"key": "vector_generation", "match": {"value": generation}},
]
)
self._scroll(must)
)
self._call(
"POST",
f"/collections/{self._collection}/points/delete?wait=true",
{
"filter": {
"must": [
{"key": "workspace_id", "match": {"value": workspace_id}},
{"key": "record_kind", "match": {"any": ["evidence"]}},
{"key": "vector_generation", "match": {"value": generation}},
]
}
},
{"filter": {"must": must}},
)
return before
@@ -261,9 +249,10 @@ class QdrantVectorStore:
raise VectorStoreError("Only exact Evidence generations may be listed")
if _WORKSPACE.fullmatch(workspace_id) is None:
raise VectorStoreError("Invalid Evidence workspace namespace")
self._require_bound_workspace(workspace_id)
points = self._scroll(
[
{"key": "workspace_id", "match": {"value": workspace_id}},
*self._workspace_filter(),
{"key": "record_kind", "match": {"any": ["evidence"]}},
]
)
@@ -279,6 +268,10 @@ class QdrantVectorStore:
def _workspace_filter(self) -> list[dict]:
return [{"key": "workspace_id", "match": {"value": self._workspace_id}}]
def _require_bound_workspace(self, workspace_id: str) -> None:
if workspace_id != self._workspace_id:
raise VectorStoreError("Evidence workspace namespace does not match bound workspace")
def _allowed_record_kinds(
self, collections: list[str], kinds: list[str] | None
) -> list[str]:
@@ -303,7 +296,7 @@ class QdrantVectorStore:
response = self._call("GET", f"/collections/{self._collection}", None, allow_missing=True)
if response is None:
if not strict:
return None
raise VectorStoreError("Qdrant collection is missing")
self._call(
"PUT",
f"/collections/{self._collection}",
@@ -329,6 +322,8 @@ class QdrantVectorStore:
raise VectorStoreError("Qdrant collection configuration mismatch")
for field_name in _KEYWORD_INDEXES:
if field_name not in result.get("payload_schema", {}):
if not strict:
raise VectorStoreError("Qdrant collection payload indexes mismatch")
self._call(
"PUT",
f"/collections/{self._collection}/index",