// diag — one read-only JSON blob per hosted DID, over each DID's owner-gated control socket.
//
// Inspecting a running daemon used to mean ssh + a heredoc + a throwaway .mjs with absolute-path imports
// into the installed tree; a whole working day went into that on 2026-07-27 (and one heredoc died because
// a paste indented the closing EOF). The daemon already answered get-state / members-diag / pointer-diag —
// nothing collected them. These tests pin the properties that make the collected blob trustworthy:
//
// 1. all three sections arrive for a hosted DID, and the summary rolls up the fields an operator scans
// first (rev, writable, amCreator/forcedRole, stale/missing).
// 2. the per-DID socket path is resolved the way the daemon lays it out — primary DID on the FLAT
// control.sock, every other DID under dids/<did16>/. Getting this wrong looks exactly like "the
// daemon isn't running", which is the most expensive way to be wrong here.
// 3. ONE failure never eats the report: an unreachable DID, or a single op that throws, still leaves
// every other DID and every other section reported — a node with a wedged pointer store must still
// be able to tell you its state and roster, because that is usually what says WHY it is wedged.
//
// Run: bare test/diag.test.js
import '../lib/bare-globals.js'
import test from 'brittle'
import fs from 'bare-fs'
import { join } from 'bare-path'
import { serveDeviceUds } from '../lib/serve-device-uds.js'
import { collectLocalDiag, summarize } from '../lib/diag.js'
import { did16For } from '../lib/control-socket.js'
const PRIMARY = 'did:plc:diagprimary00000000000000'
const SECOND = 'did:plc:diagsecond000000000000000'
const tmp = () => { const d = '/tmp/hpp-diag-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
// The three reads a real SwarmDevice answers. These shapes mirror lib/swarm-device.js state() and
// membersDiag() FIELD FOR FIELD — an invented stub shape is not coverage: the first version of this stub
// returned `{rev, records}`, the summary was written against it, and running the real thing printed
// `rev: null` with `records` as the key LIST. The stub is only worth something if it lies the same way
// the daemon does.
function stubDevice (did, { stale = 0, amCreator = true, forcedRole = null, throwOn = null } = {}) {
return {
did,
async state () {
if (throwOn === 'get-state') throw new Error('state exploded')
return {
did,
self: 'peerA',
holdsLease: false, holdsSigner: true,
leaseHolder: 'peerA',
leaseEpoch: 3,
indexers: 1,
writable: true,
head: { rev: '3ke6kgap4u222', commitCid: 'bafyreiabc' },
recordCount: 7,
records: [{ collection: 'app.bsky.feed.post', rkey: 'a' }]
}
},
async membersDiag () {
if (throwOn === 'members-diag') throw new Error('members exploded')
return {
did,
writable: true,
amCreator,
forcedRole,
baseLength: 122287,
indexedLength: 122287,
// Per-writer detail: the aggregate above cannot say WHO accounts for a gap. Two writers, one of
// them carrying blocks the view has not consumed.
writers: [
{ key: 'aaaaaaaaaaaa', authored: 100, seen: 100, pending: 0, indexer: true, removed: false, local: true },
{ key: 'bbbbbbbbbbbb', authored: 146, seen: 100, pending: 46, indexer: false, removed: false, local: false }
],
membersProjected: true,
rosterLen: 2,
roster: [{ key: 'f4a7f860', role: 'owner' }, { key: 'b825e3d2', role: 'writer' }],
membersInView: [{ key: 'f4a7f860' }],
devicesInView: null
}
},
async pointerDiag ({ limit = 0 } = {}) {
if (throwOn === 'pointer-diag') throw new Error('pointer store wedged')
return { did, checked: 7, ok: 7 - stale, stale, missing: 0, unresolvable: 0, tombstones: 0, entries: [], limit }
}
}
}
async function serveAt (t, device, uds) {
fs.mkdirSync(uds.slice(0, uds.lastIndexOf('/')), { recursive: true })
const srv = await serveDeviceUds(device, { uds })
t.teardown(async () => { try { await srv.close() } catch {} })
return srv
}
test('diag collects state + members + pointers for a hosted DID, and summarizes them', async (t) => {
const storage = tmp()
const cfg = { dids: [{ did: PRIMARY, primary: true }] }
await serveAt(t, stubDevice(PRIMARY, { stale: 2, forcedRole: 'forced-non-creator', amCreator: false }), join(storage, 'control.sock'))
const rows = await collectLocalDiag({ cfg, storage, dids: [PRIMARY] })
t.is(rows.length, 1, 'one row per hosted DID')
const r = rows[0]
t.absent(r.unreachable, 'the DID was reachable over its control socket')
t.ok(r.state && r.state.head && r.state.head.rev === '3ke6kgap4u222', 'get-state is in the blob')
t.ok(r.members && Array.isArray(r.members.roster), 'members-diag is in the blob')
t.ok(r.pointers && r.pointers.checked === 7, 'pointer-diag is in the blob')
t.absent(r.errors, 'no per-op errors when every op answers')
// The summary is what an operator reads first — it must carry the role AND the byte health, because
// those are the two things that were unknowable at a glance during the loser-club/wj62 incidents.
const s = summarize(PRIMARY, r)
t.is(s.rev, '3ke6kgap4u222', 'summary reads rev off state.head.rev — the field the daemon ACTUALLY returns')
t.is(s.records, 7, 'summary reads the record COUNT (recordCount), not the key list')
t.is(s.indexed, '122287/122287', 'summary carries the indexed/base pair (stuck vs slow, at a glance)')
t.is(s.writers, 2, 'summary carries the writer COUNT')
t.is(s.writersPending, 46, 'and the total blocks authored-but-not-in-the-view — what attributes a gap')
t.alike(r.members.writers.find((w) => w.pending > 0).key, 'bbbbbbbbbbbb', 'the full section names WHICH writer is behind')
t.is(s.writable, true, 'summary carries writable')
t.is(s.roster, 2, 'summary carries the roster size')
t.is(s.membersInView, true, 'summary says whether __members__ is in the linearized view')
t.is(s.devicesInView, false, 'summary says whether __devices__ is in the view (empty on wj62)')
t.is(s.amCreator, false, 'summary carries the HONEST creator role (_isCreator, not the prototype method)')
t.is(s.forcedRole, 'forced-non-creator', 'summary carries an explicit role override')
t.is(s.stale, 2, 'summary carries the stale-byte count (the wj62 signature)')
t.is(s.reachable, true, 'summary marks the DID reachable')
})
test('the per-DID control socket is resolved as the daemon lays it out', async (t) => {
const storage = tmp()
const cfg = { dids: [{ did: PRIMARY, primary: true }, { did: SECOND }] }
await serveAt(t, stubDevice(PRIMARY), join(storage, 'control.sock'))
await serveAt(t, stubDevice(SECOND), join(storage, 'dids', did16For(SECOND), 'control.sock'))
const rows = await collectLocalDiag({ cfg, storage, dids: [PRIMARY, SECOND] })
t.is(rows.length, 2, 'both hosted DIDs reported')
t.ok(rows[0].socket.endsWith('/control.sock') && !rows[0].socket.includes('/dids/'), 'the PRIMARY DID keeps the flat control.sock')
t.ok(rows[1].socket.includes('/dids/' + did16For(SECOND) + '/'), 'a non-primary DID is read under dids/<did16>/')
t.absent(rows[0].unreachable, 'primary reachable')
t.absent(rows[1].unreachable, 'non-primary reachable')
t.is(rows[1].state.did, SECOND, 'the non-primary row carries ITS own state, not the primary\'s')
})
test('one failure never eats the report', async (t) => {
const storage = tmp()
const missing = 'did:plc:diagmissing00000000000000'
const cfg = { dids: [{ did: PRIMARY, primary: true }, { did: missing }] }
// PRIMARY is up but its pointer store is wedged; `missing` has no socket at all.
await serveAt(t, stubDevice(PRIMARY, { throwOn: 'pointer-diag' }), join(storage, 'control.sock'))
const rows = await collectLocalDiag({ cfg, storage, dids: [PRIMARY, missing] })
const up = rows.find((r) => r.did === PRIMARY)
const down = rows.find((r) => r.did === missing)
t.ok(up.state && up.members, 'a wedged pointer store still reports state AND roster — usually what says why')
t.absent(up.pointers, 'the failing section is absent rather than faking a value')
t.ok(up.errors && /wedged/.test(up.errors['pointer-diag']), 'the failing op NAMES its error')
t.ok(down.unreachable && /control socket/.test(down.unreachable), 'an unreachable DID says so, with the path it looked at')
t.absent(down.state, 'and reports no state for it')
t.is(rows.length, 2, 'the reachable DID is still reported alongside the unreachable one')
})
// A signing node reports holdsLease FALSE — the __lease__ heartbeat was replaced by the presence
// election and the field is vestigial. Surfacing it in the summary beside `rev` cost a real session
// (2026-07-29) chasing a signing wedge that did not exist: both co-owners read holdsLease:false while
// federating to bsky perfectly. The summary must report `signs` (the gate sign() consults) and must NOT
// report holdsLease at all.
{
const row = summarize('did:plc:x', { state: { head: { rev: 'r1' }, holdsLease: false, holdsSigner: true, recordCount: 2, writable: true } })
if (row.signs !== true) throw new Error('FAIL: summary must report `signs` from holdsSigner')
if ('holdsLease' in row) throw new Error('FAIL: summary must NOT surface the legacy holdsLease field')
console.log(' ok - summary reports `signs`, never the legacy holdsLease')
const unknown = summarize('did:plc:y', { state: { head: null, recordCount: 0 } })
if (unknown.signs !== null) throw new Error('FAIL: absent holdsSigner must be null, not false')
console.log(' ok - an older daemon that does not report holdsSigner reads null, not a confident false')
}