// Which DIDs the phone's in-process favela-web subscribes to.
//
// ⛔ THE CONTROL BELOW IS THE BUG. favela-web reads its roster at MODULE TOP LEVEL:
//
// const ROSTER = (process.env.FAVELA_ROSTER || '').split(',').map((s) => s.trim()).filter(Boolean)
//
// (bin/favela-web.js:152). `prepareEnv()` never set FAVELA_ROSTER, so on the phone that expression
// evaluated to `[]`, `rosterDids` was empty, `addPeer()` was never called, and the boot logged
//
// [favela] joined 0 DID topic(s); indexing over P2P...
//
// Nothing was wired wrong and nothing threw. The phone simply subscribed to no one, so every record
// authored anywhere else — including on another co-owner of ITS OWN DID — reached its overlay store and
// never reached the index every screen reads. Measured 2026-08-18: three posts, pointers converged
// (checked 183→184→185, bytes fetched), still 404 on the phone two and a half hours and two boots later.
//
// So the first test replicates favela-web's parser VERBATIM against the value the phone used to hand it,
// pinning the defect; the rest assert the roster that replaces it.
//
// ⛔ AND THE ROSTER IS NOT THE SIGNING SET. A roster is what this node INDEXES (many DIDs, read-only,
// verified over the firehose). What it SIGNS for is one control socket (lib/writer.js:283). Conflating
// them is how a write lands in someone else's repo, so `hosted` DIDs are in the roster because we index
// them, not because we sign for them.
import test from 'brittle'
import { rosterFor, rosterEnv } from '../lib/roster.js'
const WJ62 = 'did:plc:wj62ebbusln6gxo5azo6ogzo'
const E2JEV = 'did:plc:e2jevhuurrhlm36ufk7fklpg'
const WQNW = 'did:plc:wqnwulql7ndcexample00000'
// favela-web/bin/favela-web.js:152, copied byte-for-byte. If favela-web ever changes how it parses the
// env, this test fails and tells us — which is the point of pinning the CONSUMER's parser rather than
// asserting our own idea of the format.
const parseAsFavelaWebDoes = (v) => String(v || '').split(',').map((s) => s.trim()).filter(Boolean)
test('control: what the phone used to hand favela-web parsed to nobody', (t) => {
t.alike(parseAsFavelaWebDoes(undefined), [], 'unset FAVELA_ROSTER -> no DIDs -> joined 0 DID topic(s)')
t.alike(parseAsFavelaWebDoes(''), [], 'empty string is the same silence')
})
test('the roster round-trips through favela-web own parser', (t) => {
const env = rosterEnv({ hosted: [WJ62], tracked: [E2JEV] })
t.alike(parseAsFavelaWebDoes(env), [WJ62, E2JEV], 'what we set is exactly what favela-web reads back')
})
test('hosted DIDs come first', (t) => {
// addPeer() runs in order and the first roster entry is also what apProjected() asks about
// (bin/favela-web.js:351-352), so a tracked stranger must not displace our own DID.
t.alike(rosterFor({ hosted: [WJ62], tracked: [E2JEV, WQNW] }), [WJ62, E2JEV, WQNW])
})
test('every hosted DID is carried, not just the first', (t) => {
// Two accounts on one handset. Reads are already multi-DID; taking nodes[0] here would silently
// half-subscribe the second user.
t.alike(rosterFor({ hosted: [WJ62, E2JEV] }), [WJ62, E2JEV])
})
test('deduped across sources', (t) => {
// A hosted DID you also follow, or a tracked DID you later co-own. addPeer() dedups by `byDid`, but a
// duplicate here would still be a lie in the logged topic count.
t.alike(rosterFor({ hosted: [WJ62], tracked: [WJ62], following: [WJ62, E2JEV] }), [WJ62, E2JEV])
})
test('junk is dropped rather than passed through', (t) => {
// tracked-dids.json is a file on disk that a crash can truncate; `following` comes off the index.
// A malformed entry must not become a topic join or a verifyKey resolution attempt.
t.alike(rosterFor({ hosted: [WJ62], tracked: ['', ' ', null, undefined, 42, 'not-a-did', 'did:'] }), [WJ62])
})
test('a comma inside a DID can never split one entry into two', (t) => {
// The env format is comma-separated, so a DID containing a comma would silently become two bogus
// entries in favela-web. Nothing should be able to smuggle one through.
t.alike(rosterFor({ tracked: ['did:plc:aaa,did:plc:bbb'] }), [], 'rejected, not split')
})
test('an empty roster yields an empty string, not the word undefined', (t) => {
// `String(undefined)` is 'undefined', which would parse to one garbage DID. A node with nothing to
// subscribe to must produce the same silence as before, not a fake peer.
t.is(rosterEnv({}), '')
t.alike(parseAsFavelaWebDoes(rosterEnv({})), [])
})