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:
Chris Coutinho
2026-06-16 00:38:35 +02:00
co-authored by Claude Opus 4.8
parent 07ee91399b
commit ef5b3f3873
11 changed files with 770 additions and 6 deletions
+10
View File
@@ -47,6 +47,7 @@ from nextcloud_mcp_server.api import (
list_supported_scopes,
list_webhooks,
provision_app_password,
purge_doc_types_route,
revoke_user_access,
unified_search,
update_user_scopes,
@@ -2422,6 +2423,15 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None =
routes.append(
Route("/api/v1/webhooks/{webhook_id}", delete_webhook, methods=["DELETE"])
)
# Vector-sync admin: purge indexed vectors by doc type (admin consent —
# called by Astrolabe when a source is disabled for semantic search)
routes.append(
Route(
"/api/v1/vector-sync/purge",
purge_doc_types_route,
methods=["POST"],
)
)
# Access and scope management endpoints (ADR-022)
routes.append(
Route(