Build a searchable case queue for two teams and at least 62 seeded cases. The source database remains authoritative. An index worker receives case changes with versions, so a retry of an older event must not roll a record back. The search endpoint accepts a bounded query, region, and status. Exact case IDs lead their own query class, equal scores receive an immutable tie-breaker, and cursors belong to one normalized query and filter set. The current user may see only authorized cases. Check titles, snippets, counts, suggestions, and facets, not merely the list cards. The deliverable is a working route plus an evidence packet for freshness, stable ordering, revocation, and query cost.
Project: private case search
Build contract
- Store a durable change record with each case write; index only events newer than the stored case version and measure visible lag.
- Normalize and bound search input, define a ranking policy, and paginate with an opaque cursor bound to filters and sort.
- Apply current record permissions before page selection, counts, snippets, suggestions, and facets; recheck detail and write routes.
Implementation checkpoint
function mayApplyProjection(indexedVersion, eventVersion) {
return Number.isSafeInteger(eventVersion) && eventVersion > indexedVersion;
}
console.log(mayApplyProjection(62, 61));
// Output: falseCost and boundaries
Indexing adds write work and storage. A rebuild reads O(n) source records plus changes that arrive during the scan. Search ranking and authorization can inspect more candidates than the page size, especially when a team can see only a small fraction of matches. Record per-query candidate counts, response latency, and index lag. A short permission cache may reduce repeated checks but requires a revocation path. Do not lower visibility standards merely to make a benchmark pass.
Failure drill
Deliver case 47 updates out of order. Search must keep the latest version. Remove team East access while its index projection stays stale; East must not see the case in any result-derived field. Change North/open to South/open while a page request is pending, and confirm that the old response or cursor cannot contaminate the new result set. Seed tied scores at the page boundary and inspect duplicates or omissions. Finally rebuild the index under writes and compare versions before switching the read alias.
Acceptance checks
- Older index events cannot overwrite newer projections and a rebuild catches up before cutover.
- Exact ID and title tasks return expected leading results with stable tie order.
- Query and filter changes reject old cursors; page boundaries contain no duplicates or gaps.
- Permission revocation hides cards, snippets, totals, facets, and suggestions even with stale indexed metadata.
Common Mistakes
- Treating index contents as current permission truth.
- Filtering private rows after limiting a result page.
- Using a cursor with no query or filter binding.
Related lessons
Search Index Freshness and Rebuilds; Search Query Normalization and Ranking; Search Filters and Result Cursors; Search Permissions and Private Results; Authorization: check permission for this record on every request.
