The REST layer asked the search service for results and then removed the ones the
caller may not read. Both search services cut their results down to twenty before
returning them, so the removal ran on an already shortened list and a caller with
access to few notes was served fewer results than it is allowed to see, down to
none at all. In EmbeddingSearch the notes the caller cannot read also fed the
table boost, so they moved the ranking of the results that were kept.
The read check now travels with the query as a predicate over the note id, and
every implementation applies it before it cuts anything: EmbeddingSearch drops
the entries while it scores them, so neither the table weights nor the cutoff see
them, and LuceneSearch walks the hits in score order until it has collected a
full page of readable ones. LuceneSearch reads only the id field to decide on a
hit and loads the rest of the document for the hits that are kept.
query(String) is gone, so there is no longer a way to search without saying who
is asking.
What is this PR for?
/notebook/search asked the search service for results and then dropped the ones the caller may not read. Both search services cut their results down to twenty before returning them, so that removal ran on an already shortened list:
The read check now travels with the query, as a predicate over the note id, and every implementation applies it before it cuts anything.
LuceneSearch reads only the id field to decide on a hit and loads the whole document for the hits it keeps, so hits that are dropped cost one stored field read and no highlighting.
query(String) is removed rather than kept next to the new method: leaving it would leave a way to search without saying who is asking, which is the defect this issue is about. SearchService is bound in ZeppelinServer to the three implementations in the repository and is not reachable as an extension point, so nothing outside the tree implements it.
Note that this changes what the result limit means: it is now the top twenty results the caller may read, rather than what is left of the top twenty overall after filtering.
What type of PR is it?
Bug Fix
What is the Jira issue?
How should this be tested?
New tests in both search services, keepsReadableResultsThatTheCutWouldHide, put twenty-eight matching notes in the index and let the caller read three of them. Each test first checks its own fixture: it queries with an allow-all predicate and asserts that the readable notes really do fall outside the cut, so that the test cannot quietly stop testing anything if the limit or the scoring changes later. It then queries with the real predicate and expects all three readable notes back and nothing else.
LuceneSearchTest.returnsNothingWhenTheCallerMayReadNothing covers the walk ending on its own when no hit is readable.
Run locally:
The existing call sites in both test classes now pass id -> true explicitly.
Questions:
Possible follow-up
Indexing the note id as its own field would let LuceneSearch hand the permission filter to Lucene instead of walking the hits, but it needs an index schema change and a rebuild, so it is left out of this issue.