@@ -76,10 +76,9 @@ vi.mock('@/executor/utils/resolved-secret-content-projection', () => ({
7676 projectResolvedSecretModelContent : ( value : string ) => ( { safe : true , value } ) ,
7777} ) )
7878
79+ import { resourceScopeFromOwner , resourceScopeKey } from '@/lib/core/resource-scope'
7980import { withSearchDiagnostics } from '@/lib/knowledge/search/diagnostics'
8081import {
81- decodeLiveReference ,
82- encodeLiveReference ,
8382 matchesLiveFilters ,
8483 readLiveDocument ,
8584 searchLiveKnowledge ,
@@ -89,6 +88,11 @@ import { NativeSearchError } from '@/lib/sim-search/live/http'
8988import { createPolicyVerifier } from '@/lib/sim-search/live/policy'
9089import { defaultLiveSearchPolicy } from '@/lib/sim-search/live/policy-schema'
9190import { livePolicyFor } from '@/lib/sim-search/live/policy-store'
91+ import {
92+ decodeLiveReference ,
93+ encodeLiveReference ,
94+ liveReferenceOwner ,
95+ } from '@/lib/sim-search/live/reference'
9296import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
9397
9498workspaceAuthzMockFns . mockPermissionSatisfies . mockImplementation (
@@ -1185,22 +1189,117 @@ describe('authorized live retrieval', () => {
11851189 } )
11861190 ) . rejects . toBe ( 'user_stop:test' )
11871191 } )
1188- it ( 'rejects cross-user document references before token resolution' , async ( ) => {
1189- const search = await searchLiveKnowledge . execute ( { principal, input } )
1190- const reference = decodeLiveReference ( search . results [ 0 ] . documentId )
1191- mocks . resolveAccount . mockClear ( )
1192- await expect (
1192+ describe ( 'live document IDs' , ( ) => {
1193+ const workspaceScope = resourceScopeKey ( resourceScopeFromOwner ( { workspaceId : 'workspace' } ) )
1194+ /** The earlier wire form, spelled out so these tests pin what older conversations hold. */
1195+ const legacyId = ( fields : Record < string , string > ) =>
1196+ `live:${ Buffer . from ( JSON . stringify ( { v : 1 , ...fields } ) ) . toString ( 'base64url' ) } `
1197+ const readerOwner = liveReferenceOwner ( 'reader' , workspaceScope )
1198+ const fields = { account : 'account' , provider : 'google_drive' as const , id : 'doc' }
1199+ const read = ( documentId : string , extra : Record < string , unknown > = { } , as = principal ) =>
11931200 readLiveDocument . execute ( {
1194- principal,
1201+ principal : as ,
11951202 input : {
11961203 workspaceId : 'workspace' ,
1197- documentId : encodeLiveReference ( { ... reference , user : 'someone-else' } ) ,
1204+ documentId,
11981205 limit : 3 ,
11991206 resultSecretRegistry : new ResolvedSecretTraceRegistry ( [ ] ) ,
1207+ ...extra ,
12001208 } ,
12011209 } )
1202- ) . rejects . toThrow ( 'Document not found' )
1203- expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1210+
1211+ it ( 'mints a compact ID naming no reader or scope that reads back' , async ( ) => {
1212+ const search = await searchLiveKnowledge . execute ( { principal, input } )
1213+ const [ first ] = search . results
1214+ if ( ! first ) throw new Error ( 'Expected a search result' )
1215+ const { documentId } = first
1216+ expect ( documentId ) . toBe ( encodeLiveReference ( fields , readerOwner ) )
1217+ expect ( documentId ) . toMatch ( / ^ l i v e 2 : g o o g l e _ d r i v e ~ a c c o u n t ~ d o c ~ [ 0 - 9 a - f ] { 6 } $ / )
1218+ const result = await read ( documentId )
1219+ expect ( result . documentId ) . toBe ( documentId )
1220+ expect ( result . chunks [ 0 ] ?. content ) . toBe ( 'Evidence' )
1221+ expect ( mocks . resolveAccount ) . toHaveBeenLastCalledWith (
1222+ expect . objectContaining ( { workspaceId : 'workspace' } ) ,
1223+ 'reader' ,
1224+ 'account' ,
1225+ expect . anything ( )
1226+ )
1227+ } )
1228+ it ( "refuses another member's personal account through the reader's own account list" , async ( ) => {
1229+ mocks . resolveAccount . mockImplementation ( async ( _owner , _userId , accountId : string ) => {
1230+ if ( accountId !== 'account' )
1231+ throw new NativeSearchError ( 'reconnect' , 'This search connection is no longer available.' )
1232+ return { account, accessToken : 'secret' }
1233+ } )
1234+ const theirs = encodeLiveReference ( { ...fields , account : 'their-account' } , readerOwner )
1235+ await expect ( read ( theirs ) ) . rejects . toMatchObject ( { code : 'unauthorized' } )
1236+ expect ( mocks . resolveAccount ) . toHaveBeenCalledWith (
1237+ expect . anything ( ) ,
1238+ 'reader' ,
1239+ 'their-account' ,
1240+ expect . anything ( )
1241+ )
1242+ expect ( mocks . read ) . not . toHaveBeenCalled ( )
1243+ } )
1244+ it . each ( [
1245+ [ 'another member' , 'other-reader' , workspaceScope ] ,
1246+ [
1247+ 'another scope' ,
1248+ 'reader' ,
1249+ resourceScopeKey ( resourceScopeFromOwner ( { workspaceId : 'other' } ) ) ,
1250+ ] ,
1251+ ] ) (
1252+ 'refuses an ID minted for %s as not found before resolving any account' ,
1253+ async ( _case , user , scope ) => {
1254+ const theirs = encodeLiveReference ( fields , liveReferenceOwner ( user , scope ) )
1255+ await expect ( read ( theirs ) ) . rejects . toThrow ( 'Document not found in this search scope' )
1256+ expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1257+ expect ( mocks . read ) . not . toHaveBeenCalled ( )
1258+ }
1259+ )
1260+ it . each ( [
1261+ [ 'a missing field' , 'live2:google_drive~account' ] ,
1262+ [ 'an unknown provider' , 'live2:nowhere~account~doc' ] ,
1263+ [ 'a broken escape' , 'live2:google_drive~account~%E0%A4' ] ,
1264+ [ 'too many fields' , 'live2:google_drive~a~b~c~d~e~f~g~h' ] ,
1265+ [ 'an unreadable earlier form' , 'live:not-a-reference' ] ,
1266+ [ 'no prefix' , 'google_drive~account~doc' ] ,
1267+ ] ) ( 'asks the caller to search again for %s' , async ( _case , documentId ) => {
1268+ await expect ( read ( documentId ) ) . rejects . toThrow ( 'Search again' )
1269+ expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1270+ } )
1271+ it ( 'still reads an earlier-form ID minted for this reader and scope' , async ( ) => {
1272+ const result = await read ( legacyId ( { user : 'reader' , scope : workspaceScope , ...fields } ) )
1273+ expect ( result . chunks [ 0 ] ?. content ) . toBe ( 'Evidence' )
1274+ } )
1275+ it . each ( [
1276+ [ 'reader' , { user : 'someone-else' , scope : workspaceScope } ] ,
1277+ [ 'scope' , { user : 'reader' , scope : 'workspace:elsewhere' } ] ,
1278+ ] ) (
1279+ 'rejects an earlier-form ID minted for another %s before token resolution' ,
1280+ async ( _case , binding ) => {
1281+ await expect ( read ( legacyId ( { ...binding , ...fields } ) ) ) . rejects . toThrow (
1282+ 'Document not found'
1283+ )
1284+ expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1285+ }
1286+ )
1287+ it ( 'matches a selected document across the earlier and current forms' , async ( ) => {
1288+ const current = encodeLiveReference ( fields , readerOwner )
1289+ const earlier = legacyId ( { user : 'reader' , scope : workspaceScope , ...fields } )
1290+ await expect ( read ( current , { filters : { documentIds : [ earlier ] } } ) ) . resolves . toMatchObject ( {
1291+ documentId : current ,
1292+ } )
1293+ await expect ( read ( earlier , { filters : { documentIds : [ current ] } } ) ) . resolves . toBeDefined ( )
1294+ expect (
1295+ matchesLiveFilters ( document , current , 'google_drive' , { documentIds : [ earlier ] } )
1296+ ) . toBe ( true )
1297+ await expect (
1298+ read ( current , {
1299+ filters : { documentIds : [ encodeLiveReference ( { ...fields , id : 'other' } , readerOwner ) ] } ,
1300+ } )
1301+ ) . rejects . toThrow ( 'outside the selected search filters' )
1302+ } )
12041303 } )
12051304 it ( 'rejects a document moved out of scope while its content was being read' , async ( ) => {
12061305 const search = await searchLiveKnowledge . execute ( { principal, input } )
0 commit comments