Adds SearchEngine::search_contacts and search_contacts_page (keyset-paginated) in crates/nuncio-store/src/search.rs, querying the existing contacts_fts FTS5 table, mirroring the search_messages/search_messages_page and search_events patterns exactly (same sanitizer, same snippet()/rank usage, same SearchHit { id, title, snippet } shape).
Verified contacts_fts was already populated correctly by the contacts_ai/contacts_au/contacts_ad triggers in crates/nuncio-store/src/db.rs (created in an earlier story but unused until now) -- no trigger changes were needed; save_contact's INSERT OR REPLACE correctly fires the delete-then-insert triggers so the FTS row is replaced, not duplicated or left stale (already covered by the pre-existing save_contact_upserts_existing_row_and_fts_entry test).
Engine-only change, per WS-E10 scope -- no proto/gRPC change (that's WS-E11).
Tests
New tests in crates/nuncio-store/src/search.rs:
fts5_contact_search_ranks_matches_and_excludes_others -- inserts several contacts, confirms matches are ranked/returned and non-matches excluded.
fts5_contact_search_reflects_update_and_delete -- proves contacts_au/contacts_ad keep the FTS index in sync after an update and a delete.
fts5_contact_search_matches_email_and_survives_edge_query -- matches against the JSON-encoded email column and confirms an FTS5-operator-laden query does not error.
search_contacts_page_returns_every_hit_once_no_gaps -- keyset pagination returns every matching contact exactly once across pages, mirroring the existing message-search pagination test.
## Summary
- Adds `SearchEngine::search_contacts` and `search_contacts_page` (keyset-paginated) in `crates/nuncio-store/src/search.rs`, querying the existing `contacts_fts` FTS5 table, mirroring the `search_messages`/`search_messages_page` and `search_events` patterns exactly (same sanitizer, same `snippet()`/`rank` usage, same `SearchHit { id, title, snippet }` shape).
- Verified `contacts_fts` was already populated correctly by the `contacts_ai`/`contacts_au`/`contacts_ad` triggers in `crates/nuncio-store/src/db.rs` (created in an earlier story but unused until now) -- no trigger changes were needed; `save_contact`'s `INSERT OR REPLACE` correctly fires the delete-then-insert triggers so the FTS row is replaced, not duplicated or left stale (already covered by the pre-existing `save_contact_upserts_existing_row_and_fts_entry` test).
- Engine-only change, per WS-E10 scope -- no proto/gRPC change (that's WS-E11).
## Tests
New tests in `crates/nuncio-store/src/search.rs`:
- `fts5_contact_search_ranks_matches_and_excludes_others` -- inserts several contacts, confirms matches are ranked/returned and non-matches excluded.
- `fts5_contact_search_reflects_update_and_delete` -- proves `contacts_au`/`contacts_ad` keep the FTS index in sync after an update and a delete.
- `fts5_contact_search_matches_email_and_survives_edge_query` -- matches against the JSON-encoded email column and confirms an FTS5-operator-laden query does not error.
- `search_contacts_page_returns_every_hit_once_no_gaps` -- keyset pagination returns every matching contact exactly once across pages, mirroring the existing message-search pagination test.
## Gate output
```
cargo fmt --all -- --check # clean
cargo check-all # clean, 0 warnings
cargo test-all # all green, 0 ignored
cargo test -p nuncio-store # 80 passed; 0 failed; 0 ignored
```
Refs #318
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
SearchEngine::search_contactsandsearch_contacts_page(keyset-paginated) incrates/nuncio-store/src/search.rs, querying the existingcontacts_ftsFTS5 table, mirroring thesearch_messages/search_messages_pageandsearch_eventspatterns exactly (same sanitizer, samesnippet()/rankusage, sameSearchHit { id, title, snippet }shape).contacts_ftswas already populated correctly by thecontacts_ai/contacts_au/contacts_adtriggers incrates/nuncio-store/src/db.rs(created in an earlier story but unused until now) -- no trigger changes were needed;save_contact'sINSERT OR REPLACEcorrectly fires the delete-then-insert triggers so the FTS row is replaced, not duplicated or left stale (already covered by the pre-existingsave_contact_upserts_existing_row_and_fts_entrytest).Tests
New tests in
crates/nuncio-store/src/search.rs:fts5_contact_search_ranks_matches_and_excludes_others-- inserts several contacts, confirms matches are ranked/returned and non-matches excluded.fts5_contact_search_reflects_update_and_delete-- provescontacts_au/contacts_adkeep the FTS index in sync after an update and a delete.fts5_contact_search_matches_email_and_survives_edge_query-- matches against the JSON-encoded email column and confirms an FTS5-operator-laden query does not error.search_contacts_page_returns_every_hit_once_no_gaps-- keyset pagination returns every matching contact exactly once across pages, mirroring the existing message-search pagination test.Gate output
Refs #318