|
6 | 6 | * nothing about what a workspace search returns. A signed-in reader's own grants widen what they |
7 | 7 | * read, and a source whose reader must be proven live admits a candidate only once the proof |
8 | 8 | * holds, with the readable rows below it filling the page when it does not. A Search-marked knowledge base |
9 | | - * named explicitly by id is searched the same way. |
| 9 | + * named explicitly by id is searched the same way, except that it keeps no vector candidates. |
10 | 10 | */ |
11 | 11 | import { createHash } from 'node:crypto' |
12 | 12 | import type { Principal } from '@sim/auth/principal' |
@@ -48,10 +48,14 @@ afterAll(async () => { |
48 | 48 | await db.$client.end() |
49 | 49 | }) |
50 | 50 |
|
| 51 | +const ALL_MODES: readonly KnowledgeSearchMode[] = ['vector', 'hybrid'] |
| 52 | +/** A Search knowledge base's chunk writes delete its candidate rows, so only keyword ranking finds it. */ |
| 53 | +const KEYWORD_RANKED_MODES: readonly KnowledgeSearchMode[] = ['hybrid'] |
| 54 | + |
51 | 55 | describe.each([ |
52 | | - ['a workspace knowledge base', false], |
53 | | - ['a dormant search index', true], |
54 | | -] as const)('search of %s decides access on the document', (_label, isSearchIndex) => { |
| 56 | + ['a workspace knowledge base', false, ALL_MODES], |
| 57 | + ['a dormant search index', true, KEYWORD_RANKED_MODES], |
| 58 | +] as const)('search of %s decides access on the document', (_label, isSearchIndex, modes) => { |
55 | 59 | const ids = createKnowledgeAclFixtureIds() |
56 | 60 | const baseId = generateId() |
57 | 61 | const readable = [generateId(), generateId(), generateId()] |
@@ -161,41 +165,43 @@ describe.each([ |
161 | 165 | return result.rows.map((row) => row.documentId).sort() |
162 | 166 | } |
163 | 167 |
|
164 | | - it.each(['vector', 'hybrid'] as const)( |
165 | | - 'returns a signed-in reader the rows an actorless run gets (%s)', |
166 | | - async (mode) => { |
167 | | - const previousDebug = db.$client.options.debug |
168 | | - const statements: string[] = [] |
169 | | - db.$client.options.debug = (_connection, query) => { |
170 | | - statements.push(query) |
171 | | - } |
172 | | - try { |
173 | | - const signedIn = await search(reader, mode) |
174 | | - const workspaceKey = await search(actorless, mode) |
175 | | - expect(signedIn).toEqual([...readable].sort()) |
176 | | - expect(workspaceKey).toEqual(signedIn) |
177 | | - /** Nothing read the per-row machinery the search-index path keeps for itself. */ |
178 | | - for (const fragment of [ |
179 | | - 'embedding_keyword_search', |
180 | | - 'knowledge_projection_dirty', |
181 | | - '"embedding_search"."acl"', |
182 | | - '"embedding_search"."connector_id"', |
183 | | - ]) { |
184 | | - expect(statements.filter((statement) => statement.includes(fragment))).toEqual([]) |
185 | | - } |
186 | | - } finally { |
187 | | - db.$client.options.debug = previousDebug |
| 168 | + it.each(modes)('returns a signed-in reader the rows an actorless run gets (%s)', async (mode) => { |
| 169 | + const previousDebug = db.$client.options.debug |
| 170 | + const statements: string[] = [] |
| 171 | + db.$client.options.debug = (_connection, query) => { |
| 172 | + statements.push(query) |
| 173 | + } |
| 174 | + try { |
| 175 | + const signedIn = await search(reader, mode) |
| 176 | + const workspaceKey = await search(actorless, mode) |
| 177 | + expect(signedIn).toEqual([...readable].sort()) |
| 178 | + expect(workspaceKey).toEqual(signedIn) |
| 179 | + /** Nothing read the per-row machinery the search-index path keeps for itself. */ |
| 180 | + for (const fragment of [ |
| 181 | + 'embedding_keyword_search', |
| 182 | + 'knowledge_projection_dirty', |
| 183 | + '"embedding_search"."acl"', |
| 184 | + '"embedding_search"."connector_id"', |
| 185 | + ]) { |
| 186 | + expect(statements.filter((statement) => statement.includes(fragment))).toEqual([]) |
188 | 187 | } |
| 188 | + } finally { |
| 189 | + db.$client.options.debug = previousDebug |
189 | 190 | } |
190 | | - ) |
| 191 | + }) |
191 | 192 |
|
192 | | - it.each(['vector', 'hybrid'] as const)( |
| 193 | + it.each(modes)( |
193 | 194 | 'returns a signed-in reader the document shared with them alone as well (%s)', |
194 | 195 | async (mode) => { |
195 | 196 | const owner: Principal = { kind: 'session', userId: ids.aliceId, sessionId: 'fixture-owner' } |
196 | 197 | expect(await search(owner, mode)).toEqual([...readable, personal].sort()) |
197 | 198 | } |
198 | 199 | ) |
| 200 | + |
| 201 | + it.runIf(isSearchIndex)('finds nothing by vector, since no candidate row is kept', async () => { |
| 202 | + const owner: Principal = { kind: 'session', userId: ids.aliceId, sessionId: 'fixture-owner' } |
| 203 | + expect(await search(owner, 'vector')).toEqual([]) |
| 204 | + }) |
199 | 205 | }) |
200 | 206 |
|
201 | 207 | describe('a workspace source whose reader must be proven live', () => { |
|
0 commit comments