// Adopt-into-swarm continuity (the wj62 path). An EXISTING single-tenant repo (records + head,
// signed by a raw #atproto key) is imported into a swarm that shares that key + has a SEPARATE keet
// membership root. The swarm must CONTINUE the chain — next commit.prev = the adopted commit,
// prevData = the adopted data root — NOT re-author (which would break federation). Deterministic
// (network:false). This is what makes converting wj62 to a swarm safe.
import '../lib/bare-globals.js'
import test from 'brittle'
import crypto from 'hypercore-crypto'
import fs from 'bare-fs'
import b4a from 'b4a'
import { StateBaseV3 } from 'atproto-room/lib/state-base.js'
import { SwarmDevice } from '../lib/swarm-device.js'
import { makeAttest, makeVerifyAttest, attestDeviceChain, deviceKeetKeyPair, rootIdentityPublicKey } from 'atproto-room/lib/membership.js'
import { createRepo, readCar, loadCommit, listRecords } from 'atproto-repo/lib/repo.js'
import { verifyCommit } from 'atproto-repo/lib/commit.js'
import { deriveAtprotoKey } from 'atproto-identity'
import { decodeDidKey } from 'atproto-identity/lib/did-key.js'
const SIGN_SRC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' // stand-in for wj62's raw signing key
const KEET_ROOT = 'legal winner thank year wave sausage worth useful legal winner thank yellow' // NEW keet membership root
const DEV = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' // this device's identity
const DID = 'did:plc:adoptwj6222222222222222'
const POST = 'app.bsky.feed.post'
const signKey = deriveAtprotoKey(SIGN_SRC)
const { pubkey } = decodeDidKey(signKey.didKey)
const REV0 = '3mmwu7vak2000'; const RK1 = '3mmwu7vak2001'; const RK2 = '3mmwu7vak2002'; const RK3 = '3mmwu7vak2003'
const tmp = () => { const d = '/tmp/hpp-adopt-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
test('the swarm continues an adopted repo chain (prev = adopted commit), not re-authors it', async (t) => {
// 1. an EXISTING repo (stand-in for wj62's), signed by the shared key
const existingRecords = [
{ collection: POST, rkey: RK1, record: { $type: POST, text: 'wj62 post 1', createdAt: '2026-07-01T00:00:00.000Z' } },
{ collection: POST, rkey: RK2, record: { $type: POST, text: 'wj62 post 2', createdAt: '2026-07-01T00:00:01.000Z' } }
]
const existing = await createRepo({ records: existingRecords, did: DID, rev: REV0, privateKey: signKey.privateKey })
const head = { rev: REV0, commitCid: existing.commitCid, dataCid: existing.dataCid, signedCommit: loadCommit(await readCar(existing.carBytes)).value }
// 2. a swarm device for the SAME DID + signing key, with a NEW keet membership root
const rootPub = await rootIdentityPublicKey(KEET_ROOT)
const dev = await deviceKeetKeyPair(DEV); const proof = await attestDeviceChain({ rootMnemonic: KEET_ROOT, devicePublicKey: dev.publicKey })
const members = [{ key: b4a.toString(dev.publicKey, 'hex'), role: 1 }]
const A = new StateBaseV3(tmp(), crypto.randomBytes(32), {
roomSeed: crypto.randomBytes(32), encryptionKey: crypto.randomBytes(32), network: false,
selfKey: dev.publicKey, attest: makeAttest(dev, proof), verifyAttest: makeVerifyAttest(rootPub),
getMembers: async () => members
})
await A.ready(); await A.projectMembers(members)
const device = new SwarmDevice(A, { did: DID, privateKey: signKey.privateKey, self: b4a.toString(dev.publicKey, 'hex'), firehose: true })
t.teardown(async () => { try { await A.close() } catch {} })
// 3. adopt — the continuity check runs inside (imported records must reproduce the data root)
const adopted = await device.adopt(existingRecords, head)
t.ok(adopted.commitCid.equals(existing.commitCid), 'adopted head == the existing commit')
t.alike((await device.repo.listRecords()).map((r) => r.rkey).sort(), [RK1, RK2].sort(), 'existing records imported into the swarm')
// 4. a NEW swarm write continues the chain
await device.putRecord(POST, RK3, { $type: POST, text: 'first swarm-signed post', createdAt: '2026-07-05T00:00:00.000Z' })
await device.claimLease()
const nh = await device.sign()
t.ok(nh && nh.signedCommit, 'swarm signed a continuing commit')
t.ok(nh.signedCommit.prev && nh.signedCommit.prev.equals(existing.commitCid), 'new commit.prev == the ADOPTED commit (continues, not re-authors)')
t.ok(verifyCommit(nh.signedCommit, pubkey), 'continuing commit verifies against the DID signing key')
const fh = await device.firehoseLog()
t.is(fh[fh.length - 1].prevData, existing.dataCid.toString(), 'firehose #commit prevData == adopted data root (relay-invertible continuity)')
})
test('continuity guard: an INCOMPLETE import is rejected (would re-author)', async (t) => {
const existingRecords = [
{ collection: POST, rkey: RK1, record: { $type: POST, text: 'wj62 post 1', createdAt: '2026-07-01T00:00:00.000Z' } },
{ collection: POST, rkey: RK2, record: { $type: POST, text: 'wj62 post 2', createdAt: '2026-07-01T00:00:01.000Z' } }
]
const existing = await createRepo({ records: existingRecords, did: DID, rev: REV0, privateKey: signKey.privateKey })
const head = { rev: REV0, commitCid: existing.commitCid, dataCid: existing.dataCid, signedCommit: loadCommit(await readCar(existing.carBytes)).value }
const rootPub = await rootIdentityPublicKey(KEET_ROOT)
const dev = await deviceKeetKeyPair(DEV); const proof = await attestDeviceChain({ rootMnemonic: KEET_ROOT, devicePublicKey: dev.publicKey })
const members = [{ key: b4a.toString(dev.publicKey, 'hex'), role: 1 }]
const A = new StateBaseV3(tmp(), crypto.randomBytes(32), {
roomSeed: crypto.randomBytes(32), encryptionKey: crypto.randomBytes(32), network: false,
selfKey: dev.publicKey, attest: makeAttest(dev, proof), verifyAttest: makeVerifyAttest(rootPub),
getMembers: async () => members
})
await A.ready(); await A.projectMembers(members)
const device = new SwarmDevice(A, { did: DID, privateKey: signKey.privateKey, self: b4a.toString(dev.publicKey, 'hex'), firehose: true })
t.teardown(async () => { try { await A.close() } catch {} })
// only ONE of the two records → data root won't match → MUST throw (prevents a re-authoring cutover)
await t.exception(() => device.adopt([existingRecords[0]], head), /re-author|reproduce/, 'incomplete import rejected on a fresh device')
})