Keeping Indexes Fresh and Permission-Aware
Handle updates, deletes, and re-embedding — and make sure users can only retrieve documents they're allowed to see.
Two bugs end RAG features, and neither shows up in a demo. The first is staleness: the billing article changed last Tuesday, but the index still holds the old chunks, so your assistant confidently quotes a price you no longer charge, with a citation that makes it look authoritative. The second is a leak: an internal "known issues" article meant for support agents gets retrieved for a customer's question, and the assistant summarizes it for them.
Both happen because an index is a copy of your data, and copies drift. In this lesson you'll keep Deskmate's knowledge-base index in sync through updates, deletes, and embedding model changes, and you'll make retrieval permission-aware so each user can only retrieve what they're allowed to read, enforced in the database rather than hoped for in a prompt.
An index is a cache, so treat it like one
Every vector index is derived data: chunks and embeddings computed from source documents at some point in the past. That makes it a cache, with all of a cache's problems. It can be stale (the source changed), it can hold entries that should be gone (the source was deleted or unpublished), and it can be inconsistent (half the chunks of an article are new, half are old).
Stale RAG is worse than a stale search page. A search results page shows a title and a date, and users know to click through. A RAG answer restates old content in fresh, confident prose, often with a citation. Users have no way to tell that the cited article has since changed.
So design the sync deliberately. You need answers to four questions:
| Question | Deskmate's answer |
|---|---|
| What's the source of truth? | The kb_articles table; the index is always rebuilt from it |
| How fast must changes appear? | Edits within a minute; unpublishes and deletes immediately |
| What triggers a re-index? | An event on every article save, plus a periodic reconciliation sweep |
| How do we know it's in sync? | Each article records the hash of the text its current chunks came from |
Embeddings in Practice introduced storing a content hash and the model name with every vector so re-runs skip unchanged text. Here you'll extend that from whole articles to chunks, where the hard part is keeping a set of rows consistent with one source document.