// DidPeerRouter regression — multi-DID reading over ONE shared swarm. Two holders serve real signed
// repos (RepoManager + FirehoseSequencer) on an in-process testnet; a reader must learn BOTH.
// CONTROL: per-DidPeer attach() collides on the single announce channel -> only the FIRST-added DID
// reads; the second never learns its cores (the bug).
// FIX: DidPeerRouter dispatches each announce by DID -> BOTH read.
// Run: node test/router.test.mjs
import { mkdtempSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'
import createTestnet from '@hyperswarm/testnet'
import Hyperswarm from 'hyperswarm'
import { RepoStore } from 'atproto-store/lib/repo-store.js'
import { RepoManager } from 'atproto-store/lib/repo-manager.js'
import { FirehoseSequencer } from 'atproto-store/lib/sequencer.js'
import { openCorestore } from 'atproto-store/lib/corestore.js'
import { createAtprotoIdentityFromKey } from 'atproto-identity/lib/atproto-identity.js'
import { discoveryTopic } from 'atproto-identity/lib/discovery.js'
import { DidPeer, DidPeerRouter } from '../index.js'
const assert = (c, m) => { if (!c) { console.error('FAIL:', m); cleanupSync(); process.exit(1) } console.log(' ok -', m) }
const iso = '2026-07-01T00:00:00.000Z'
const dirs = []
const toClose = []
function newDir (p) { const d = mkdtempSync(join(tmpdir(), p)); dirs.push(d); return d }
function cleanupSync () { for (const d of dirs) { try { rmSync(d, { recursive: true, force: true }) } catch {} } }
const idA = createAtprotoIdentityFromKey(new Uint8Array(32).fill(9), { handle: 'a.test', pdsUrl: 'https://pds.local' })
const idB = createAtprotoIdentityFromKey(new Uint8Array(32).fill(11), { handle: 'b.test', pdsUrl: 'https://pds.local' })
const testnet = await createTestnet(3)
const bootstrap = testnet.bootstrap
// A holder authors a signed repo + sequences its commit, then serves the cores on its own swarm.
async function makeHolder (id, text) {
const store = openCorestore(newDir('holder-'))
const repoStore = await new RepoStore(store, { name: `atproto-repo:${id.did}` }).ready()
let micros = 1_700_000_000_000_000n
const mgr = await new RepoManager(repoStore, { did: id.did, privateKey: id.privateKey, clock: () => (micros += 1_000_000n) }).init()
const write = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: '1', record: { $type: 'app.bsky.feed.post', text, createdAt: iso } })
const seq = await new FirehoseSequencer(store.get({ name: 'firehose' })).ready()
await seq.sequenceCommit(write, { time: iso })
const peer = new DidPeer(store, { did: id.did, verifyKey: id.publicKey })
await peer.provide({ repoKey: repoStore.core.key, firehoseKey: seq.core.key })
const swarm = new Hyperswarm({ bootstrap })
swarm.on('connection', (c) => peer.attach(c))
await swarm.join(discoveryTopic(id.did), { server: true, client: true }).flushed()
toClose.push(async () => { try { await swarm.destroy() } catch {}; try { await peer.close() } catch {}; try { await store.close() } catch {} })
return peer
}
const holderA = await makeHolder(idA, 'hello from A')
const holderB = await makeHolder(idB, 'hello from B')
console.log('holders serving:', idA.did.slice(0, 20), '+', idB.did.slice(0, 20))
// --- CONTROL: per-peer attach() — only the FIRST-added DID reads ---
{
const store = openCorestore(newDir('ctrl-'))
const swarm = new Hyperswarm({ bootstrap })
const cA = new DidPeer(store, { did: idA.did, verifyKey: idA.publicKey })
const cB = new DidPeer(store, { did: idB.did, verifyKey: idB.publicKey })
swarm.on('connection', (c) => cA.attach(c)) // cA registered FIRST -> grabs the channel on every conn
swarm.on('connection', (c) => cB.attach(c))
await swarm.join(discoveryTopic(idA.did), { server: true, client: true }).flushed()
await swarm.join(discoveryTopic(idB.did), { server: true, client: true }).flushed()
const [chA, chB] = await Promise.all([cA.ready({ timeout: 15000 }), cB.ready({ timeout: 15000 })])
assert(!!chA, 'CONTROL: the first-added DID (A) reads')
assert(!chB, 'CONTROL: per-peer attach() — the SECOND DID (B) never reads (reproduces the bug)')
await swarm.destroy(); await cA.close(); await cB.close(); await store.close()
}
// --- FIX: DidPeerRouter dispatches by DID — BOTH read ---
{
const store = openCorestore(newDir('router-'))
const swarm = new Hyperswarm({ bootstrap })
const router = new DidPeerRouter(store)
swarm.on('connection', (c) => router.onconnection(c))
const pA = router.add(idA.did, { verifyKey: idA.publicKey })
const pB = router.add(idB.did, { verifyKey: idB.publicKey })
await swarm.join(discoveryTopic(idA.did), { server: true, client: true }).flushed()
await swarm.join(discoveryTopic(idB.did), { server: true, client: true }).flushed()
const [hA, hB] = await Promise.all([pA.ready({ timeout: 20000 }), pB.ready({ timeout: 20000 })])
assert(!!hA, 'ROUTER: DID A reaches a verified head')
assert(!!hB, 'ROUTER: DID B reaches a verified head (both members read — the fix)')
await swarm.destroy(); await router.close(); await store.close()
}
console.log('\nROUTER OK: per-peer attach reads only the first DID; DidPeerRouter reads BOTH')
for (const close of toClose) await close()
await testnet.destroy()
cleanupSync()