@@ -174,11 +174,11 @@ export interface DocumentMetadata {
174174}
175175
176176/**
177- * Batch-fetch display metadata for documents referenced by search results.
178- * Applies the same visibility and access predicates as the search SQL itself ,
179- * so the lookup never surfaces a filename for a row the caller could not have
180- * matched. Returns a map keyed by document id; missing ids indicate the
181- * document is no longer visible and should be skipped.
177+ * Batch-fetch display metadata for documents referenced by search results, under the full read
178+ * predicate and the scope the results were read under — with the live grants that scope resolved ,
179+ * so a gated source's result keeps its name and URL, and a revoked one loses them here too.
180+ * Returns a map keyed by document id; missing ids indicate the document is no longer visible and
181+ * should be skipped.
182182 */
183183export async function getDocumentMetadataByIds (
184184 documentIds : string [ ] ,
@@ -206,7 +206,7 @@ export async function getDocumentMetadataByIds(
206206 eq ( document . userExcluded , false ) ,
207207 isNull ( document . archivedAt ) ,
208208 isNull ( document . deletedAt ) ,
209- knowledgeMetadataCandidateAccessCondition ( access )
209+ knowledgeAccessCondition ( access )
210210 )
211211 )
212212 )
@@ -598,6 +598,8 @@ export interface LiveSourceAccess {
598598 gates : ( connectorId : string ) => boolean
599599 /** The caller's scope with its grants, and the gated sources those grants do not cover. */
600600 resolve : ( ) => Promise < { access : KnowledgeAccessScope ; denied : ReadonlySet < string > } >
601+ /** The scope content was read under: with its grants once they were resolved, else as given. */
602+ current : ( ) => Promise < KnowledgeAccessScope >
601603}
602604
603605/** Binds a search's gated sources to one memoized resolution of the caller's grants. */
@@ -619,6 +621,7 @@ export function liveSourceAccessFor(
619621 }
620622 return {
621623 gates : ( connectorId ) => gated . has ( connectorId ) ,
624+ current : async ( ) => ( pending ? ( await pending ) . access : access ) ,
622625 resolve : ( ) => {
623626 pending ??= measureSearchStage ( 'live_source_grants' , async ( ) => {
624627 const scopes = await mapWithConcurrency ( pages , SOURCE_RANKING_CONCURRENCY , ( page ) =>
@@ -2055,6 +2058,8 @@ export interface RetrievalStatus {
20552058export interface KnowledgeRetrievalResult {
20562059 rows : SearchResult [ ]
20572060 retrieval : RetrievalStatus
2061+ /** The scope the returned content was read under; what may see these rows may see their metadata. */
2062+ readAccess : KnowledgeAccessScope
20582063}
20592064
20602065/** Legacy surfaces cannot silently present partial retrieval as complete. */
@@ -2090,14 +2095,16 @@ export async function retrieveKnowledgeSearch(
20902095 keyword : new SearchBudget ( 'keyword' , deadline , params . signal ) ,
20912096 tags : new SearchBudget ( 'tags' , deadline , params . signal ) ,
20922097 }
2093- const finish = ( rows : SearchResult [ ] ) : KnowledgeRetrievalResult => {
2098+ const finish = async ( rows : SearchResult [ ] ) : Promise < KnowledgeRetrievalResult > => {
20942099 params . signal ?. throwIfAborted ( )
2100+ const readAccess = ( await liveSourceAccess ?. current ( ) ) ?? access
20952101 const timedOutLegs = Object . values ( budgets )
20962102 . filter ( ( budget ) => budget . timedOut )
20972103 . map ( ( budget ) => budget . leg )
20982104 return {
20992105 rows : boostRecency ? applyRecencyBoost ( rows ) : rows ,
21002106 retrieval : { status : timedOutLegs . length ? 'partial' : 'complete' , timedOutLegs } ,
2107+ readAccess,
21012108 }
21022109 }
21032110 /**
0 commit comments