OverviewFilesCommitsIssuesDocsReleasesPeers

ap-bridge / test / feed-holder.test.js

205 lines · javascript ref:
// feed-holder.test.js — the bridge holds somebody else's feed and reads it.
//
// ⛔ REAL feeds, real replication over a pipe. The unit tests above use a fake held feed, which
// cannot tell me whether an Autobee opened by KEY on a second corestore actually converges — and
// that is the whole claim being made here.

import test from 'brittle'
import fs from 'bare-fs'
import os from 'bare-os'
import { join } from 'bare-path'
import Corestore from 'corestore'
import { Feed } from 'favela-peer/lib/feed-base.js'
import { RecordStore } from 'favela-peer/lib/record-store.js'
import { FavelaFeed } from '../lib/favela-feed.js'
import { attest, identity } from 'favela-peer/test/helpers.js'

const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'holder-'))

function pipe (a, b) {
  const s1 = a.replicate(true)
  const s2 = b.replicate(false)
  s1.pipe(s2).pipe(s1)
  return () => { s1.destroy(); s2.destroy() }
}

async function until (fn, { timeout = 5000 } = {}) {
  const deadline = Date.now() + timeout
  while (Date.now() < deadline) {
    const v = await fn()
    if (v) return v
    await new Promise((r) => setTimeout(r, 25))
  }
  return null
}

test('★★ a bridge holding a feed by KEY reads its posts and its reach', async (t) => {
  const records = tmp() // stands in for the overlay: the log carries pointers, bytes travel separately

  // The peer — somebody else's machine.
  const peer = new Feed(new Corestore(tmp()), null, { store: new RecordStore(records) })
  await peer.ready()
  await peer.addDevice(await attest(peer))
  await peer.update()
  await peer.put('reach', 'self', { frontmatter: { networks: ['fediverse'], createdAt: 'x' }, body: '' })
  await peer.put('post', '01k2test', { frontmatter: { createdAt: '2026-09-08T00:00:00Z' }, body: 'over the swarm\n' })
  await peer.update()

  // The bridge — a SECOND corestore, opening the same feed BY KEY. It has no credential for it.
  const bridge = new Feed(new Corestore(tmp()), peer.key, { store: new RecordStore(records) })
  await bridge.ready()
  t.teardown(() => Promise.all([peer.close(), bridge.close()]))

  const unpipe = pipe(peer.base.corestore ?? peer.base.store, bridge.base.corestore ?? bridge.base.store)
  t.teardown(unpipe)

  const held = new FavelaFeed({ feed: bridge })
  const got = await until(async () => {
    const { rows } = await held.ownPosts()
    return rows.length ? rows : null
  })

  t.ok(got, 'the post replicated to a bridge that only had the key')
  t.is(got[0].name, '01k2test')
  t.is(got[0].body, 'over the swarm\n')

  const reach = await held.reach()
  t.alike(reach.networks, ['fediverse'], 'and its owner\'s consent came with it')

  // ⛔ The bridge cannot write to a feed it merely holds. That is the keyless claim.
  t.absent(bridge.base.writable, 'the bridge is NOT a writer on their feed')
})

// ⛔⛔ THE TRIGGER, and it is the reason a registered user's posts reached nobody.
//
// The outbox had all 16 of ashatron's posts and prsm.space saw none of them, because an outbox is a
// PULL surface and no fediverse server backfills one. Delivery needs a push, the push needs
// `Publisher.onHead()`, and nothing ever called it for a held feed: the primary gets `onHead` from
// DidPeer and a favela feed had no equivalent.
//
// ⛔ AND HOOKING ONCE MISSES A WRITER. Measured on this exact setup: the bridge reports ONE writer
// immediately after `ready()` and TWO once replication settles — the second arrives with the system
// core. A snapshot taken at hold() therefore hooks a real core and still misses the one that shows
// up later, which is the failure mode that reads as "it worked in testing". favela-peer paid for
// this shape three separate times (boot-only, then an `update` event that only fires inside
// `_onError`, then a writer-set snapshot), and each version SHIPPED because the unit tests were
// green and only a real append over a real pipe could tell.
//
// So this asserts against real replication: a post written on the peer AFTER the bridge is already
// holding and watching must reach the callback with nothing polling.
test('⛔⛔ watch() fires when a held feed advances — real append, real pipe', async (t) => {
  const records = tmp()

  const peer = new Feed(new Corestore(tmp()), null, { store: new RecordStore(records) })
  await peer.ready()
  await peer.addDevice(await attest(peer))
  await peer.update()

  const bridge = new Feed(new Corestore(tmp()), peer.key, { store: new RecordStore(records) })
  await bridge.ready()
  t.teardown(() => Promise.all([peer.close(), bridge.close()]))

  const unpipe = pipe(peer.base.corestore ?? peer.base.store, bridge.base.corestore ?? bridge.base.store)
  t.teardown(unpipe)

  // Stand in for the FeedHolder's own storage/swarm — this test is about the trigger, not the swarm.
  const { FeedHolder } = await import('../lib/feed-holder.js')
  const holder = Object.create(FeedHolder.prototype)
  holder._held = new Map([[bridge.key.toString('hex'), { feed: bridge }]])
  holder._hooked = new Map()
  holder._watchers = new Map()
  holder.debounceMs = 50
  holder._log = () => {}

  let fires = 0
  holder.watch(bridge.key, () => { fires++ })
  t.teardown(() => { for (const [, w] of holder._watchers) if (w.timer) clearTimeout(w.timer) })

  // The boot fire is a real trigger and counts — it is what reconciles a feed held across a restart.
  await until(async () => fires >= 1)
  const afterBoot = fires

  // Now somebody writes, with the bridge already holding. Nothing here polls the feed.
  await peer.put('post', '01k2later', { frontmatter: { createdAt: '2026-09-08T01:00:00Z' }, body: 'written after the watch\n' })
  await peer.update()

  const advanced = await until(async () => (fires > afterBoot ? fires : null), { timeout: 10000 })
  t.ok(advanced, 'the watcher fired on a remote append, with nothing polling')

  const stats = holder.watchStats()
  const one = Object.values(stats)[0]
  // ⛔ Counted per trigger so a dead hook is OBSERVABLE. The only evidence favela-peer had for three
  // broken versions was how LONG something took, which is inference, not measurement.
  t.ok(one.append > 0, 'and it was the APPEND hook that fired, not the boot pass')
  t.ok(one.hooked > 0, 'writer cores were hooked — after the feed had them, not before')

  // And the post is readable, so the callback is being handed a feed that actually advanced.
  const { rows } = await new FavelaFeed({ feed: bridge }).ownPosts()
  t.ok(rows.some((r) => r.name === '01k2later'), 'the record the trigger was announcing')
})

// ⛔⛔ A KNOCK MUST NOT STRAND THE BRIDGE BEHIND THE OWNER (2026-10-12).
//
// FeedHolder opened every held feed with favela-peer's default of `optimistic: false`. Every favela
// node opens feeds with `optimistic: true` (favela-peer node.js: "the doorbell is on by default"), and
// the owner's apply ACKS a stranger's knock. So the owner's log links past the knock and a holder that
// refuses optimistic blocks can never follow it: the two compute different views of one log. MEASURED
// live on fleeky's feed: the bridge stuck at 106 of 113 applied, the newest posts never reaching the
// fediverse. (On autobee 2.1.1 the same knock also pinned a core; that half is favela-peer's
// holder-after-knock.test.js.)
//
// So this goes through FeedHolder.hold() itself — the place the option is chosen — and asserts the
// bridge sees what the owner wrote AFTER a stranger knocked.
test('⛔⛔ a knock on a held feed does not strand the bridge behind the owner', async (t) => {
  const { FeedHolder } = await import('../lib/feed-holder.js')
  const records = tmp()

  // The owner, opened the way a favela node opens it.
  const owner = new Feed(new Corestore(tmp()), null, { store: new RecordStore(records), optimistic: true })
  await owner.ready()
  await owner.addDevice(await attest(owner))
  await owner.update()

  // A stranger knocks from their own machine: a reader of the owner's log, a writer of nothing in it.
  const stranger = new Feed(new Corestore(tmp()), null, { store: new RecordStore(tmp()), optimistic: true })
  await stranger.ready()
  const ownerOnStranger = new Feed(new Corestore(tmp()), owner.key, { store: new RecordStore(records), optimistic: true })
  await ownerOnStranger.ready()
  const unpipeStranger = pipe(owner, ownerOnStranger)

  await until(async () => { await ownerOnStranger.update(); return (await ownerOnStranger.devices()).length > 0 }, { timeout: 10000 })
  // A knock proves which identity is knocking (favela-peer feed.js applyKnock): the stranger's device
  // binds the writer core it knocks through.
  const strangerDevice = await (await identity()).device()
  await ownerOnStranger.knock(stranger.key, (writerKeyHex) => strangerDevice.bind(writerKeyHex))
  t.ok(await until(async () => { await owner.update(); return (await owner.knocks()).length }, { timeout: 10000 }),
    'setup: the stranger\'s knock landed in the owner\'s feed')

  await owner.put('post', 'after-knock', { frontmatter: { createdAt: '2026-10-12T00:00:00Z' }, body: 'written after the knock\n' })
  await owner.update()

  // The bridge holds the owner's feed through FeedHolder, with an inert swarm: replication is piped by
  // hand, so this test is about the hold, not the DHT.
  const swarm = { connections: [], on () {}, join () {}, async leave () {} }
  const holder = new FeedHolder({ storage: tmp(), swarm })
  await holder.ready()
  const held = await holder.hold(owner.key)
  const unpipeBridge = pipe(owner, held.feed)

  t.teardown(async () => {
    unpipeStranger(); unpipeBridge()
    await Promise.all([owner.close(), stranger.close(), ownerOnStranger.close()])
    await holder.close()
  })

  const settles = (p, ms) => Promise.race([p.then(() => true), new Promise((r) => setTimeout(() => r(false), ms))])
  const seen = await until(async () => {
    if (!(await settles(held.feed.update(), 2000))) return null
    return (await held.feed.pointers('post')).some((p) => p.name === 'after-knock') || null
  }, { timeout: 15000 })

  t.ok(seen, 'the bridge sees what the owner wrote after the knock')
  t.absent(held.feed.base.writable && (await held.feed.devices()).includes(held.feed.deviceKey),
    '⛔ and holding still grants the bridge nothing on the roster')
})