@@ -1188,12 +1188,16 @@ const SATURATED_REACH_TTL_MS = 5 * 60 * 1000
11881188 * A counted reach: whether it is broad enough to walk the whole graph for, or empty, in which
11891189 * case the caller reads nothing in these bases and no leg has anything to rank.
11901190 */
1191- interface RememberedReach {
1191+ interface CountedReach {
11921192 broad : boolean
11931193 empty : boolean
11941194}
11951195
1196- const saturatedReach = new LRUCache < string , RememberedReach > ( {
1196+ /**
1197+ * Only breadth is remembered. Emptiness decides completeness, not strategy, so it is counted on
1198+ * every search: the count of a reach of nothing finds nothing and costs almost nothing.
1199+ */
1200+ const saturatedReach = new LRUCache < string , { broad : boolean } > ( {
11971201 max : 10_000 ,
11981202 ttl : SATURATED_REACH_TTL_MS ,
11991203} )
@@ -1258,10 +1262,13 @@ async function estimateFilteredDocuments(
12581262
12591263/**
12601264 * How far a caller reaches: broad when they reach at least {@link BROAD_REACH_SHARE} of the
1261- * bases' documents, empty when they reach none. Counted once against that bound and remembered,
1262- * so the first search after the window pays for it and the rest do not. A caller whose probe
1263- * already saturated is known to reach past the probe's limit, so a bound inside that limit is
1264- * met without counting.
1265+ * bases' documents, empty when they reach none. A reach of nothing is a bounded set of nothing: a
1266+ * caller who reads no document in these bases, such as a member with no source of their own yet,
1267+ * has nothing for any leg to rank, where an unbounded set would have each leg scan to its
1268+ * deadline for rows it cannot find. Breadth is counted once against the bound and remembered, so
1269+ * the first search after the window pays for it and the rest do not. A caller whose probe already
1270+ * saturated is known to reach past the probe's limit, so a bound inside that limit is met without
1271+ * counting.
12651272 *
12661273 * The count reads as many index entries as the caller reaches, so on a large index it can cost
12671274 * more than the leg it serves; it gets the probe's share of the deadline, never the whole leg's.
@@ -1274,7 +1281,7 @@ async function countReach(
12741281 budget : SearchBudget | undefined ,
12751282 plan : SearchAccessPlan | undefined ,
12761283 saturated : boolean
1277- ) : Promise < RememberedReach | null > {
1284+ ) : Promise < CountedReach | null > {
12781285 if ( access . kind !== 'user' ) return { broad : true , empty : false }
12791286 const countBudget = budget ?. capped ( VECTOR_PROBE_BUDGET_MS )
12801287 try {
@@ -1354,31 +1361,21 @@ export async function resolveReach(
13541361) : Promise < PermittedDocuments > {
13551362 const key = reachKey ( knowledgeBaseIds , access , plan )
13561363 const remembered = key ? saturatedReach . get ( key ) : undefined
1357- if ( remembered ) return permittedFromReach ( remembered )
1364+ if ( remembered ) return { kind : 'unbounded' , broad : remembered . broad }
13581365 try {
13591366 const reach = await countReach ( knowledgeBaseIds , access , budget , plan , false )
13601367 /** A count that ran out of time decides this search only; the next one counts again. */
13611368 if ( reach === null ) return { kind : 'unbounded' , broad : true }
1362- if ( key ) saturatedReach . set ( key , reach )
1363- return permittedFromReach ( reach )
1369+ if ( reach . empty ) return { kind : 'bounded' , documents : [ ] }
1370+ if ( key ) saturatedReach . set ( key , { broad : reach . broad } )
1371+ return { kind : 'unbounded' , broad : reach . broad }
13641372 } catch ( error ) {
13651373 /** The leg's own deadline passed during the count: the leg is short, the search is not failed. */
13661374 if ( ! budget ?. isTimeout ( error ) ) throw error
13671375 return { kind : 'unbounded' , broad : true }
13681376 }
13691377}
13701378
1371- /**
1372- * A reach of nothing is a bounded set of nothing: a caller who reads no document in these bases,
1373- * such as a member with no source of their own yet, has nothing for any leg to rank, where an
1374- * unbounded set would have each leg scan for rows it cannot find.
1375- */
1376- function permittedFromReach ( reach : RememberedReach ) : PermittedDocuments {
1377- return reach . empty
1378- ? { kind : 'bounded' , documents : [ ] }
1379- : { kind : 'unbounded' , broad : reach . broad }
1380- }
1381-
13821379/**
13831380 * Resolve the permitted set with the candidate predicate both legs apply, so restricting a leg
13841381 * to it never admits a document the leg would otherwise refuse. Tag filters stay chunk-level in
@@ -1406,10 +1403,6 @@ export async function resolvePermittedDocuments(params: {
14061403 params . accessPlan && ( dateFilterCondition ( params . filters ) || params . filters ?. source )
14071404 )
14081405 const remembered = key && ! filteredDirectly ? saturatedReach . get ( key ) : undefined
1409- if ( remembered ?. empty ) {
1410- annotateSearchDiagnostics ( { permittedDocuments : 'bounded' , permittedDocumentCount : 0 } )
1411- return { kind : 'bounded' , documents : [ ] }
1412- }
14131406 if ( remembered ) {
14141407 probe = { kind : 'saturated' }
14151408 broad = remembered . broad
@@ -1442,9 +1435,10 @@ export async function resolvePermittedDocuments(params: {
14421435 true
14431436 )
14441437 /** A count that ran out of time decides this search only; the next one counts again. */
1445- if ( reach !== null ) {
1438+ if ( reach ?. empty ) probe = { kind : 'documents' , documents : [ ] }
1439+ else if ( reach !== null ) {
14461440 broad = reach . broad
1447- if ( key ) saturatedReach . set ( key , reach )
1441+ if ( key ) saturatedReach . set ( key , { broad } )
14481442 }
14491443 } catch ( error ) {
14501444 /** The leg's own deadline passed during the count: the leg is short, the search is not failed. */
0 commit comments