-
Notifications
You must be signed in to change notification settings - Fork 3.8k
improvement(knowledge): resolve connector and member state once per search #8040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+59,212
−604
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
d7d57fc
improvement(knowledge): resolve connectors once and rank each source …
waleedlatif1 4601d47
improvement(knowledge): resolve the caller's member identities with t…
waleedlatif1 0795a12
chore(knowledge): drop the stage the access plan made unnecessary
waleedlatif1 edf9679
improvement(knowledge): resolve live source grants once per search
waleedlatif1 6104e1e
test(db): register the projection-source script migration in the push…
waleedlatif1 b445152
fix(knowledge): walk a saturated slice and re-read connector state at…
waleedlatif1 b6da62a
improvement(knowledge): ask a live source for grants only when one of…
waleedlatif1 0f01d94
fix(knowledge): rank uploads without a sliced source and bound the gr…
waleedlatif1 b958c90
fix(knowledge): refill after a denied gated source, reserve the index…
waleedlatif1 886c5f7
fix(knowledge): start the scan budget over when the pages are rebuilt
waleedlatif1 3ec5f2e
fix(knowledge): serialize the projection source with its document
waleedlatif1 4d3f266
improvement(knowledge): choose the vector plan by the caller's reach
waleedlatif1 abf3989
fix(knowledge): search a broad caller's sources when their whole-grap…
waleedlatif1 463b50a
fix(knowledge): read result metadata under the scope the results were…
waleedlatif1 8aa42b9
fix(knowledge): keep what a short broad walk found when searching its…
waleedlatif1 41434b5
fix(api): fail a v1 knowledge search whose retrieval ran out of time
waleedlatif1 94d9412
improvement(knowledge): mirror each chunk's source and ACL onto the r…
waleedlatif1 ace08b1
improvement(knowledge): decide candidate readability on the ranking row
waleedlatif1 0dcbffd
improvement(knowledge): rank the sliced sources on the projection row
waleedlatif1 9d70046
improvement(knowledge): let a resolved scope's reach choose its plan …
waleedlatif1 1ab4dbf
improvement(knowledge): walk every source a caller reads whole; one k…
waleedlatif1 9b57644
improvement(knowledge): widen a broad reader's short walk; keyword wi…
waleedlatif1 48da9fc
improvement(knowledge): index the projection by source; rank a narrow…
waleedlatif1 b77b385
improvement(knowledge): widen a broad walk only when it is short of t…
waleedlatif1 aa44c56
fix(knowledge): hydrate an oversized ranking page in slices, only as …
waleedlatif1 65be3b7
fix(knowledge): count a candidate as considered only once its slice i…
waleedlatif1 da7afa8
fix(knowledge): pair an observer with its connector, settle the per-s…
waleedlatif1 46e88eb
fix(knowledge): give a broad reader's wider walk half of what the leg…
waleedlatif1 66c972d
improvement(knowledge): let an on-row walk run to its cap, widen a na…
waleedlatif1 265ed27
test(knowledge): expect the scan settings to share the deadline state…
waleedlatif1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import { db } from '@sim/db' | ||
| import { knowledgeConnector, knowledgeConnectorMember } from '@sim/db/schema' | ||
| import { and, eq, inArray, isNull, sql } from 'drizzle-orm' | ||
| import { SOURCE_ACL_MAX_AGE_MS } from '@/lib/knowledge/access/freshness' | ||
| import { | ||
| type KnowledgeConnectorEligibility, | ||
| type KnowledgeMemberObserver, | ||
| type KnowledgeMemberObservers, | ||
| type SearchAccessPlan, | ||
| textArrayLiteral, | ||
| } from '@/lib/knowledge/access/predicate' | ||
| import type { KnowledgeAccessScope } from '@/lib/knowledge/access/types' | ||
| import { searchIntegrationAccessCondition } from '@/lib/knowledge/search/integration-policy' | ||
|
|
||
| /** | ||
| * The connectors a search may read from, grouped by access mode, with the ones whose reader access | ||
| * must be proven live marked. | ||
| * | ||
| * Deletion, archival, a pending access rewrite and the organization's integration approval are | ||
| * facts about a connector. Resolving them once per query — there are tens of connectors against | ||
| * hundreds of thousands of documents — leaves each candidate its own columns to check. | ||
| */ | ||
| async function resolveConnectorEligibility( | ||
| knowledgeBaseIds: readonly string[] | ||
| ): Promise<KnowledgeConnectorEligibility> { | ||
| const eligibility: { | ||
| workspace: string[] | ||
| admin: string[] | ||
| members: string[] | ||
| liveProofRequired: string[] | ||
| } = { workspace: [], admin: [], members: [], liveProofRequired: [] } | ||
| if (knowledgeBaseIds.length === 0) return eligibility | ||
| const rows = await db | ||
| .select({ | ||
| id: knowledgeConnector.id, | ||
| accessMode: knowledgeConnector.accessMode, | ||
| connectorType: knowledgeConnector.connectorType, | ||
| /** A GitHub connector is gated only where it names the immutable repository behind a grant. */ | ||
| githubRepository: sql<boolean>`${knowledgeConnector.sourceConfig}::jsonb ? 'githubRepositoryId'`, | ||
| }) | ||
| .from(knowledgeConnector) | ||
| .where( | ||
| and( | ||
| inArray(knowledgeConnector.knowledgeBaseId, [...knowledgeBaseIds]), | ||
| isNull(knowledgeConnector.deletedAt), | ||
| isNull(knowledgeConnector.archivedAt), | ||
| eq(knowledgeConnector.accessRewritePending, false), | ||
| searchIntegrationAccessCondition() | ||
| ) | ||
| ) | ||
| for (const row of rows) { | ||
| if (row.accessMode === 'workspace') eligibility.workspace.push(row.id) | ||
| else if (row.accessMode === 'admin') eligibility.admin.push(row.id) | ||
| else if (row.accessMode === 'members') eligibility.members.push(row.id) | ||
| else continue | ||
| const live = | ||
| (row.connectorType === 'github' && row.githubRepository) || | ||
| (row.connectorType === 'confluence' && row.accessMode === 'admin') | ||
| if (live) eligibility.liveProofRequired.push(row.id) | ||
| } | ||
| return eligibility | ||
| } | ||
|
|
||
| /** | ||
| * The caller's own member identities on these connectors, split by whether the member's change | ||
| * feed is itself current. | ||
| * | ||
| * A members-mode document is readable while one of the caller's active members observes it, | ||
| * freshly — and which members those are is a fact about the caller, not about any document. A | ||
| * member whose feed drained recently confirms every observation it holds, so its observations need | ||
| * no age check at all; the rest are checked against the age of the observation itself. Resolved | ||
| * once, the per-document check becomes one lookup on the observation key, with no join to the | ||
| * member behind it. | ||
| */ | ||
| async function resolveMemberObservers( | ||
| access: KnowledgeAccessScope, | ||
| connectorIds: readonly string[] | ||
| ): Promise<{ observers: KnowledgeMemberObservers; memberSources: string[] }> { | ||
| if (access.kind !== 'user' || connectorIds.length === 0 || access.tokens.length === 0) { | ||
| return { observers: { confirmed: [], observed: [] }, memberSources: [] } | ||
| } | ||
| const rows = await db | ||
| .select({ | ||
| id: knowledgeConnectorMember.id, | ||
| connectorId: knowledgeConnectorMember.connectorId, | ||
| syncedThrough: knowledgeConnectorMember.memberSyncedThrough, | ||
| }) | ||
| .from(knowledgeConnectorMember) | ||
| .where( | ||
| and( | ||
| inArray(knowledgeConnectorMember.connectorId, [...connectorIds]), | ||
| eq(knowledgeConnectorMember.status, 'active'), | ||
| sql`${knowledgeConnectorMember.subjectToken} = ANY(${textArrayLiteral([...access.tokens])})` | ||
| ) | ||
| ) | ||
| const cutoff = Date.now() - SOURCE_ACL_MAX_AGE_MS | ||
| const confirmed: KnowledgeMemberObserver[] = [] | ||
| const observed: KnowledgeMemberObserver[] = [] | ||
| const memberSources = new Set<string>() | ||
| for (const row of rows) { | ||
| const member = { id: row.id, connectorId: row.connectorId } | ||
| if (row.syncedThrough !== null && row.syncedThrough.getTime() > cutoff) confirmed.push(member) | ||
| else observed.push(member) | ||
| memberSources.add(row.connectorId) | ||
| } | ||
| return { observers: { confirmed, observed }, memberSources: [...memberSources] } | ||
| } | ||
|
|
||
| /** | ||
| * Everything a search needs to know about its sources and the caller's standing in them, resolved | ||
| * once: which connectors it may read, the caller's member identities there, and the sources they | ||
| * are a member of. Each is a fact about a connector or a caller, so deriving them per candidate | ||
| * document is what made retrieval cost grow with the size of what someone may read. | ||
| */ | ||
| export async function resolveSearchAccessPlan( | ||
| knowledgeBaseIds: readonly string[], | ||
| access: KnowledgeAccessScope | ||
| ): Promise<SearchAccessPlan> { | ||
| const connectors = await resolveConnectorEligibility(knowledgeBaseIds) | ||
| const { observers, memberSources } = await resolveMemberObservers(access, connectors.members) | ||
| return { connectors, observers, memberSources } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.