diff --git a/beehive/src/relay-directory.ts b/beehive/src/relay-directory.ts index a7a8f81ebc..a4316afadc 100644 --- a/beehive/src/relay-directory.ts +++ b/beehive/src/relay-directory.ts @@ -69,10 +69,12 @@ export async function relayDirectory(viewer: string, transport: DirectoryQuery, const membership = latest(await pages({ kinds:[39002], authors:[authority], '#p':[viewer] }),e => tag(e,'d')); const channels = new Map>(); for (const [channel,e] of membership) for (const t of e.tags) { - if (t[0] !== 'p' || !key(t[1]) || t[3] !== 'bot' && !known.has(t[1])) continue; + // Membership enriches the owner's known roster only. It is never a + // discovery seed: foreign bot members must not expand profile/policy fan-out. + if (t[0] !== 'p' || !key(t[1]) || !known.has(t[1])) continue; if (!channels.has(t[1])) channels.set(t[1],new Set()); channels.get(t[1])!.add(channel); } - const candidates = [...new Set([...known,...channels.keys()])]; + const candidates = [...known]; if (candidates.length > 1000) throw Error('Directory candidate limit exceeded'); async function batches(filters: DirectoryFilter[]) { const events: Event[] = []; diff --git a/beehive/test/relay-directory.test.ts b/beehive/test/relay-directory.test.ts index 512f85546c..b95f862472 100644 --- a/beehive/test/relay-directory.test.ts +++ b/beehive/test/relay-directory.test.ts @@ -36,7 +36,9 @@ test('full canonical discovery returns only viewer-owned agents and does not pro assert.deepEqual(calls[1],[{kinds:[39002],authors:[relay],'#p':[viewer],limit:500}]); for (const batch of calls.slice(2)) { assert.ok(batch.length <= 10); for (const f of batch) { assert.equal(f.authors.length,1); assert.equal(f.limit,1); } } const policies = calls.flat().filter(f => f.kinds[0] === 30177 && f['#d']); - assert.deepEqual(new Set(policies.map(f => JSON.stringify(f))),new Set([{kinds:[30177],authors:[viewer],'#d':[a],limit:1},{kinds:[30177],authors:[other],'#d':[b],limit:1}].map(v=>JSON.stringify(v)))); + assert.deepEqual(policies,[{kinds:[30177],authors:[viewer],'#d':[a],limit:1}]); + const hydratedAuthors = calls.flat().filter(f => f.kinds[0] === 0 || f.kinds[0] === 10100).map(f => f.authors[0]); + assert.deepEqual(new Set(hydratedAuthors),new Set([a]),'foreign channel agents never enter profile/runtime fan-out'); }); test('membership never promotes unowned agents and malformed latest owned policy controls discovery', async () => {