feat(vector-sync): honor Astrolabe admin consent for searchable sources
Consume the astrolabe.semantic_search capability as the source of truth for which content sources an admin has approved for semantic search, and enforce it independently of Astrolabe (this server queries Qdrant directly). - capabilities.py: cached per-user reader for enabled_doc_types (TTL+LRU, fail-open so older Astrolabe / transient OCS errors don't break search) - semantic search: intersect requested doc_types with the allowed set; restrict to the allowed set when none requested; short-circuit when empty - scanner: skip disabled sources during discovery (files discovery yields nothing when disabled, so the existing grace-period reconcile purges them) - processor: drop near-real-time index tasks for disabled doc_types (webhook events bypass the scanner gate); deletes always proceed - vector/purge.py + POST /api/v1/vector-sync/purge: admin-only global delete-by-doc_type, called by Astrolabe when a source is disabled so consent is binding on data-at-rest Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
07ee91399b
commit
ef5b3f3873
@@ -20,6 +20,7 @@ from mcp.types import (
|
||||
from pydantic import Field
|
||||
|
||||
from nextcloud_mcp_server.auth import require_scopes
|
||||
from nextcloud_mcp_server.capabilities import allowed_doc_types
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
from nextcloud_mcp_server.context import get_client
|
||||
from nextcloud_mcp_server.models.semantic import (
|
||||
@@ -300,6 +301,33 @@ def configure_semantic_tools(mcp: FastMCP):
|
||||
# files under their own user_id.
|
||||
accessible_owners = await list_accessible_owners(client.sharing, username)
|
||||
|
||||
# Admin consent gate: restrict to source types the Astrolabe admin has
|
||||
# approved (and that are installed for this user). This mirrors
|
||||
# Astrolabe's own server-side enforcement but is independent because
|
||||
# this tool queries Qdrant directly. ``None`` = no restriction
|
||||
# (fail-open / Astrolabe predating this feature). An empty allow-set
|
||||
# means the admin disabled every source.
|
||||
allowed = await allowed_doc_types(client, username)
|
||||
if allowed is not None:
|
||||
if doc_types is None:
|
||||
doc_types = sorted(allowed)
|
||||
else:
|
||||
doc_types = [dt for dt in doc_types if dt in allowed]
|
||||
if not doc_types:
|
||||
logger.info(
|
||||
"Semantic search short-circuited for user %s: no requested "
|
||||
"doc_type is both installed and admin-approved",
|
||||
username,
|
||||
)
|
||||
return SemanticSearchResponse(
|
||||
results=[],
|
||||
query=query,
|
||||
total_found=0,
|
||||
search_method=f"bm25_hybrid_{fusion}",
|
||||
verified_chunk_count=0,
|
||||
dropped_document_count=0,
|
||||
)
|
||||
|
||||
try:
|
||||
# The nc_semantic_search tool deliberately uses BM25-hybrid (dense +
|
||||
# sparse with RRF/DBSF fusion) as the single tool-layer algorithm.
|
||||
|
||||
Reference in New Issue
Block a user