@@ -18,7 +18,7 @@ const snippetMaxRunes = 200
1818// Hit is one unit-level semantic search result, anchored to a specific
1919// message. For a run document Ordinal is the anchor: the member message
2020// whose rune span contains the matched chunk's center rune (see
21- // anchorOrdinal ), while OrdinalStart/OrdinalEnd span the whole run. For a
21+ // anchorMemberIndex ), while OrdinalStart/OrdinalEnd span the whole run. For a
2222// user document all three ordinals are the message's own ordinal.
2323type Hit struct {
2424 SessionID string
@@ -231,7 +231,7 @@ const runMemberSeparatorRunes = 2
231231
232232// resolveRunHit computes a run hit's anchor ordinal and anchor-local
233233// snippet: the anchor is the member whose rune span contains the matched
234- // chunk's center rune (see anchorOrdinal ), and the snippet is the
234+ // chunk's center rune (see anchorMemberIndex ), and the snippet is the
235235// intersection of the chunk's rune window with that member's span — always
236236// a substring of the anchor message's own text, so the db layer's snippet
237237// centering (semanticSnippet) can locate it inside the anchor message's
@@ -277,17 +277,6 @@ func chunkWindow(contentRunes, chunkIndex int, o kitvec.SplitOptions) (start, en
277277 return start , end
278278}
279279
280- // anchorOrdinal maps a matched chunk back to the run member whose rune span
281- // contains the chunk's center rune (chunk_start + actual_chunk_runes/2).
282- // Separator runes between members belong to no member's span, so a center
283- // falling there resolves to the preceding member: the earlier member wins a
284- // boundary tie. offsets must be non-empty (run documents only); user
285- // documents pass their ordinal through without calling this.
286- func anchorOrdinal (offsets []db.UnitOffset , contentRunes , chunkIndex int , o kitvec.SplitOptions ) int {
287- start , end := chunkWindow (contentRunes , chunkIndex , o )
288- return offsets [anchorMemberIndex (offsets , start , end )].Ordinal
289- }
290-
291280// anchorMemberIndex returns the offsets index of the run member whose rune
292281// span contains the [start, end) chunk window's center rune, with the
293282// earlier member winning when the center falls in the separator between two
@@ -310,15 +299,17 @@ func anchorMemberIndex(offsets []db.UnitOffset, start, end int) int {
310299// keyed by doc_key, in maxSQLVars-sized chunks: a deep semantic overfetch
311300// (large limit * over-fetch factor) can carry thousands of doc keys, well
312301// past what a single IN (...) clause can bind. A key with no matching row is
313- // simply absent from the result.
302+ // simply absent from the result. Rows parked at a negative sentinel ordinal
303+ // by a concurrent Refresh (see evictSlotOccupant) are excluded the same way:
304+ // mid-refresh state must never hydrate into a hit with a negative ordinal.
314305func (ix * Index ) lookupMirrorDocs (ctx context.Context , docKeys []string ) (map [string ]mirrorDoc , error ) {
315306 docs := make (map [string ]mirrorDoc , len (docKeys ))
316307 err := chunkKeys (docKeys , func (chunk []string ) error {
317308 placeholders , args := inPlaceholders (chunk )
318309 rows , err := ix .db .QueryContext (ctx , `
319310SELECT doc_key, session_id, ordinal, ordinal_end, subordinate, offsets, content
320311 FROM vector_messages
321- WHERE doc_key IN ` + placeholders , args ... )
312+ WHERE ordinal >= 0 AND doc_key IN ` + placeholders , args ... )
322313 if err != nil {
323314 return fmt .Errorf ("look up search hit documents: %w" , err )
324315 }
@@ -378,7 +369,10 @@ func truncateRunes(s string, maxRunes int) string {
378369// yields a zero UnitRef. Each ref is a point lookup on the retained unique
379370// (session_id, ordinal) index — greatest unit ordinal <= ref ordinal, then a
380371// containment check against ordinal_end — via one prepared statement, so a
381- // batch of any size never approaches SQLite's bind-variable limit.
372+ // batch of any size never approaches SQLite's bind-variable limit. Rows
373+ // parked at a negative sentinel ordinal by a concurrent Refresh (see
374+ // evictSlotOccupant) are skipped so a ref can never resolve into
375+ // mid-refresh state and surface a negative ordinal range.
382376//
383377// Like Search and StaleActive, it fails closed with ErrMirrorVersionMismatch
384378// — before touching any table — when ix was opened read-only against a
@@ -397,7 +391,7 @@ func (ix *Index) ResolveMessageUnits(
397391 stmt , err := ix .db .PrepareContext (ctx , `
398392SELECT doc_key, ordinal, ordinal_end, subordinate
399393 FROM vector_messages
400- WHERE session_id = ? AND ordinal <= ?
394+ WHERE session_id = ? AND ordinal >= 0 AND ordinal <= ?
401395 ORDER BY ordinal DESC LIMIT 1` )
402396 if err != nil {
403397 return nil , fmt .Errorf ("resolve message units: %w" , err )
0 commit comments