From c3758a0bdf1d1c539bd5d1a6e6c5394fcaa420e1 Mon Sep 17 00:00:00 2001 From: Chris Coutinho Date: Thu, 4 Jun 2026 13:02:43 +0200 Subject: [PATCH] fix: only merge prior acl_principals for files (review #848) existing_principals() ran for every doc type when seeding acl_principals. note/news_item/deck_card IDs are per-user (not globally unique) and chunk point IDs are user-agnostic, so on an ID collision the merge would pull in another user's principal and cross-surface their content via the acl_principals search branch. It was also N wasted tenant-wide scrolls on initial sync for those types. Gate the prior-principal merge on doc_type == "file" (the only type with cross-user dedup + globally-unique fileid); other types seed with the indexer only. Co-Authored-By: Claude Opus 4.8 (1M context) --- nextcloud_mcp_server/vector/processor.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/nextcloud_mcp_server/vector/processor.py b/nextcloud_mcp_server/vector/processor.py index d0eea99e..a25d919f 100644 --- a/nextcloud_mcp_server/vector/processor.py +++ b/nextcloud_mcp_server/vector/processor.py @@ -781,12 +781,21 @@ async def _index_document( _acl_hash = compute_acl_hash([("user", doc_task.user_id)]) # Observed-access ACL principals (computed once per document, not per chunk). - # Seed with the indexer (and owner, if distinct) unioned with any principals - # already recorded — so re-indexing after a content change preserves - # visibility for readers who had previously claimed the file rather than - # resetting it to just the indexer. + # Seed with the indexer (and owner, if distinct). For files — the only type + # with cross-user dedup and globally-unique IDs (Nextcloud fileid) — union in + # any principals already recorded so re-indexing after a content change + # preserves visibility for readers who had previously claimed the file. For + # note/news_item/deck_card, IDs are per-user (not globally unique) and point + # IDs are user-agnostic, so merging another user's principals on an ID + # collision would wrongly cross-surface their content; those types are + # seeded with the indexer only. + _prior_principals = ( + await existing_principals(doc_task.doc_id, doc_task.doc_type) + if doc_task.doc_type == "file" + else [] + ) _acl_principals = sorted( - set(await existing_principals(doc_task.doc_id, doc_task.doc_type)) + set(_prior_principals) | { f"user:{doc_task.user_id}", f"user:{doc_task.owner_id or doc_task.user_id}",