// Identity-autobase force-resync + re-admission.
//
// Asserts the recovery contract:
// 1. the re-joined peer uses a DIFFERENT local writer key (the keypair is the
// lever for this base — it pins keyPair, so a fresh corestore alone is not
// enough, unlike per-repo trackers)
// 2. it can READ existing history while still read-only
// 3. it is READ-ONLY until an indexer admits it (autobase golden rule:
// never write content before admission)
// 4. after admission it is writable, has a non-empty indexer set, and its
// writes actually linearize — visible on the ORIGIN, not just locally
// 5. the resync record round-trips, so a restart reopens the healed store
// rather than reverting to the stuck one
import test from 'brittle'
import Corestore from 'corestore'
import b4a from 'b4a'
import fs from '#node:fs'
import os from '#node:os'
import path from '#node:path'
import crypto from 'hypercore-crypto'
import { IdentityRegistry } from 'hyper-identity/auth/identity-registry'
import {
forceResyncIdentity,
loadIdentityResync,
openResyncedIdentity
} from '../lib/services/identity-resync.js'
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
function wire (a, b) {
const s1 = a.base.replicate(true)
const s2 = b.base.replicate(false)
s1.pipe(s2).pipe(s1)
return () => { try { s1.destroy(); s2.destroy() } catch {} }
}
async function tmpdir (name) {
const dir = path.join(os.tmpdir(), 'pgv3-identity-resync-' + name + '-' + crypto.randomBytes(6).toString('hex'))
fs.mkdirSync(dir, { recursive: true })
return dir
}
test('forceResyncIdentity: clean re-join + re-admission restores a working writer', async (t) => {
const originDir = await tmpdir('origin')
const peerDir = await tmpdir('peer')
// ── origin with real history ──
const originStore = new Corestore(path.join(originDir, 'store'))
await originStore.ready()
const origin = new IdentityRegistry(originStore.namespace('identity'), {})
await origin.ready()
const bootstrapKey = b4a.toString(origin.base.key, 'hex')
const seeded = await origin.createUser({ name: 'pre-existing-user' })
// ── the peer force-resyncs: fresh corestore + freshly minted writer keypair ──
const { registry: peer, store: peerStore, rec, localKeyHex } =
await forceResyncIdentity({ storage: peerDir, bootstrapKey })
t.ok(localKeyHex, 'resynced peer has a local writer key')
t.is(rec.gen, 1, 'first resync is generation 1')
const un = wire(origin, peer)
await sleep(1500)
try { await peer.base.update() } catch {}
// 2 + 3 — reads history, but must NOT be writable yet
t.absent(peer.base.writable, 'resynced peer is READ-ONLY before admission')
const readBeforeAdmit = await peer.getUser(seeded.userId)
t.ok(readBeforeAdmit, 'read-only peer can READ existing history')
// 4 — re-admission by the indexer
await origin.addWriter(localKeyHex, { role: 'writer', addedBy: 'test-origin' })
await origin.base.update()
await sleep(3000)
await peer.base.update()
t.ok(peer.base.writable, 'peer is WRITABLE after the indexer admits it')
const lz = peer.base.linearizer || (peer.base._applyState && peer.base._applyState.linearizer)
t.ok(lz && lz.indexers.length > 0, 'peer has a NON-EMPTY indexer set after admission')
// the payload: does a write actually persist and linearize?
const written = await peer.createUser({ name: 'written-through-healed-peer' })
await sleep(2500)
await peer.base.update()
await origin.base.update()
t.ok(await peer.getUser(written.userId), 'healed peer reads back its own write')
t.ok(await origin.getUser(written.userId), 'the ORIGIN sees the write — it really linearized')
un()
await peer.close(); await peerStore.close()
await origin.close(); await originStore.close()
})
test('the resync record survives a restart (the heal is not reverted on boot)', async (t) => {
const originDir = await tmpdir('origin2')
const peerDir = await tmpdir('peer2')
const originStore = new Corestore(path.join(originDir, 'store'))
await originStore.ready()
const origin = new IdentityRegistry(originStore.namespace('identity'), {})
await origin.ready()
const bootstrapKey = b4a.toString(origin.base.key, 'hex')
const first = await forceResyncIdentity({ storage: peerDir, bootstrapKey })
const firstLocal = first.localKeyHex
await first.registry.close(); await first.store.close()
// simulate a daemon restart: read the persisted record and reopen from it
const rec = loadIdentityResync(peerDir)
t.ok(rec, 'resync record persisted')
t.is(rec.gen, 1, 'record carries the generation')
const reopened = await openResyncedIdentity({ storage: peerDir, bootstrapKey, rec })
const reopenedLocal = b4a.toString(reopened.registry.base.local.key, 'hex')
t.is(reopenedLocal, firstLocal,
'reopening from the record yields the SAME writer key — the heal survives restart')
await reopened.registry.close(); await reopened.store.close()
await origin.close(); await originStore.close()
})
test('a second resync mints a NEW generation and a NEW writer key', async (t) => {
const originDir = await tmpdir('origin3')
const peerDir = await tmpdir('peer3')
const originStore = new Corestore(path.join(originDir, 'store'))
await originStore.ready()
const origin = new IdentityRegistry(originStore.namespace('identity'), {})
await origin.ready()
const bootstrapKey = b4a.toString(origin.base.key, 'hex')
const one = await forceResyncIdentity({ storage: peerDir, bootstrapKey })
await one.registry.close(); await one.store.close()
const two = await forceResyncIdentity({ storage: peerDir, bootstrapKey })
t.is(two.rec.gen, 2, 'generation increments')
t.not(two.localKeyHex, one.localKeyHex, 'second resync uses a DIFFERENT writer key')
await two.registry.close(); await two.store.close()
await origin.close(); await originStore.close()
})