// label-intent / label-list — the local surface a projector uses to label (member labels, hyper-social #80).
//
// The gateway's member projector calls label-intent for each roster member (and with neg:true when one
// leaves) on the labeler DID's local control socket. It re-asserts the whole set on every roster change,
// so the verb must be idempotent: an unchanged state writes nothing (LabelRoom.ensure). A DID that is not
// a labeler answers with an error that says so, not an unknown-method error.
//
// Run: bare test/label-uds.test.mjs
import '../lib/bare-globals.js'
import test from 'brittle'
import fs from 'bare-fs'
import { join } from 'bare-path'
import { Client } from 'hyper-svc'
import { serveDeviceUds } from '../lib/serve-device-uds.js'
import { LabelRoom } from 'atproto-room/lib/label-room.js'
const SRC = 'did:plc:wqnwulql7ndcz6xmtvaovl6u'
const WJ = 'did:plc:wj62ebbusln6gxo5azo6ogzo'
const KEY = new Uint8Array(32).fill(44)
const tmp = () => { const d = '/tmp/hpp-lu-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
const device = { state: () => ({ ok: true }), membersDiag: async () => ({}), did: SRC }
function mockSb () {
const m = new Map()
let writes = 0
return {
writes: () => writes,
async set (k, v) { writes++; m.set(k, v) },
async del (k) { m.delete(k) },
async get (k) { return m.has(k) ? m.get(k) : null },
async list (prefix) { const o = {}; for (const [k, v] of m) if (k.startsWith(prefix)) o[k] = v; return o }
}
}
async function serve (t, labels) {
const uds = join(tmp(), 'c.sock')
const s = await serveDeviceUds(device, { uds, labels })
const c = await Client.connectUds(uds)
t.teardown(async () => { try { await c.close() } catch {} ; try { await s.close() } catch {} })
return c
}
test('★ label-intent is idempotent; label-list shows intents and how many are signed', async (t) => {
const sb = mockSb()
const room = new LabelRoom(sb, { src: SRC, privateKey: KEY, holds: () => true, ns: '__labelintent__' })
const c = await serve(t, room)
const a = await c.call('label-intent', { uri: WJ, val: 'loser-club-member' })
t.is(a.changed, true, 'a new label is written')
const n = sb.writes()
const b = await c.call('label-intent', { uri: WJ, val: 'loser-club-member' })
t.is(b.changed, false, 'the same label again is not')
t.is(sb.writes(), n, 'and writes nothing')
await room.tick()
const l = await c.call('label-list', {})
t.is(l.intents.length, 1)
t.alike({ uri: l.intents[0].uri, val: l.intents[0].val, neg: l.intents[0].neg }, { uri: WJ, val: 'loser-club-member', neg: false })
t.is(l.signed, 1, 'one label in the signed log')
const g = await c.call('label-intent', { uri: WJ, val: 'loser-club-member', neg: true })
t.is(g.changed, true, 'a negation is a change')
})
// ⛔ DIAGNOSE WHAT THE GATE SEES. 2026-10-14: loser-club's member labels were never re-signed with `ver`, both
// co-owners logged nothing, and get-state's holdsSigner (asked WITHOUT the network head) said stortron while the
// label gate asks WITH it. label-list now answers both questions on the device itself: would this device's label
// tick sign right now (the same holds() the tick calls), and what is actually in the signed log, ver included.
test('label-list reports the label gate and each signed label with its ver', async (t) => {
const sb = mockSb()
let gate = true
const room = new LabelRoom(sb, { src: SRC, privateKey: KEY, holds: () => gate, ns: '__labelintent__' })
const c = await serve(t, room)
await c.call('label-intent', { uri: WJ, val: 'loser-club-member' })
await room.tick()
const l = await c.call('label-list', {})
t.is(l.holds, true, 'holds: the gate the tick asks')
t.is(l.labels.length, 1)
t.alike({ uri: l.labels[0].uri, val: l.labels[0].val, ver: l.labels[0].ver, neg: l.labels[0].neg }, { uri: WJ, val: 'loser-club-member', ver: 1, neg: false })
t.ok(typeof l.labels[0].cts === 'string', 'with its cts')
t.is(l.projection, null, 'projection: null until the label room has projected into a core')
gate = false
t.is((await c.call('label-list', {})).holds, false, 'and it follows the election')
})
test('label-intent checks its input', async (t) => {
const c = await serve(t, new LabelRoom(mockSb(), { src: SRC, privateKey: KEY, holds: () => true, ns: '__labelintent__' }))
await t.exception(() => c.call('label-intent', { uri: WJ }), /val/)
})
test('a DID that is not a labeler says so', async (t) => {
const c = await serve(t, null)
await t.exception(() => c.call('label-intent', { uri: WJ, val: 'x' }), /not a labeler/)
await t.exception(() => c.call('label-list', {}), /not a labeler/)
})