// The FULL loser-club attempt-2 topology: a resync-flagged joiner on the SECOND DID of a multi-DID daemon.
//
// test/joiner-resync-2node.test.js proved a resync side-store joiner converges when it is the ONLY hosted
// DID. Production is not that shape. stortron hosts wj62 (primary, listed FIRST) and loser-club second, so
// by the time loser-club boots the daemon already has open connections to the peer — and its side store
// must be attached to those EXISTING connections by the catch-up loop in bootMultiDevice:
//
// const node = await bootSwarmDevice(...)
// swarm.join(node.sb.base.discoveryKey, ...)
// if (node._sideStore) for (const conn of swarm.connections) node._sideStore.replicate(conn)
//
// A single-DID test never reaches that loop — there are no connections yet when the only DID boots, so the
// forward path in the connection handler covers everything. Here both DIDs boot in ONE call with the
// primary first (a DID cannot be added to a running daemon; production edits the config and restarts,
// which is exactly this), so the room boots into a daemon that has already joined another topic.
//
// Whether a connection is actually open by then is a real race — DHT lookup vs the second DID's boot — so
// this test does not assert which path attached the side store. It asserts the OUTCOME production did not
// get: convergence. A pass narrows the cause; a failure hands over a live repro.
//
// hyper-social#16 P1. Run: bare test/joiner-resync-multidid.test.js
import '../lib/bare-globals.js'
import test from 'brittle'
import fs from 'bare-fs'
import b4a from 'b4a'
import createTestnet from '@hyperswarm/testnet'
import { bootMultiDevice, provisionDevice, resyncDid } from '../lib/device-node.js'
import { deviceKeetKeyPair } from 'atproto-room/lib/membership.js'
// Two ROOTS => two distinct DIDs (a DID derives from its root identity).
const ROOT_ROOM = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art'
const ROOT_OTHER = 'legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth title'
const DEV_A = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo vote'
const DEV_B = 'legal winner thank year wave sausage worth useful legal winner thank year wave sausage worth useful legal will'
const DEV_C = 'letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic avoid letter advice cage absurd amount doctor acoustic bless'
const DEV_D = 'letter advice cage absurd amount doctor acoustic avoid letter advice cage above'
const tmp = () => { const d = '/tmp/hpp-mdid-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
const post = (rk, text) => ({ action: 'create', collection: 'app.bsky.feed.post', rkey: rk, record: { $type: 'app.bsky.feed.post', text, createdAt: '2026-07-27T00:00:00.000Z' } })
const keys = async (n) => (await n.device.state()).records.map((r) => r.collection + '/' + r.rkey).sort()
const waitFor = async (fn, ms = 60000) => { const end = Date.now() + ms; while (Date.now() < end) { if (await fn()) return true; await sleep(500) } return false }
async function bundle (root, devMnem, handle) {
const dk = await deviceKeetKeyPair(devMnem)
const b = await provisionDevice({ rootMnemonic: root, devicePublicKey: dk.publicKey, handle, pdsUrl: 'https://pds.local' })
return { entry: { deviceMnemonic: devMnem, did: b.did, signingKey: b.signingKey, deviceProof: b.deviceProof, rootIdentityKey: b.rootIdentityKey, handle }, keetPub: b4a.toString(dk.publicKey, 'hex'), did: b.did }
}
// Incumbent = HIGHER key so the joiner sorts below it — the role hazard the ceremony hit in production.
async function pair (root, handle) {
const a = await bundle(root, DEV_A, handle)
const b = await bundle(root, DEV_B, handle)
const joinerIsA = a.keetPub < b.keetPub
return { INC: joinerIsA ? b : a, JOIN: joinerIsA ? a : b, did: a.did }
}
test('multi-DID: a resync joiner on the SECOND DID converges after connections are already open', async (t) => {
const testnet = await createTestnet(3)
const swarmOpts = { bootstrap: testnet.bootstrap }
const room = await pair(ROOT_ROOM, 'club.local') // the loser-club analog
const oInc = await bundle(ROOT_OTHER, DEV_C, 'other.local') // the wj62 analog, incumbent side
const oJoin = await bundle(ROOT_OTHER, DEV_D, 'other.local') // ...and joiner side
const roomMembers = [{ key: room.INC.keetPub, role: 1 }, { key: room.JOIN.keetPub, role: 1 }]
const otherMembers = [{ key: oInc.keetPub, role: 1 }, { key: oJoin.keetPub, role: 1 }]
t.not(room.did, oInc.did, 'precondition: two DISTINCT hosted DIDs')
// ── Incumbent daemon: hosts BOTH DIDs, the "primary" one FIRST (production's config order).
const sInc = tmp()
const nInc = await bootMultiDevice({
storage: sInc,
dids: [
{ ...oInc.entry, members: otherMembers, forceCreator: true, primary: true },
{ ...room.INC.entry, members: roomMembers, forceCreator: true }
],
swarmOpts,
signIntervalMs: 1000
})
t.teardown(async () => { try { await nInc.close() } catch {} ; await testnet.destroy() })
const inc = nInc.byDid.get(room.did)
await inc.device.applyWrite(post('3aaa', 'incumbent one'))
await inc.device.applyWrite(post('3bbb', 'incumbent two'))
await waitFor(async () => (await inc.device.state()).recordCount >= 2)
const incKeys = await keys(inc)
// ── Joiner daemon: BOTH DIDs in one boot, primary FIRST — production's config order. The DIDs boot
// sequentially inside bootMultiDevice, so by the time the room (second) boots the daemon has already
// joined the first DID's topic and may already hold open connections to the peer. When it does, the
// room's side store is attached by the CATCH-UP loop rather than by the connection handler — the half a
// single-DID test can never reach. (A DID cannot be added to a running daemon: production edits the
// config and restarts, which is exactly this.)
const sJoin = tmp()
const { gen } = resyncDid(sJoin, room.did)
t.is(gen, 1, 'room flagged for resync BEFORE boot — the production attempt-2 state')
const nJoin = await bootMultiDevice({
storage: sJoin,
dids: [
{ ...oJoin.entry, members: otherMembers, joiner: true, primary: true },
{ ...room.JOIN.entry, members: roomMembers, joiner: true }
],
swarmOpts,
signIntervalMs: 1000
})
t.teardown(async () => { try { await nJoin.close() } catch {} })
t.is(nJoin.nodes.length, 2, 'the joiner daemon hosts BOTH DIDs, as production does')
const join = nJoin.byDid.get(room.did)
t.ok(join && join._sideStore, 'the room opened from a side store (resync), as production did')
t.ok(nJoin.byDid.get(oInc.did), 'and the other DID is hosted alongside it on the same swarm + store')
const converged = await waitFor(async () => {
const s = await join.device.state()
return s.records.some((r) => r.rkey === '3aaa') && s.records.some((r) => r.rkey === '3bbb')
}, 90000)
const d = await join.device.membersDiag()
const st = await join.device.state()
t.ok(converged, `the side-store room CONVERGED over pre-existing connections (base=${d.indexedLength}/${d.baseLength}, records=${st.recordCount})`)
t.alike(await keys(join), incKeys, 'and its record set is EXACTLY the incumbent\'s — no rival line')
})