// SwarmDevice.isSigner — the signer election as sign() runs it, for a co-signer that is not sign().
//
// A labeler DID's LabelRoom signs labels with the same DID key the repo is signed with (member labels,
// hyper-social #80). It must sign on exactly the node that signs commits: two elections that can
// disagree is how two nodes end up signing. sign() gates on `sb.holdsSigner(undefined, netRev)` where
// netRev is the network head's rev (currency-aware: a co-owner behind the head must not hold). isSigner
// asks the same question with the same netRev.
//
// Run: bare test/swarm-device-is-signer.test.mjs
import '../lib/bare-globals.js'
import test from 'brittle'
import b4a from 'b4a'
import { SwarmDevice } from '../lib/swarm-device.js'
const DID = 'did:plc:issigner'
const KEY = (() => { const k = b4a.alloc(32); k[31] = 9; return k })()
const post = (text) => ({ $type: 'app.bsky.feed.post', text, createdAt: '2026-10-13T00:00:00.000Z' })
class MemKV {
constructor () { this.m = new Map() }
async set (k, v) { this.m.set(k, v) }
async get (k) { return this.m.has(k) ? this.m.get(k) : null }
async getNow (k) { return this.get(k) }
async del (k) { this.m.delete(k) }
async list (prefix = '') { const o = {}; for (const [k, v] of this.m) if (k.startsWith(prefix)) o[k] = v; return o }
async listNow (prefix = '') { return this.list(prefix) }
}
test('★ isSigner asks the presence election with the NETWORK head rev, as sign() does', async (t) => {
const sb = new MemKV()
const A = new SwarmDevice(sb, { did: DID, privateKey: KEY, self: 'A', firehose: true })
await A.putRecord('app.bsky.feed.post', 'r1', post('one'))
const h = await A.sign({ force: true })
const asked = []
let answer = false
sb.holdsSigner = (now, netHead) => { asked.push(netHead); return answer }
t.is(await A.isSigner(), false, 'the election says no → no')
answer = true
t.is(await A.isSigner(), true, 'the election says yes → yes')
t.alike(asked, [h.rev, h.rev], 'each time asked with the network head rev')
})
// ⛔ THE ELECTION MUST BE READABLE AS THE SIGN GATE SEES IT. 2026-10-14: loser-club's two co-owners BOTH reported
// holdsSigner:true while replicating the same base; get-state asked the election without the network head, the
// gate asks with it, and neither showed who was present or which heads were known. state() now reports both.
test('state() reports the election with and without the network head, and isSigner', async (t) => {
const sb = new MemKV()
const A = new SwarmDevice(sb, { did: DID, privateKey: KEY, self: 'A', firehose: true })
await A.putRecord('app.bsky.feed.post', 'r1', post('one'))
const h = await A.sign({ force: true })
sb.holdsSigner = (now, netHead) => netHead === null || netHead === undefined
sb.signerState = (now, netHead) => ({ self: 'A', signer: netHead ? 'B' : 'A', effective: ['A', 'B'], holds: !netHead, heads: { A: null, B: h.rev } })
const s = await A.state()
t.is(s.isSigner, false, 'isSigner: the gate (asked with the network head)')
t.is(s.election.netRev, h.rev, 'the network head rev the gate uses')
t.is(s.election.withHead.signer, 'B')
t.is(s.election.noHead.signer, 'A')
t.alike(s.election.withHead.heads, { A: null, B: h.rev }, 'with each co-owner\'s known head')
})
test('state().election carries the substrate\'s presence attach/receive counts', async (t) => {
const sb = new MemKV()
const A = new SwarmDevice(sb, { did: DID, privateKey: KEY, self: 'A', firehose: true })
sb.signerState = () => ({ self: 'A', signer: 'A', effective: ['A'], holds: true, heads: {} })
sb.presenceDiag = () => ({ attached: 3, duplicate: 1, received: 0, peers: {} })
const s = await A.state()
t.alike(s.election.presence, { attached: 3, duplicate: 1, received: 0, peers: {} })
})
test('a substrate with no election (single writer) signs — the same default as sign()', async (t) => {
const A = new SwarmDevice(new MemKV(), { did: DID, privateKey: KEY, self: 'A', firehose: true })
t.is(await A.isSigner(), true)
})
test('before any commit the net head is null', async (t) => {
const sb = new MemKV()
const asked = []
sb.holdsSigner = (now, netHead) => { asked.push(netHead); return true }
const A = new SwarmDevice(sb, { did: DID, privateKey: KEY, self: 'A', firehose: true })
await A.isSigner()
t.alike(asked, [null])
})