feat: P3 effective configuration, memory root, and revision-scoped records
This commit is contained in:
@@ -36,7 +36,10 @@ _KEYWORD_INDEXES = (
|
||||
)
|
||||
|
||||
|
||||
def point_id(workspace_id: str, kind: str, record_key: str) -> str:
|
||||
def point_id(workspace_id: str, kind: str, record_key: str, workspace_revision: str | None = None) -> str:
|
||||
# P3: schema/Evidence points are revision-scoped; memory/solved remain workspace-wide.
|
||||
if workspace_revision is not None:
|
||||
return str(uuid5(NAMESPACE_URL, f"thothii:{workspace_id}:{workspace_revision}:{kind}:{record_key}"))
|
||||
return str(uuid5(NAMESPACE_URL, f"thothii:{workspace_id}:{kind}:{record_key}"))
|
||||
|
||||
|
||||
@@ -63,6 +66,7 @@ class QdrantVectorStore:
|
||||
self._base_url = base_url.rstrip("/")
|
||||
self._collection = collection
|
||||
self._workspace_id = workspace_id
|
||||
self._workspace_revision = None
|
||||
self._workspace_revision = workspace_revision
|
||||
self._expected_dimension = expected_dimension
|
||||
self._collection_lifecycle = collection_lifecycle
|
||||
@@ -127,6 +131,7 @@ class QdrantVectorStore:
|
||||
if not allowed_record_kinds:
|
||||
return []
|
||||
filter_must = self._workspace_filter()
|
||||
filter_must.extend(self._revision_filter(allowed_record_kinds))
|
||||
filter_must.append(self._semantic_kind_filter(allowed_record_kinds))
|
||||
filter_must.append({"key": "record_kind", "match": {"any": allowed_record_kinds}})
|
||||
if metadata_filter is not None:
|
||||
@@ -195,7 +200,12 @@ class QdrantVectorStore:
|
||||
semantic_kind = qdrant_semantic_kind(write_record.record.kind)
|
||||
points.append(
|
||||
{
|
||||
"id": point_id(self._workspace_id, semantic_kind, write_record.record.id),
|
||||
"id": point_id(
|
||||
self._workspace_id,
|
||||
semantic_kind,
|
||||
write_record.record.id,
|
||||
self._workspace_revision if semantic_kind in ("schema_table", "schema_column", "evidence") else None,
|
||||
),
|
||||
"vector": write_record.embedding,
|
||||
"payload": qdrant_payload(
|
||||
write_record.record,
|
||||
@@ -275,10 +285,22 @@ class QdrantVectorStore:
|
||||
def _workspace_filter(self) -> list[dict]:
|
||||
return [{"key": "workspace_id", "match": {"value": self._workspace_id}}]
|
||||
|
||||
def _revision_filter(self, kinds: list[str]) -> list[dict]:
|
||||
if self._workspace_revision is None:
|
||||
return []
|
||||
if not any(kind in ("schema_table", "schema_column", "evidence") for kind in kinds):
|
||||
return []
|
||||
return [{"key": "workspace_revision", "match": {"value": self._workspace_revision}}]
|
||||
|
||||
def _semantic_kind_filter(self, record_kinds: list[str]) -> dict:
|
||||
semantic_kinds = sorted({qdrant_semantic_kind(kind) for kind in record_kinds})
|
||||
return {"key": "kind", "match": {"any": semantic_kinds}}
|
||||
|
||||
def bind_workspace_revision(self, workspace_revision: str) -> None:
|
||||
if not re.fullmatch(r"[0-9a-f]{40}", workspace_revision):
|
||||
raise VectorStoreError("workspace revision is invalid")
|
||||
self._workspace_revision = workspace_revision
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user