docir — design documents Graph

Documents / Issues / issue-e19a2fde1805

`search` fetches `limit * 2` candidates then filters, so it under-returns on a closed corpus

Under-return looks like corpus scarcity rather than filtering.

issue-e19a2fde1805issueresolved#cosmetic#retrieval
View as Markdown◉ View in graph

Class: unstated · Severity: cosmetic Flow: arch-f220a644d654 · Step: search with many inactive documents in the head of the ranking Question: None · Frequency: corpora with a high proportion of closed documents

Finding

search fetches limit * 2 candidates then filters; if more than half are inactive it returns fewer than limit with no indication why.

What happens today

document_service.py:236-247.

Impact

Under-return looks like corpus scarcity rather than filtering. The 2× factor is an unexplained constant.

Proposed default

Filter in SQL, or loop until the limit is met or candidates are exhausted.

Resolution

FIXED 2026-07-29 with the second option: the candidate pool doubles until the limit is met or the index is exhausted, so a short result means the corpus is short. Filtering in SQL was the alternative and was not taken — FTS5 does not know a document's status, so it would mean teaching the search adapter about documents, for a case a retry loop covers at no structural cost. The unexplained 2 is now _SEARCH_OVERFETCH, documented as a starting point rather than a bound. THE FIRST TEST DID NOT DISCRIMINATE, and only injecting the old behaviour revealed it: resolving a document re-indexes it, which moves its FTS row to the back, so equally-weighted closed documents drift behind the open ones and a fixed pool finds the open ones anyway. The fixture now gives the closed documents repeated query terms so BM25 ranks them first — the shape the finding actually describes. Recorded because the same trap will catch the next person writing a search test.

Actors affected

  • AI coding agent

Evidence

  • src/docir/modules/documents/application/services/document_service.py:236-247

Migrated from the discovery gap register (GAP-018); the register itself now lives in this store.

To amend: Re-verify: