The permission filter runs inside the vector search
The usual shape of RAG is: search the index, then filter the results. That fails twice. It leaks — rows you can't see still decided which five chunks came back. And it starves — if the closest matches are all out of scope, you get an empty answer even though relevant, visible data exists.
So the scope comes first: the project, task and note ids this user may read. It's applied in the SQL WHERE clause, before ORDER BY embedding <=> query, so the nearest neighbours are chosen among permitted rows only. The cost is one scope query per question; the benefit is that there is no code path where the model sees a row the user couldn't open.