// Proves the admit-dedup guard: a member attaches its `admit` proof to EVERY
// optimistic write, so apply used to re-run addWriter/ackWriter (+log) once per
// op. The _admitted Set must collapse that to ONE addWriter per writer, while
// still rejecting an unverified proof (no admission, no set application).
import { StateBaseV3 } from '../lib/state-base-v3.js'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import fs from 'bare-fs'
import path from 'bare-path'
import os from 'bare-os'
const ROOT = path.join(os.tmpdir(), 'sbv3-admit-dedup-' + Bare.pid)
try { fs.rmSync(ROOT, { recursive: true, force: true }) } catch {}
const roomKey = crypto.randomBytes(32)
const roomSeed = crypto.randomBytes(32)
const kpA = crypto.keyPair() // admin → elected creator
const kpB = crypto.keyPair() // peer member (admitted optimistically)
const kpC = crypto.keyPair() // outsider (NOT a member → rejected, never admitted)
const hex = (b) => b4a.toString(b, 'hex')
const members = [
{ key: hex(kpA.publicKey), role: 1 },
{ key: hex(kpB.publicKey), role: 3 }
]
const baseOpts = (kp) => ({ roomSeed, encryptionKey: roomSeed, network: false, selfKeyPair: kp, selfKey: kp.publicKey, getMembers: async () => members })
let pass = 0; let fail = 0
const ok = (c, m) => { if (c) { pass++; console.log(' ok ' + m) } else { fail++; console.log(' FAIL ' + m) } }
async function main () {
const A = new StateBaseV3(path.join(ROOT, 'A'), roomKey, baseOpts(kpA))
await A.ready()
ok(A.writable, 'A (admin) elected creator → writable')
await A.projectMembers(members)
const B = new StateBaseV3(path.join(ROOT, 'B'), roomKey, baseOpts(kpB))
await B.ready()
const admitB = await B._buildAdmissionProof()
ok(!!admitB, 'B built an admission proof')
// Counting stub for the autobase writer ops; tx = A's view (has __members__).
let addWriterCalls = 0; let ackCalls = 0
const ctx = {
node: { from: { key: B.base.local.key } },
tx: A.base.view,
base: {
addWriter: async () => { addWriterCalls++ },
ackWriter: async () => { ackCalls++ }
}
}
// rec has no `key` → admit block runs, then _applySet returns before the data
// insert (keeps the test focused on admission, no writable-tx needed).
const recB = { admit: admitB }
// Simulate the peer's repeated admit-carrying ops.
for (let i = 0; i < 5; i++) await A._applySet(recB, ctx)
ok(addWriterCalls === 1, '1. addWriter called EXACTLY once across 5 admit ops (got ' + addWriterCalls + ')')
ok(ackCalls === 1, '2. ackWriter called EXACTLY once (got ' + ackCalls + ')')
ok(A._admitted.has(hex(B.base.local.key)), '3. B recorded in _admitted set')
// Outsider C: valid sig but NOT a member → role null → never admitted, set never applied.
const cLocal = crypto.randomBytes(32)
const sigC = crypto.sign(b4a.concat([cLocal, A.base.key]), kpC.secretKey)
const admitC = JSON.stringify({ identity: hex(kpC.publicKey), proof: hex(sigC) })
const ctxC = { node: { from: { key: cLocal } }, tx: A.base.view, base: ctx.base }
const before = addWriterCalls
await A._applySet({ admit: admitC }, ctxC)
ok(addWriterCalls === before, '4. outsider C did NOT trigger addWriter (still ' + addWriterCalls + ')')
ok(!A._admitted.has(hex(cLocal)), '5. outsider C NOT recorded in _admitted (rejected)')
await A.close(); await B.close()
try { fs.rmSync(ROOT, { recursive: true, force: true }) } catch {}
console.log('\n[probe] pass=' + pass + ' fail=' + fail)
Bare.exit(fail ? 1 : 0)
}
main().catch((e) => { console.error('[probe] THREW:', e && e.stack ? e.stack : e); Bare.exit(2) })