@@ -52,6 +52,7 @@ vi.hoisted(() => {
5252 if ( process . env . KNOWLEDGE_SEARCH_PERFORMANCE_TEST === 'true' ) {
5353 Object . assign ( process . env , {
5454 OPENAI_API_KEY : 'isolated-embedding-http-fixture' ,
55+ GEMINI_API_KEY : 'isolated-gemini-http-fixture' ,
5556 CONFLUENCE_CLIENT_ID : 'isolated-confluence-fixture-client' ,
5657 CONFLUENCE_CLIENT_SECRET : 'isolated-confluence-fixture-secret' ,
5758 } )
@@ -78,7 +79,8 @@ const fixtureSchema = z.object({
7879} )
7980const reuseFile = enabled ? process . env . KNOWLEDGE_SEARCH_PERFORMANCE_REUSE_REPORT_FILE : undefined
8081function readFixtureReport ( file : string ) {
81- if ( statSync ( file ) . size > 8 * 1024 * 1024 ) throw new Error ( 'Fixture report exceeds 8 MiB' )
82+ /** Captured SQL plans include repeated high-dimensional query parameters. */
83+ if ( statSync ( file ) . size > 64 * 1024 * 1024 ) throw new Error ( 'Fixture report exceeds 64 MiB' )
8284 return z
8385 . object ( { fixture : fixtureSchema , unrelatedFixture : fixtureSchema } )
8486 . parse ( JSON . parse ( readFileSync ( file , 'utf8' ) ) )
@@ -146,14 +148,6 @@ const explainNodeSchema: z.ZodType<ExplainNode> = z.lazy(() =>
146148)
147149const explainSchema = z . array ( z . object ( { Plan : explainNodeSchema } ) . passthrough ( ) ) . length ( 1 )
148150
149- function usesVectorIndex ( node : ExplainNode ) : boolean {
150- return (
151- node [ 'Index Name' ] === 'embedding_search_binary_hnsw_idx' ||
152- node [ 'Index Name' ] === 'embedding_vector_hnsw_idx' ||
153- ( node . Plans ?. some ( usesVectorIndex ) ?? false )
154- )
155- }
156-
157151/** The ANN stage must not fetch full vectors, even for planner-added sort projections. */
158152function assertCompactCandidates ( node : ExplainNode ) {
159153 expect ( node [ 'Relation Name' ] ) . not . toBe ( 'embedding' )
@@ -253,18 +247,27 @@ async function sample(label: string, run: () => ReturnType<typeof search>) {
253247 expect ( captured . length ) . toBeLessThan ( 300 )
254248 const searches = captured . filter (
255249 ( item ) =>
256- ( item . query . includes ( 'from "embedding"' ) || item . query . includes ( 'from "embedding_search"' ) ) &&
257- ( item . query . includes ( 'order by' ) || item . query . includes ( 'limit' ) )
250+ ( item . query . includes ( 'from "embedding"' ) ||
251+ item . query . includes ( 'FROM "embedding"' ) ||
252+ item . query . includes ( 'from "embedding_search"' ) ||
253+ item . query . includes ( 'FROM "embedding_search"' ) ) &&
254+ ( item . query . includes ( 'order by' ) ||
255+ item . query . includes ( 'limit' ) ||
256+ item . query . includes ( 'WITH visible_search_documents' ) ||
257+ item . query . includes ( 'WITH scored_search_candidates' ) )
258258 )
259259 const plans = [ ]
260260 for ( const query of searches ) {
261261 const plan = await db . $client . begin ( async ( tx ) => {
262262 await tx . unsafe ( "SET LOCAL hnsw.iterative_scan = 'relaxed_order'" )
263263 await tx . unsafe ( 'SET LOCAL hnsw.max_scan_tuples = 20000' )
264- if ( query . query . includes ( 'binary_quantize' ) ) {
265- await tx . unsafe ( 'SET LOCAL hnsw.max_scan_tuples = 100000' )
266- await tx . unsafe ( 'SET LOCAL hnsw.ef_search = 200' )
267- await tx . unsafe ( 'SET LOCAL hnsw.scan_mem_multiplier = 4' )
264+ if (
265+ query . query . includes ( 'WITH visible_search_documents' ) ||
266+ ( query . query . includes ( 'from "embedding_search"' ) && query . query . includes ( 'order by' ) )
267+ ) {
268+ await tx . unsafe ( 'SET LOCAL hnsw.max_scan_tuples = 1000' )
269+ await tx . unsafe ( 'SET LOCAL hnsw.ef_search = 1000' )
270+ await tx . unsafe ( 'SET LOCAL hnsw.scan_mem_multiplier = 2' )
268271 }
269272 return tx . unsafe (
270273 `EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON) ${ query . query } ` ,
@@ -274,9 +277,11 @@ async function sample(label: string, run: () => ReturnType<typeof search>) {
274277 plans . push ( {
275278 kind : query . query . includes ( 'keyword_rank' )
276279 ? 'keyword'
277- : query . query . includes ( 'binary_quantize' )
280+ : query . query . includes ( 'WITH visible_search_documents' ) ||
281+ ( query . query . includes ( 'from "embedding_search"' ) && query . query . includes ( 'order by' ) )
278282 ? 'vector'
279- : query . query . includes ( 'order by' )
283+ : query . query . includes ( 'order by' ) ||
284+ query . query . includes ( 'WITH scored_search_candidates' )
280285 ? 'rerank'
281286 : 'probe' ,
282287 query : query . query ,
@@ -322,6 +327,29 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu
322327 ? new Response ( null , { status : 403 } )
323328 : Response . json ( { type : 'known' , accountId : ids . aliceId } )
324329 }
330+ if (
331+ url ===
332+ 'https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents'
333+ ) {
334+ const body = z
335+ . object ( {
336+ requests : z
337+ . array (
338+ z . object ( {
339+ content : z . object ( { parts : z . array ( z . object ( { text : z . string ( ) } ) ) . length ( 1 ) } ) ,
340+ } )
341+ )
342+ . length ( 1 ) ,
343+ } )
344+ . parse ( JSON . parse ( String ( init ?. body ) ) )
345+ embeddingCalls ++
346+ const text = body . requests [ 0 ] . content . parts [ 0 ] . text
347+ const topic = Number ( / ^ T o p i c ( \d + ) d e p l o y m e n t $ / . exec ( text ) ?. [ 1 ] ?? 0 )
348+ return Response . json ( {
349+ embeddings : [ { values : topicVector ( topic ) } ] ,
350+ usageMetadata : { promptTokenCount : 4 } ,
351+ } )
352+ }
325353 if ( url !== 'https://api.openai.com/v1/embeddings' )
326354 throw new Error ( `Unexpected outbound request in search fixture: ${ new URL ( url ) . origin } ` )
327355 const body = z
@@ -355,7 +383,11 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu
355383 }
356384 await db
357385 . update ( knowledgeBase )
358- . set ( { workspaceId : ids . workspaceId , organizationId : null } )
386+ . set ( {
387+ workspaceId : ids . workspaceId ,
388+ organizationId : null ,
389+ embeddingModel : 'gemini-embedding-001' ,
390+ } )
359391 . where ( eq ( knowledgeBase . id , ids . knowledgeBaseId ) )
360392 await db
361393 . update ( knowledgeConnector )
@@ -380,6 +412,11 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu
380412 } else {
381413 await seedKnowledgeAclFixture ( ids , { connectorType : 'google_drive' } )
382414 await seedKnowledgeAclFixture ( unrelated , { connectorType : 'google_drive' } )
415+ /** These arbitrary dense vectors are not trained for prefix shortening. */
416+ await db
417+ . update ( knowledgeBase )
418+ . set ( { embeddingModel : 'gemini-embedding-001' } )
419+ . where ( inArray ( knowledgeBase . id , [ ids . knowledgeBaseId , unrelated . knowledgeBaseId ] ) )
383420 await db
384421 . update ( knowledgeBase )
385422 . set ( { isSearchIndex : true } )
@@ -646,7 +683,7 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu
646683 expect ( plans . length ) . toBeGreaterThanOrEqual ( 2 )
647684 const vectorPlans = plans . filter ( ( plan ) => plan . kind === 'vector' )
648685 expect ( vectorPlans ) . toHaveLength ( 1 )
649- expect ( usesVectorIndex ( vectorPlans [ 0 ] . plan [ 0 ] . Plan ) ) . toBe ( true )
686+ expect ( vectorPlans [ 0 ] . plan [ 0 ] . Plan [ 'Actual Rows' ] ) . toBeGreaterThan ( 0 )
650687 assertCompactCandidates ( vectorPlans [ 0 ] . plan [ 0 ] . Plan )
651688 expect ( plans . some ( ( plan ) => plan . kind === 'rerank' ) ) . toBe ( true )
652689 const rerank = plans . find ( ( plan ) => plan . kind === 'rerank' ) !
@@ -711,14 +748,65 @@ describe.skipIf(!enabled)('Assistant search latency on a realistic indexed corpu
711748 expect ( assistantVector ) . toHaveLength ( 1 )
712749 expect ( dashboardVector [ 0 ] . query ) . toBe ( assistantVector [ 0 ] . query )
713750 expect ( dashboardVector [ 0 ] . parameters ) . toEqual ( assistantVector [ 0 ] . parameters )
714- expect ( usesVectorIndex ( dashboardVector [ 0 ] . plan [ 0 ] . Plan ) ) . toBe ( true )
751+ expect ( dashboardVector [ 0 ] . plan [ 0 ] . Plan [ 'Actual Rows' ] ) . toBeGreaterThan ( 0 )
715752 } , 180_000 )
716753
717754 it ( 'keeps inaccessible content out of an otherwise identical search' , async ( ) => {
718755 const { result } = await sample ( 'denied' , ( ) => search ( ids . bobId ) )
719756 expect ( result . data . results ) . toEqual ( [ ] )
720757 } , 180_000 )
721758
759+ it ( 'preserves recall when the nearest topic is mostly inaccessible within a broad permission scope' , async ( ) => {
760+ const reader = `u:${ ids . aliceId } @fixture.test`
761+ /** Four consecutive topics share each document; hide 99% of the query's topic cluster. */
762+ await db . execute ( sql `UPDATE document
763+ SET acl = ARRAY[${ `u:${ ids . bobId } @fixture.test` } ]
764+ WHERE knowledge_base_id = ${ ids . knowledgeBaseId }
765+ AND external_id::int % 8 = 0 AND external_id::int % 800 <> 0` )
766+ try {
767+ for ( const surface of [ 'copilot' , 'dashboard' ] as const ) {
768+ const { result, plans, diagnostics } = await sample (
769+ `filtered-neighborhood.${ surface } ` ,
770+ async ( ) => {
771+ if ( surface === 'copilot' ) return search ( )
772+ const data = await searchScopedKnowledge . execute ( {
773+ principal : { kind : 'session' , userId : ids . aliceId , sessionId : 'fixture-dashboard' } ,
774+ input : {
775+ workspaceId : ids . workspaceId ,
776+ query : 'Orion deployment' ,
777+ topK : 15 ,
778+ surface,
779+ } ,
780+ } )
781+ return resultSchema . parse ( { success : true , data } )
782+ }
783+ )
784+ expect ( diagnostics ) . toMatchObject ( { retrievalStatus : 'complete' , timedOutLegs : [ ] } )
785+ expect ( result . data . results ) . toHaveLength ( 15 )
786+ const rerank = plans . find ( ( plan ) => plan . kind === 'rerank' ) !
787+ expect ( rerank ) . toBeDefined ( )
788+ const actual = await db . $client . unsafe ( rerank . query , rerank . parameters ) . values ( )
789+ const expected = await db . execute < { id : string } > ( sql `SELECT e.id FROM embedding e
790+ INNER JOIN document d ON d.id = e.document_id
791+ WHERE e.knowledge_base_id = ${ ids . knowledgeBaseId } AND e.enabled
792+ AND d.acl @> ARRAY[${ reader } ]::text[]
793+ ORDER BY (e.embedding <=> ${ JSON . stringify ( queryVector ) } ::vector) + 0, e.id
794+ LIMIT ${ actual . length } ` )
795+ expect ( expected . length ) . toBeGreaterThan ( 0 )
796+ const expectedIds = new Set ( expected . map ( ( { id } ) => id ) )
797+ const recall = actual . filter ( ( [ id ] ) => expectedIds . has ( id ) ) . length / expected . length
798+ expect ( recall ) . toBeGreaterThanOrEqual ( 0.95 )
799+ report [ `recall.filtered-neighborhood.${ surface } ` ] = { neighbors : expected . length , recall }
800+ saveReport ( )
801+ }
802+ } finally {
803+ await db
804+ . update ( document )
805+ . set ( { acl : [ reader ] } )
806+ . where ( eq ( document . knowledgeBaseId , ids . knowledgeBaseId ) )
807+ }
808+ } , 180_000 )
809+
722810 it ( 'ranks a small permission scope by its bounded IDs without a corpus-wide vector probe' , async ( ) => {
723811 const documentIds = [ 0 , 8 , 16 ] . map ( ( index ) => `${ ids . workspaceId } -doc-${ index } ` )
724812 await db
0 commit comments