@@ -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,7 @@ 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 { decodeLiveReference , encodeLiveReference } from '@/lib/sim-search/live/reference'
9292import { ResolvedSecretTraceRegistry } from '@/executor/utils/resolved-secret-trace-registry'
9393
9494workspaceAuthzMockFns . mockPermissionSatisfies . mockImplementation (
@@ -1185,22 +1185,110 @@ describe('authorized live retrieval', () => {
11851185 } )
11861186 ) . rejects . toBe ( 'user_stop:test' )
11871187 } )
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 (
1188+ describe ( 'live document IDs' , ( ) => {
1189+ const workspaceScope = resourceScopeKey ( resourceScopeFromOwner ( { workspaceId : 'workspace' } ) )
1190+ /** The earlier wire form, spelled out so these tests pin what older conversations hold. */
1191+ const legacyId = ( fields : Record < string , string > ) =>
1192+ `live:${ Buffer . from ( JSON . stringify ( { v : 1 , ...fields } ) ) . toString ( 'base64url' ) } `
1193+ const fields = { account : 'account' , provider : 'google_drive' as const , id : 'doc' }
1194+ const read = ( documentId : string , extra : Record < string , unknown > = { } , as = principal ) =>
11931195 readLiveDocument . execute ( {
1194- principal,
1196+ principal : as ,
11951197 input : {
11961198 workspaceId : 'workspace' ,
1197- documentId : encodeLiveReference ( { ... reference , user : 'someone-else' } ) ,
1199+ documentId,
11981200 limit : 3 ,
11991201 resultSecretRegistry : new ResolvedSecretTraceRegistry ( [ ] ) ,
1202+ ...extra ,
12001203 } ,
12011204 } )
1202- ) . rejects . toThrow ( 'Document not found' )
1203- expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1205+
1206+ it ( 'mints a compact ID naming no reader or scope that reads back' , async ( ) => {
1207+ const search = await searchLiveKnowledge . execute ( { principal, input } )
1208+ const documentId = search . results [ 0 ] ! . documentId
1209+ 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 } $ / )
1210+ const result = await read ( documentId )
1211+ expect ( result . documentId ) . toBe ( documentId )
1212+ expect ( result . chunks [ 0 ] ?. content ) . toBe ( 'Evidence' )
1213+ expect ( mocks . resolveAccount ) . toHaveBeenLastCalledWith (
1214+ expect . objectContaining ( { workspaceId : 'workspace' } ) ,
1215+ 'reader' ,
1216+ 'account' ,
1217+ expect . anything ( )
1218+ )
1219+ } )
1220+ it ( "refuses another member's personal account through the reader's own account list" , async ( ) => {
1221+ mocks . resolveAccount . mockImplementation ( async ( _owner , _userId , accountId : string ) => {
1222+ if ( accountId !== 'account' )
1223+ throw new NativeSearchError ( 'reconnect' , 'This search connection is no longer available.' )
1224+ return { account, accessToken : 'secret' }
1225+ } )
1226+ const theirs = encodeLiveReference ( { ...fields , account : 'their-account' } )
1227+ await expect ( read ( theirs ) ) . rejects . toMatchObject ( { code : 'unauthorized' } )
1228+ expect ( mocks . resolveAccount ) . toHaveBeenCalledWith (
1229+ expect . anything ( ) ,
1230+ 'reader' ,
1231+ 'their-account' ,
1232+ expect . anything ( )
1233+ )
1234+ expect ( mocks . read ) . not . toHaveBeenCalled ( )
1235+ } )
1236+ it ( "gates a shared account's ID by the reader's own verification" , async ( ) => {
1237+ const search = await searchLiveKnowledge . execute ( { principal, input } )
1238+ mocks . service . mockResolvedValue ( {
1239+ policy : defaultLiveSearchPolicy ( ) ,
1240+ partial : false ,
1241+ verify : async ( ) => false ,
1242+ } )
1243+ const other = createSessionPrincipal ( { userId : 'other-reader' , sessionId : 's2' } )
1244+ await expect ( read ( search . results [ 0 ] ! . documentId , { } , other ) ) . rejects . toThrow (
1245+ 'outside your organization'
1246+ )
1247+ expect ( mocks . read ) . not . toHaveBeenCalled ( )
1248+ } )
1249+ it . each ( [
1250+ [ 'a missing field' , 'live2:google_drive~account' ] ,
1251+ [ 'an unknown provider' , 'live2:nowhere~account~doc' ] ,
1252+ [ 'a broken escape' , 'live2:google_drive~account~%E0%A4' ] ,
1253+ [ 'too many fields' , 'live2:google_drive~a~b~c~d~e~f~g~h' ] ,
1254+ [ 'an unreadable earlier form' , 'live:not-a-reference' ] ,
1255+ [ 'no prefix' , 'google_drive~account~doc' ] ,
1256+ ] ) ( 'asks the caller to search again for %s' , async ( _case , documentId ) => {
1257+ await expect ( read ( documentId ) ) . rejects . toThrow ( 'Search again' )
1258+ expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1259+ } )
1260+ it ( 'still reads an earlier-form ID minted for this reader and scope' , async ( ) => {
1261+ const result = await read ( legacyId ( { user : 'reader' , scope : workspaceScope , ...fields } ) )
1262+ expect ( result . chunks [ 0 ] ?. content ) . toBe ( 'Evidence' )
1263+ } )
1264+ it . each ( [
1265+ [ 'reader' , { user : 'someone-else' , scope : workspaceScope } ] ,
1266+ [ 'scope' , { user : 'reader' , scope : 'workspace:elsewhere' } ] ,
1267+ ] ) (
1268+ 'rejects an earlier-form ID minted for another %s before token resolution' ,
1269+ async ( _case , binding ) => {
1270+ await expect ( read ( legacyId ( { ...binding , ...fields } ) ) ) . rejects . toThrow (
1271+ 'Document not found'
1272+ )
1273+ expect ( mocks . resolveAccount ) . not . toHaveBeenCalled ( )
1274+ }
1275+ )
1276+ it ( 'matches a selected document across the earlier and current forms' , async ( ) => {
1277+ const current = encodeLiveReference ( fields )
1278+ const earlier = legacyId ( { user : 'reader' , scope : workspaceScope , ...fields } )
1279+ await expect ( read ( current , { filters : { documentIds : [ earlier ] } } ) ) . resolves . toMatchObject ( {
1280+ documentId : current ,
1281+ } )
1282+ await expect ( read ( earlier , { filters : { documentIds : [ current ] } } ) ) . resolves . toBeDefined ( )
1283+ expect (
1284+ matchesLiveFilters ( document , current , 'google_drive' , { documentIds : [ earlier ] } )
1285+ ) . toBe ( true )
1286+ await expect (
1287+ read ( current , {
1288+ filters : { documentIds : [ encodeLiveReference ( { ...fields , id : 'other' } ) ] } ,
1289+ } )
1290+ ) . rejects . toThrow ( 'outside the selected search filters' )
1291+ } )
12041292 } )
12051293 it ( 'rejects a document moved out of scope while its content was being read' , async ( ) => {
12061294 const search = await searchLiveKnowledge . execute ( { principal, input } )
0 commit comments