// favela-web Writer — the POSSE write path. Proves the langs fix: bsky-fronted posts (public scope) MUST
// declare `langs` so bsky.app's custom-feed language tuner (FeedTuner.preferredLangOnly -> lande) never
// mis-detects short/technical text and silently drops the post from the feed; favela-local posts carry no
// langs (net.favela.local.post has no such field). Pure — stubs the hpp `_call`. Run: node test/writer.test.mjs
import { Writer, writeTransportConfigured } from '../lib/writer.js'
const assert = (c, m) => { if (!c) { console.error('FAIL:', m); process.exit(1) } console.log(' ok -', m) }
const mk = () => {
const w = new Writer({})
const calls = []
w._call = async (method, params) => { calls.push({ method, params }); return { commit: 'stub' } }
return { w, calls }
}
// public (bsky) posts declare langs so bsky takes the fast path and never runs lande on the raw text
{
const { w, calls } = mk()
await w.post('test 2') // default scope = public; "test 2" -> lande mis-detects as French with no langs
const { collection, record } = calls[0].params
assert(collection === 'app.bsky.feed.post', 'public scope -> app.bsky.feed.post')
assert(Array.isArray(record.langs) && record.langs.length === 1 && record.langs[0] === 'en', 'public post declares langs:[en]')
assert(record.$type === 'app.bsky.feed.post' && record.text === 'test 2', 'record shape intact')
}
// caller can override the declared language
{
const { w, calls } = mk()
await w.post('hola', { langs: ['es'] })
assert(calls[0].params.record.langs[0] === 'es', 'langs override honored')
}
// favela-local posts carry NO langs (never fronted to bsky; local lexicon has no langs field)
{
const { w, calls } = mk()
await w.post('local note', { scope: 'local' })
const { collection, record } = calls[0].params
assert(collection === 'net.favela.local.post', 'local scope -> net.favela.local.post')
assert(!('langs' in record), 'local post has no langs field')
}
// an explicitly empty langs array adds no field (avoid emitting langs:[])
{
const { w, calls } = mk()
await w.post('x', { langs: [] })
assert(!('langs' in calls[0].params.record), 'empty langs => no langs field')
}
console.log('writer.test.mjs — all passed')
// ── IN-PROCESS TRANSPORT (favela-android) ───────────────────────────────────────────────────────────
// favela-android is favela-web's UI ported to React Native over a node running IN THE SAME PROCESS.
// There is no UDS and no swarm to reach: the SwarmDevice object is right there. Every method on this
// class is record-BUILDING (scope->collection, facets, embeds, langs, the 300-grapheme cap) plus one
// `_call` — so injecting the device makes the phone reuse this write path verbatim instead of
// reimplementing the record shapes, which is exactly how favela shipped uri-only strongRefs once.
console.log('\n# in-process device transport')
{
const seen = []
const device = {
applyWrite: async (op) => { seen.push(['apply-write', op]); return { ok: true, recordCount: 1 } },
state: async () => ({ did: 'did:plc:inproc' }),
delegate: async (op) => { seen.push(['delegate', op]); return { delegation: 'x' } },
listDevices: async () => [{ subKey: 'aa' }],
revokeDevice: async (k) => { seen.push(['revoke', k]); return { ok: true } },
isDeviceRevoked: async () => false
}
const blobs = { put: async (bytes, mimeType) => ({ cid: 'bafyfake', size: bytes.length, mimeType }) }
const w = new Writer({ device, blobs })
assert(await w.ready() === true, 'ready() is immediate with an injected device — no socket, no DHT')
const r = await w.post('hello from the phone')
assert(seen[0][0] === 'apply-write', 'post routed to device.applyWrite, not a socket')
assert(seen[0][1].collection === 'app.bsky.feed.post', 'the SAME record shape the web path builds')
assert(Array.isArray(seen[0][1].record.langs), 'and the same langs handling')
assert(r.rkey && r.collection, 'post returns rkey/collection as before')
const st = await w.state()
assert(st.did === 'did:plc:inproc', 'get-state maps to device.state()')
const b = await w.uploadBlob(Buffer.from('abc'), 'image/png')
assert(b.cid === 'bafyfake' && b.size === 3, 'upload-blob maps to the blob store, bytes NOT hex-encoded in-process')
const devs = await w.listDevices()
assert(Array.isArray(devs) && devs[0].subKey === 'aa', 'list-devices maps to device.listDevices()')
}
// ── the WRITE GATE: which transports count as "configured" ────────────────────────────────────────────
// WHY THIS IS A FUNCTION AND NOT AN `if`. bin/favela-web.js disables writes wholesale unless a transport
// is named, because an instance that cannot name the node it writes to must not guess — the 2026-08-09
// accident was a test run posting into live wj62 through a default. But the gate tested ONLY
// FAVELA_CONTROL_KEY (the swarm transport), while Writer has always supported a LOCAL UDS. So a
// co-located instance with hpp's socket right there — favela-android's embedded favela-web — was refused
// its own local device and the phone could not post.
//
// ⛔ EXPLICIT CONFIG ONLY. Writer falls back to DEFAULT_SOCK when nothing is set, so gating on
// "does Writer have a uds" would re-open the exact hazard: every unconfigured instance would silently
// acquire writes against whatever hpp owns the default socket. The gate must read the ENV, not the Writer.
console.log('write gate')
{
assert(writeTransportConfigured({}) === null, 'nothing configured -> writes refused')
assert(writeTransportConfigured({ FAVELA_CONTROL_KEY: 'abc' }) === 'swarm', 'control key -> swarm transport')
assert(writeTransportConfigured({ FAVELA_HPP_SOCK: '/run/hpp.sock' }) === 'uds', 'explicit local socket -> uds transport')
assert(writeTransportConfigured({ FAVELA_CONTROL_KEY: 'abc', FAVELA_HPP_SOCK: '/run/hpp.sock' }) === 'uds',
'local socket WINS over the swarm key — never take a DHT round-trip to reach a device on this box')
assert(writeTransportConfigured({ FAVELA_CONTROL_KEY: ' ' }) === null, 'whitespace is not configuration')
assert(writeTransportConfigured({ FAVELA_HPP_SOCK: '' }) === null, 'empty socket is not configuration')
// the hazard, asserted directly: the DEFAULT socket must never open the gate by itself
assert(writeTransportConfigured({ SOME_OTHER: 'x' }) === null, 'DEFAULT_SOCK does not count as configured')
}
console.log('\nall writer tests passed')