A production search request is almost never purely about meaning.
Find me similar incidents usually means from this customer, in the last quarter, that I am allowed to see.
Those conditions live in metadata stored alongside each vector, and they have to be honoured.
There are three places the filter can be applied, and the difference is not academic.
Post-filtering runs the vector search first and then discards the results that fail the condition.
It is trivial to implement and behaves badly the moment the filter is selective.
If one document in a thousand matches, a hundred nearest neighbours will very often contain none.
The query returns an empty list and the user concludes the search is broken.
Pre-filtering finds the matching set first and searches only within it.
That is exact, but an approximate index such as HNSW cannot be traversed over an arbitrary subset.
Falling back to comparing every candidate is correct and can be far slower than the index it replaced.
Modern engines filter during traversal, checking the condition as the graph is walked.
This keeps most of the speed while ensuring enough matching neighbours are actually reached.
Design metadata deliberately: low-cardinality fields filter well, free text does not.
Access control belongs in this layer, since a document that was never retrieved cannot leak into a prompt.