// Daemon-level proof: org + repo join + write-enrollment happen over the
// swarm via blind-pairing through the rewired RPCs (create-org/repo-invite +
// join-org/repo). No HTTP, no enroll-writer RPC. Two daemons on DIFFERENT
// identity topics — they rendezvous purely via the per-room pairing topic.
import test from 'brittle'
import process from '#node:process'
import { mkdtempSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import b4a from 'b4a'
import createTestnet from '@hyperswarm/testnet'
import { startPearGit } from '../index.js'
let n = 0
const tmpStorage = () => mkdtempSync(join(tmpdir(), 'pgv3-dp-' + (n++) + '-'))
const tmpSock = () => join(mkdtempSync(join(tmpdir(), 'pgv3-dp-sock-' + (n++) + '-')), 'svc.sock')
async function waitFor (pred, ms = 30000) {
const t0 = Date.now()
while (Date.now() - t0 < ms) {
try { if (await pred()) return true } catch {}
await new Promise((r) => setTimeout(r, 100))
}
return false
}
function issuesOf (list) {
return (list && list.issues) ? list.issues : (Array.isArray(list) ? list : [])
}
test('org + repo join over the swarm via daemon RPCs (blind-pairing, no HTTP)', async (t) => {
process.env.PGV3_USE_RAW_IDENTITY = '1'
const testnet = await createTestnet(3, t.teardown)
const A = await startPearGit({
storage: tmpStorage(), uds: tmpSock(), http: { port: 0 },
swarm: { swarm: { bootstrap: testnet.bootstrap } }, deploy: false, sync: false
})
t.teardown(async () => { try { await A.close() } catch {} })
const B = await startPearGit({
storage: tmpStorage(), uds: tmpSock(), http: { port: 0 },
swarm: { swarm: { bootstrap: testnet.bootstrap } }, deploy: false, sync: false
})
t.teardown(async () => { try { await B.close() } catch {} })
t.not(
b4a.toString(A.svc.swarm.topic, 'hex'),
b4a.toString(B.svc.swarm.topic, 'hex'),
'daemons are on DIFFERENT identity topics'
)
const { Client } = await import('hyper-svc')
const ca = await Client.connectUds(A.svc.uds.path)
const cb = await Client.connectUds(B.svc.uds.path)
t.teardown(async () => { try { await ca.close() } catch {}; try { await cb.close() } catch {} })
// ── ORG ──────────────────────────────────────────────────────
await ca.call('create-org', { name: 'palpha', type: 'group', displayName: 'PAlpha' })
const { invitation: orgInv } = await ca.call('create-org-invite', { name: 'palpha', role: 'contributor' })
t.ok(typeof orgInv === 'string' && orgInv.startsWith('roompair1:'), 'org pairing invite minted (RPC)')
const oj = await cb.call('join-org', { invitation: orgInv })
t.is(oj.name, 'palpha', 'B joined the org via join-org RPC')
const orgWritable = await waitFor(() => { const r = B.orgs.getOrgRoom('palpha'); return r && r.writable })
t.ok(orgWritable, 'B became an org writer via pairing — over the swarm, no HTTP')
await cb.call('create-issue', { org: 'palpha', title: 'org issue from B', author: 'b' })
const aSeesOrg = await waitFor(async () => {
const arr = issuesOf(await ca.call('list-issues', { org: 'palpha' }))
return arr.some((x) => x && x.title === 'org issue from B')
})
t.ok(aSeesOrg, 'A sees the B-authored org issue (bidirectional)')
// ── REPO ─────────────────────────────────────────────────────
await A.issueRegistry.getForRepo('prepo') // mint the tracker on the owner
const { invitation: repoInv } = await ca.call('create-repo-invite', { name: 'prepo', role: 'contributor' })
t.ok(typeof repoInv === 'string' && repoInv.startsWith('roompair1:'), 'repo pairing invite minted (RPC)')
const rj = await cb.call('join-repo', { invitation: repoInv })
t.is(rj.name, 'prepo', 'B joined the repo via join-repo RPC')
const repoWritable = await waitFor(async () => {
const tr = await B.issueRegistry.getForRepo('prepo').catch(() => null)
return tr && tr.writable
})
t.ok(repoWritable, 'B became a repo writer via pairing — over the swarm, no HTTP')
await cb.call('create-issue', { repo: 'prepo', title: 'repo issue from B', author: 'b' })
const aSeesRepo = await waitFor(async () => {
const arr = issuesOf(await ca.call('list-issues', { repo: 'prepo' }))
return arr.some((x) => x && x.title === 'repo issue from B')
})
t.ok(aSeesRepo, 'A sees the B-authored repo issue (bidirectional)')
})