OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-base / test / governance.test.mjs

77 lines · javascript ref:
// GOVERNANCE — proof-gated multi-writer admission (block-until-admitted golden rule) + roster
// projection. A joiner proves membership, is admitted as a plain writer, writes a record, and both
// co-owners converge to the IDENTICAL projected head. A non-member is rejected (its write THROWS
// rather than being silently rolled back).

import test from 'brittle'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import crypto from 'hypercore-crypto'
import { deriveAtprotoKey, createAtprotoIdentity } from 'atproto-identity'
import { AtprotoBase } from '../lib/base.js'

const MNEMONIC = 'test test test test test test test test test test test junk'
const ID = createAtprotoIdentity(MNEMONIC, { handle: 'x.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
const KEY = deriveAtprotoKey(MNEMONIC)
const post = (text) => ({ $type: 'app.bsky.feed.post', text, createdAt: '2026-07-23T00:00:00.000Z', langs: ['en'] })
const hex = (b) => b.toString('hex')
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))

function mk (opts) {
  const dir = mkdtempSync(join(tmpdir(), 'gov-'))
  const base = new AtprotoBase({ path: dir, did: ID.did, privateKey: KEY.privateKey, ackInterval: 100, ...opts })
  return { base, dir, close: async () => { await base.close(); rmSync(dir, { recursive: true, force: true }) } }
}
const pipe = (a, b) => { const s1 = a.base.base.replicate(true); const s2 = b.base.base.replicate(false); s1.pipe(s2).pipe(s1); return () => { try { s1.destroy() } catch {} ; try { s2.destroy() } catch {} } }
async function settle (...ws) { for (let i = 0; i < 15; i++) { for (const w of ws) { try { await w.base.base.update() } catch {} } await sleep(60) } }

test('governance: a member joiner is admitted (block-until-admitted) → writes → identical head', async (t) => {
  const creatorId = crypto.keyPair()
  const joinerId = crypto.keyPair()
  const roster = [{ key: hex(creatorId.publicKey), role: 1 }, { key: hex(joinerId.publicKey), role: 3 }]

  const C = mk({ identity: creatorId, members: roster })
  await C.base.ready()
  const J = mk({ bootstrap: C.base.base.key, identity: joinerId })
  await J.base.ready()
  const unpipe = pipe(C, J)
  t.teardown(async () => { unpipe(); await C.close(); await J.close() })

  await C.base.projectMembers(roster) // land __members__ before the joiner rings
  await settle(C, J)

  // Joiner writes — ensureWritable rings the CONTENT-FREE doorbell and blocks until admitted.
  await J.base.putRecord('app.bsky.feed.post', '3joiner', post('hello from the joiner'))
  t.ok(J.base.writable, 'joiner was admitted as a writer (block-until-admitted)')

  await settle(C, J)
  await C.base.settled()
  const recsC = await C.base.indexedRecords()
  t.ok(recsC.find((r) => r.key.endsWith('3joiner')), "joiner's record replicated into the creator's shared view")

  await C.base.settled(); await J.base.settled()
  const hC = await C.base.head()
  const hJ = await J.base.head()
  t.is(hC.commitCid.toString(), hJ.commitCid.toString(), 'identical projected head across co-owners after admission (deterministic, no signer election)')
})

test('governance: a NON-member is rejected — write throws, never silently rolled back', async (t) => {
  const creatorId = crypto.keyPair()
  const outsiderId = crypto.keyPair() // NOT in the roster
  const roster = [{ key: hex(creatorId.publicKey), role: 1 }]

  const C = mk({ identity: creatorId, members: roster })
  await C.base.ready()
  const X = mk({ bootstrap: C.base.base.key, identity: outsiderId })
  await X.base.ready()
  const unpipe = pipe(C, X)
  t.teardown(async () => { unpipe(); await C.close(); await X.close() })

  await C.base.projectMembers(roster)
  await settle(C, X)

  await t.exception(() => X.base.ensureWritable({ timeoutMs: 3000 }), 'non-member ensureWritable THROWS (not admitted) rather than allowing a silent-loss write')
  t.absent(X.base.writable, 'outsider never became writable')
})