fix: address PR #813 review (owner_id index, cache bound, explicit param)

- vector/qdrant_client.py: add owner_id to _PAYLOAD_INDEX_FIELDS (BLOCKING).
  Every search applies MatchAny(key="owner_id", ...); without a keyword index
  Qdrant full-scans the collection and may 400 on Qdrant Cloud strict mode.
  _ensure_payload_indexes is idempotent so existing collections migrate at
  startup.
- search/access_filter.py: bound the process-global _owners_cache with an LRU
  cap (was one unbounded entry per active user, never evicted); document the
  owner-level over-fetch limitation (a prolific sharer floods the recall
  buffer with ghost candidates that verify-on-read drops, with no second
  Qdrant pass) as a TODO toward per-file filtering.
- search/algorithms.py + semantic.py + bm25_hybrid.py: promote
  accessible_owners from **kwargs to an explicit keyword-only parameter on the
  SearchAlgorithm ABC and both implementations, so a misspelled keyword is a
  type error rather than a silent fall back to self-only scope.
- search/verification.py: document that _verify_files now verifies by global
  file id (WebDAV SEARCH), not by path.
- tests/unit/search/test_access_filter.py: add cache-hit, TTL-expiry,
  failure-not-cached, and LRU-bound tests.

Bumps the astrolabe submodule with the matching #89 review fixes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-29 13:28:45 +02:00
co-authored by Claude Opus 4.8
parent 8f0955cfc9
commit cafbfd15a9
8 changed files with 119 additions and 7 deletions
@@ -132,6 +132,16 @@ async def _verify_files(
results: list[SearchResult],
semaphore: anyio.Semaphore,
) -> set[str]:
"""Return the doc_ids of file results this user may actually access.
Verifies each file by its *global* Nextcloud file id via an ACL-aware
WebDAV SEARCH (``webdav.file_accessible_by_id``), NOT by path. This is the
ACL-aware-search fix: a file an owner shared with the querying user mounts
at a different path under each tree, so the previous path-based check
(``get_file_info``) produced false 404s and dropped legitimate shared-file
hits. Definitive 403/404 → inaccessible (dropped + scheduled for eviction
by the caller); transient/ambiguous errors → kept (fail-open).
"""
# safe: cooperative concurrency, no lock needed (see verify_search_results)
accessible: set[str] = set()