// bootMultiDevice — the multi-DID (multi-tenant) hpp daemon. Proves ONE daemon serves N DIDs from
// ONE shared Corestore (one lock, per-DID namespace), with per-DID control/membrane surfaces and a
// DID-manager (AccountRegistry). The StateBase multi-tenancy + admission foundation is proven in
// atproto-room/test/multi-did.test.js; this proves the DAEMON wiring end-to-end.
import '../lib/bare-globals.js'
import test from 'brittle'
import fs from 'bare-fs'
import { join } from 'bare-path'
import createTestnet from '@hyperswarm/testnet'
import { bootMultiDevice, provisionDevice } from '../lib/device-node.js'
import { deviceKeetKeyPair } from 'atproto-room/lib/membership.js'
const tmp = () => { const d = '/tmp/hpp-mdd-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
// Provision a self-custody DID bundle (root-side), as index.js/config supplies to the daemon.
async function didBundle (rootMnem, devMnem, handle) {
const dk = await deviceKeetKeyPair(devMnem)
const b = await provisionDevice({ rootMnemonic: rootMnem, devicePublicKey: dk.publicKey, handle, pdsUrl: 'https://pds.local' })
return { deviceMnemonic: devMnem, did: b.did, signingKey: b.signingKey, deviceProof: b.deviceProof, rootIdentityKey: b.rootIdentityKey, handle }
}
test('bootMultiDevice: one daemon serves 2 DIDs on ONE store — isolated, one lock, DID-manager', { timeout: 120000 }, async (t) => {
const testnet = await createTestnet(3)
const storage = tmp()
// two DISTINCT DIDs (distinct root mnemonics), each self-owned (single device = its own creator)
const A = await didBundle('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', 'legal winner thank year wave sausage worth useful legal winner thank yellow', 'a.local')
const B = await didBundle('zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong', 'letter advice cage absurd amount doctor acoustic avoid letter advice cage above', 'b.local')
t.not(A.did, B.did, 'two distinct DIDs')
const daemon = await bootMultiDevice({ storage, dids: [A, B], swarmOpts: { bootstrap: testnet.bootstrap }, signIntervalMs: 0 })
t.teardown(async () => { try { await daemon.close() } catch {} ; await testnet.destroy() })
t.is(daemon.nodes.length, 2, 'daemon booted 2 DIDs')
t.ok(daemon.byDid.get(A.did).sb.base.writable, 'DID A writable (its own creator)')
t.ok(daemon.byDid.get(B.did).sb.base.writable, 'DID B writable (its own creator)')
// DID-manager lists both
const listed = []; for await (const rec of daemon.registry.list()) listed.push(rec.did)
t.ok(listed.includes(A.did) && listed.includes(B.did), 'AccountRegistry (DID-manager) lists both DIDs')
// isolation: a write to A must NOT appear in B
await daemon.byDid.get(A.did).device.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey: 'a-only', record: { $type: 'app.bsky.feed.post', text: 'A only', createdAt: new Date().toISOString() } })
const aState = await daemon.byDid.get(A.did).device.state()
const bState = await daemon.byDid.get(B.did).device.state()
t.is(aState.did, A.did, 'node A serves DID A')
t.is(bState.did, B.did, 'node B serves DID B')
t.ok(aState.records.some(r => r.rkey === 'a-only'), 'DID A holds its own record')
t.absent(bState.records.some(r => r.rkey === 'a-only'), 'DID B does NOT have A\'s record (isolation)')
// ONE storage lock for BOTH DIDs (the multi-tenant win)
const locks = fs.readdirSync(join(storage, 'store')).filter(f => /CORESTORE/i.test(f))
t.is(locks.length, 1, 'exactly ONE CORESTORE lock for both DIDs')
// per-DID control surfaces (UDS) exist, one dir per DID
t.ok(fs.existsSync(join(storage, 'dids')), 'per-DID control dir created')
t.is(fs.readdirSync(join(storage, 'dids')).length, 2, 'exactly 2 per-DID control dirs')
})