Why:
- ADR-0002's keyword search needs a full-text index on content, which
collection.py deliberately deferred to plan 002. is_active and chunk_index
were unindexed while ingestion was the only reader; every /v1/points read path
filters on them.
Changes:
- content gets a TEXT index with the multilingual tokenizer, which segments
Persian correctly where the word tokenizer mishandles ZWNJ-joined compounds.
No stemmer or stopword list: content is already letter-folded by
normalize_persian_text at ingest, and the ranked Farsi lexical path is the
benchmarked BM25 sparse vector, not this index.
- Tests assert content is TEXT rather than KEYWORD -- a keyword index would only
match an entire chunk verbatim, which never happens and fails silently.
- Adds a test that a missing index is added to an already-live collection.
Impact:
- Requires re-running `python -m src.cli.qdrant_bootstrap`. Payload indexes are
additive, so no collection rebuild and no re-embedding.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Why:
- The chunks collection needs four named vectors (dense_nomic, dense_openai,
sparse, late_interaction) and payload indexes defined at creation time per
ADR-0001; sparse/multivector fields cannot be added to an existing
collection without recreating it, so schema drift here is expensive.
- Creating it at FastAPI startup would mirror the DDL-at-boot anti-pattern
ADR-0009 already rejects for Postgres and ADR-0012 rejects for LangGraph's
setup(), so it is a deployment step instead.
Changes:
- src/infrastructure/qdrant/collection.py: ensure_chunks_collection(),
idempotent and schema-verifying (raises on dimension/modifier mismatch
rather than silently accepting a misconfigured collection).
- src/cli/qdrant_bootstrap.py: the operator entry point
(python -m src.cli.qdrant_bootstrap).
- QdrantSettings gains collection/upsert_batch_size/upsert_concurrency.
Impact:
- Deployments must run the new bootstrap command before the first upload;
see ADR-0001's new "Collection provisioning" section.