Files
mempalace/tests/test_backend_conformance.py
T
Igor Lins e Silva ec5d1eb1c7 feat: add pgvector backend + namespace-isolation conformance contract
Adds a second external storage backend (Postgres/pgvector) alongside Qdrant
to prove the BaseBackend/BaseCollection contract generalizes across substrates
(SQL + JSONB containment filters + pgvector `<=>` ranking vs Qdrant's REST/dict
model), and addresses the review feedback on PR #1679.

Backend (mempalace/backends/pgvector.py):
- table-per-(namespace, palace, collection) isolation; advertises
  supports_namespace_isolation
- JSONB filter pushdown for the containment subset, local-exact fallback for
  $or/$contains/comparisons/where_document
- BM25 lexical search; marker-based mismatch protection
- optional psycopg dependency (lazy import), in-memory fake for CI, live test
  gated on MEMPALACE_PGVECTOR_LIVE_URL
- registered in registry/__init__/pyproject entry point + [pgvector] extra;
  MEMPALACE_PGVECTOR_DSN / MEMPALACE_PGVECTOR_NAMESPACE config; README docs

Isolation contract (RFC 001):
- PalaceRef/BaseBackend document the per-id MUST and the cross-namespace MUST,
  gated on the new supports_namespace_isolation capability token
- runnable conformance suite (tests/_backend_conformance.py,
  tests/test_backend_conformance.py); qdrant + pgvector run it via their fakes

Marker fail-loud guard:
- qdrant and pgvector now refuse get_collection when local_path is None instead
  of silently opening a remote collection with no mismatch protection

Review fixes:
- palace._open_collection_or_explain handles unknown-backend KeyError as a CLI
  state message instead of an escaping stack trace
- dedup.py docstring no longer claims "No API calls" unconditionally (false for
  remote backends)
2026-06-05 21:41:21 -03:00

44 lines
1.6 KiB
Python

"""Backend isolation conformance suite (RFC 001 isolation contract).
Runs the shared isolation assertions from ``_backend_conformance`` against the
built-in local backends. Server-mode backends (qdrant) run the same assertions
under their own fake/live client in ``test_qdrant_backend.py``.
"""
import pytest
from _backend_conformance import assert_partition_isolation
from mempalace.backends import PalaceRef
from mempalace.backends.chroma import ChromaBackend
from mempalace.backends.sqlite_exact import SQLiteExactBackend
_LOCAL_BACKENDS = [
pytest.param(ChromaBackend, id="chroma"),
pytest.param(SQLiteExactBackend, id="sqlite_exact"),
]
@pytest.mark.parametrize("backend_cls", _LOCAL_BACKENDS)
def test_cross_palace_isolation(backend_cls, tmp_path):
"""One backend instance must isolate two distinct palaces (PalaceRef.id)."""
backend = backend_cls()
try:
cols = []
for label in ("alpha", "beta"):
path = tmp_path / label
ref = PalaceRef(id=str(path), local_path=str(path))
cols.append(
backend.get_collection(palace=ref, collection_name="mempalace_drawers", create=True)
)
assert_partition_isolation(backend, cols[0], cols[1])
finally:
backend.close()
def test_local_backends_do_not_claim_namespace_isolation():
"""Local backends isolate by on-disk path, not namespace; they must not
advertise the namespace-isolation capability (RFC 001 isolation contract)."""
assert "supports_namespace_isolation" not in ChromaBackend.capabilities
assert "supports_namespace_isolation" not in SQLiteExactBackend.capabilities