// What the worklet announces when it comes up — and why it must not hand-roll the list.
//
// ⛔ THE WORKLET HAD DIVERGED. `boot()` joined the peer feed and the followed feeds by hand:
//
// await node.transport.join(node.peer.feed)
// for (const [, feed] of node._feeds) await node.transport.join(feed)
//
// which is exactly the code favela-peer's own `index.js` REPLACED, and its comment says why: "Three
// things have needed re-joining by hand here and each was forgotten in turn, producing a log that
// replicates for nobody." Two were forgotten here too — the NAMESPACE and the ROOMS.
//
// ⚠ And it hid, because it is only the BOOT path. `_openNames` announces the namespace itself when a
// transport already exists, so a namespace joined while running replicates fine — and then stops
// replicating after the next restart, when nothing joins it again. `_openNames` states the cost:
// "Announced, or a shared registry is three private ones."
//
// ⚠ THE FIRST TEST READS SOURCE, DELIBERATELY. The defect lives in `boot()`, which builds its own
// Hyperswarm and SwarmTransport and is therefore not observable from outside without a DHT. A
// behavioural test of `announceAll` passes with the bug still present — it did, which is how this
// one came to be written this way. boundary.test.js already takes the same approach for the same
// reason: pin the thing that actually regresses, where it actually lives.
import test from 'brittle'
import fs from 'bare-fs'
import os from 'bare-os'
import { join } from 'bare-path'
import Hyperswarm from 'hyperswarm'
import { FavelaNode } from 'favela-peer/lib/node.js'
const ROOT = new URL('..', import.meta.url).pathname
const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'fsa-announce-'))
test('boot announces through announceAll, never a hand-rolled list', (t) => {
const src = fs.readFileSync(join(ROOT, 'lib/node.js'), 'utf8')
t.ok(/node\.announceAll\(\)/.test(src),
'boot calls announceAll — the one place that knows the full set')
t.absent(/for \(const \[, feed\] of node\._feeds\) await node\.transport\.join/.test(src),
'and does not re-enumerate the feeds itself, which is how names and rooms got dropped')
})
// A transport that records rather than reaching the DHT. `announceAll` asks for nothing else, so this
// says what the call covers without adding real network timing to a question that is not about it.
function recorder () {
const joined = []
return { joined, join: async (base) => { joined.push(base) } }
}
test('announceAll covers the namespace and the rooms the hand-rolled list missed', async (t) => {
const swarm = new Hyperswarm()
const node = new FavelaNode(tmp(), { transport: null, swarm })
await node.ready()
t.teardown(async () => {
try { await node.close() } catch {}
try { await swarm.destroy() } catch {}
})
// ⚠ A sentinel rather than a real room. `createRoom` needs a linked device credential, and this
// test is about which COLLECTIONS the announce walks — not about admission. `announceAll` iterates
// `_rooms` and hands each to the transport, so a marker in that map proves the walk reaches it.
const room = { marker: 'a joined room' }
node._rooms.set('sentinel', room)
// The order `boot()` uses, and it is load-bearing: ready() builds the overlay and the transport
// needs it, so every join inside ready() is a no-op and the announce has to happen after.
const rec = recorder()
node.transport = rec
await node.announceAll()
t.ok(node.names, 'the node opened a namespace')
t.ok(rec.joined.includes(node.peer.feed), 'the peer feed is announced')
t.ok(rec.joined.includes(node.names),
'and so is the namespace — without this a shared registry is three private ones')
t.ok(rec.joined.includes(room), 'and the rooms, which the hand-rolled list also walked past')
})
// ⛔ NOTHING IN boot() MAY BLOCK THE OTA STATE WRITE. `bootAtproto` opens a corestore, a swarm and a
// topic per identity; awaiting it meant that on a device which actually held a DID, everything below
// it never ran — including the write of app-update.json. The settings page then rendered the previous
// boot's snapshot for ever: a stale version, and `staged !== running` permanently true, so the update
// banner offered an update that had already been applied and could not be cleared from the device.
//
// Source-level for the same reason as the test above: the failure is an ordering one inside a
// function that builds its own swarm, and a behavioural test cannot observe it without a DHT.
test('boot does not await the atproto boot', (t) => {
const src = fs.readFileSync(join(ROOT, 'lib/node.js'), 'utf8')
t.absent(/await bootAtproto\(/.test(src),
'awaiting it stops the OTA state write, which is what tells settings which build is running')
// ⚠ `[\s\S]*?` and not `[^)]*`: the call's own argument contains parentheses, so a
// no-close-paren class stops inside it and the assertion fails on correct code.
t.ok(/bootAtproto\([\s\S]{0,200}?\.then\(/.test(src),
'it still runs, and its result is still recorded')
// And the write it must not block still happens.
t.ok(/writeState\(\)/.test(src), 'boot still writes the update state')
})