// Determinism test for the IdentityRegistry apply function.
//
// Autobase requires apply to be DETERMINISTIC — it is replayed on every boot
// and every reorg, and each peer runs it independently to build its own view.
// If apply writes non-deterministic values, two peers replaying the SAME ops
// produce DIFFERENT view bytes, so their view cores and checkpoints can never
// match.
//
// identity-apply.js writes Date.now() into the view in three places:
// :437 ADD_WRITER -> { addedAt: Date.now() }
// :474 REGISTER_MIRROR_WRITERS -> { recordedAt: Date.now() }
// :428 SET_USERNAME -> user.updatedAt = Date.now()
//
// Claim under test: replaying an identical op batch twice yields different
// stored bytes. The CONTROL uses REGISTER_IDENTITY, which stores only
// op-provided fields — it must come out byte-identical, proving any
// difference in the other cases is attributable to apply itself and not to
// the harness.
import test from 'brittle'
import Corestore from 'corestore'
import Hyperbee from 'hyperbee'
import b4a from 'b4a'
import os from '#node:os'
import path from '#node:path'
import fs from '#node:fs'
import { createApplyFunction } from '../node_modules/hyper-identity/lib/auth/identity-apply.js'
import { encodeOp, OpTypes } from '../node_modules/hyper-identity/lib/auth/identity-operations.js'
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'apply-determinism-'))
let n = 0
async function freshView () {
const store = new Corestore(path.join(TMP, 'store-' + (n++)))
await store.ready()
// Raw Corestore takes { name }; the string form in identity-registry.js:77
// is autobase's view-store API, not this one.
const bee = new Hyperbee(store.get({ name: 'identity-view' }), {
extension: false, keyEncoding: 'utf-8', valueEncoding: 'json'
})
await bee.ready()
return { store, bee }
}
// A writer whose addWriter never fails, so the ADD_WRITER path completes.
const stubHost = { addWriter: async () => {}, removeWriter: async () => {} }
const node = (op) => ({ value: encodeOp(op), from: { key: b4a.alloc(32) } })
// Run the REAL apply over a batch, return the stored value at `key`.
async function applyAndRead (batch, key) {
const { store, bee } = await freshView()
const apply = createApplyFunction({})
await apply(batch, bee, stubHost)
const entry = await bee.get(key)
const value = entry ? entry.value : null
await bee.close(); await store.close()
return value
}
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
test('CONTROL: an op storing only op-provided fields replays byte-identically', async (t) => {
const identity = {
identityId: 'ident-control',
method: 'web-identity',
publicKey: 'ff'.repeat(32),
userId: 'user-control',
createdAt: 1700000000000 // fixed by the op, not by apply
}
const batch = [node({ type: OpTypes.REGISTER_IDENTITY, identity })]
const a = await applyAndRead(batch, 'identity:ident-control')
await sleep(25)
const b = await applyAndRead(batch, 'identity:ident-control')
t.ok(a && b, 'both replays stored the record')
t.alike(a, b, 'CONTROL replays byte-identical — the harness itself is deterministic')
})
test('ADD_WRITER: apply stamps Date.now() -> replays DIVERGE', async (t) => {
const writerKey = 'ab'.repeat(32)
const batch = [node({
type: OpTypes.ADD_WRITER, writerKey, role: 'writer', addedBy: 'test'
})]
const a = await applyAndRead(batch, 'writer:' + writerKey)
await sleep(25)
const b = await applyAndRead(batch, 'writer:' + writerKey)
t.ok(a && b, 'both replays stored the writer record')
t.comment('replay A addedAt=' + (a && a.addedAt) + ' replay B addedAt=' + (b && b.addedAt))
t.unlike(a, b,
'identical ADD_WRITER op produces DIFFERENT view bytes on replay — ' +
'apply is NON-DETERMINISTIC, so peers cannot converge on a shared view')
})
test('REGISTER_MIRROR_WRITERS: apply stamps Date.now() -> replays DIVERGE', async (t) => {
const devicePubkey = 'cd'.repeat(32)
const batch = [node({
type: OpTypes.REGISTER_MIRROR_WRITERS,
devicePubkey,
keys: { orgs: 'ef'.repeat(32) }
})]
const a = await applyAndRead(batch, 'mirror-writers:' + devicePubkey)
await sleep(25)
const b = await applyAndRead(batch, 'mirror-writers:' + devicePubkey)
t.ok(a && b, 'both replays stored the announcement')
t.comment('replay A recordedAt=' + (a && a.recordedAt) + ' replay B recordedAt=' + (b && b.recordedAt))
t.unlike(a, b,
'identical REGISTER_MIRROR_WRITERS op produces DIFFERENT view bytes on replay')
})