OverviewFilesCommitsIssuesDocsReleasesPeers

favela-peer / test / mirror.test.js

331 lines · javascript ref:
// mirror.test.js — hold the bytes of feeds you follow, before anyone asks to read them.
//
// ⛔ THE OUTAGE THIS CLOSES, measured 2026-09-08. tabertron held five pointers from ashatron and
// could read none of them: ashatron is a laptop, it went offline, and it was the only machine
// holding the bytes. Nothing was broken — bytes are fetched only when a reader asks, nobody had
// asked, so no second copy existed. The surface said so honestly: "the log arrived, the bytes did
// not."
//
// ★ AND THE FIX IS ONLY A TRIGGER. Two things already work and must not be rebuilt:
//   - a fetch WRITES the record into this feed's working tree, so the copy is durable
//   - hyper-overlay RE-SEEDS on fetch, so this node then serves those bytes to others
// So reading a record once already makes you a mirror of it. Nothing reads unprompted; that is all
// that is missing. See hyper-social/docs/HOLDING-AND-TELLING.md.
//
// ⛔ FOLLOWED FEEDS ONLY. `timeline` already excludes peeked feeds because putting them in "would
// make reading a stranger's page indistinguishable from following them". Mirroring a peek would be
// that same collapse with disk attached: every profile you glanced at becomes a permanent copy.

import test from 'brittle'
import { createMirror } from '../lib/mirror.js'

// A feed with pointers and a store that knows what is local. `pointers()` never fetches — that is
// the whole reason it exists, since list() and unresolved() both resolve every record.
function fakeFeed (names, { local = [], fail = [], onFetch = () => {} } = {}) {
  const localSet = new Set(local)
  return {
    async pointers () {
      return names.map((n) => ({ key: 'post/' + n, collection: 'post', name: n, pointer: { $oid: 'oid-' + n, size: 1 } }))
    },
    store: {
      async get (_c, n) { return localSet.has(n) ? { body: n } : null },
      async ensureLocal (_c, n) {
        onFetch(n)
        if (fail.includes(n)) throw new Error('no peer has ' + n)
        localSet.add(n)
        return { body: n }
      }
    }
  }
}

// An autobee's writer cores. `w.core.on('append')` is the real signal that blocks landed —
// including replicated ones. autobee itself emits no view-advanced event.
function fakeBase () {
  const h = []
  const core = {
    on: (_e, fn) => h.push(fn),
    off: (_e, fn) => { const i = h.indexOf(fn); if (i >= 0) h.splice(i, 1) }
  }
  return { writers: [{ core }], emit: () => h.slice().forEach((fn) => fn()), listeners: () => h.length }
}

test('★★ it fetches records nobody asked to read', async (t) => {
  const fetched = []
  const feed = fakeFeed(['a', 'b', 'c'], { onFetch: (n) => fetched.push(n) })
  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], log: () => {} })

  await m.pass()

  t.alike(fetched.sort(), ['a', 'b', 'c'], 'all three, with no reader involved')
})

test('★ a record already on disk is not fetched again', async (t) => {
  const fetched = []
  const feed = fakeFeed(['a', 'b'], { local: ['a'], onFetch: (n) => fetched.push(n) })
  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], log: () => {} })

  await m.pass()

  t.alike(fetched, ['b'], 'only the missing one')
})

// ⚠ The layer below already bounds CHUNKS — the scheduler holds an RTT-scaled inflight window per
// peer. What is unbounded is the number of FILES, and a freshly followed feed is all of them.
test('★★ never more than `concurrency` fetches in flight', async (t) => {
  let now = 0
  let peak = 0
  const names = Array.from({ length: 20 }, (_, i) => 'r' + i)
  const feed = fakeFeed(names, { onFetch: () => {} })
  // Wrap ensureLocal to hold the slot open across a tick so overlap is observable.
  const inner = feed.store.ensureLocal
  feed.store.ensureLocal = async (c, n) => {
    now++; peak = Math.max(peak, now)
    await new Promise((r) => setTimeout(r, 5))
    now--
    return inner.call(feed.store, c, n)
  }

  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], concurrency: 3, log: () => {} })
  await m.pass()

  t.is(peak, 3, 'the cap held')
  t.absent(now, 'and everything drained')
})

test('⛔ one unfetchable record does not stop the rest', async (t) => {
  const fetched = []
  const feed = fakeFeed(['a', 'b', 'c'], { fail: ['b'], onFetch: (n) => fetched.push(n) })
  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], log: () => {} })

  await m.pass()

  t.alike(fetched.sort(), ['a', 'b', 'c'], 'every one was attempted')
  // b stays missing and will be retried on the next pass — a peer that is away is not a peer that
  // is gone, and this is exactly the state the surface reports as unresolved.
})

test('★ it mirrors every followed feed, not just the first', async (t) => {
  const one = []; const two = []
  const m = createMirror({
    feeds: () => [
      { key: 'f1', feed: fakeFeed(['a'], { onFetch: (n) => one.push(n) }) },
      { key: 'f2', feed: fakeFeed(['b'], { onFetch: (n) => two.push(n) }) }
    ],
    log: () => {}
  })

  await m.pass()

  t.alike(one, ['a'])
  t.alike(two, ['b'])
})

test('★ stop() ends the pass, and a stopped mirror does nothing', async (t) => {
  const fetched = []
  const feed = fakeFeed(['a', 'b', 'c'], { onFetch: (n) => fetched.push(n) })
  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], log: () => {} })

  m.stop()
  await m.pass()

  t.is(fetched.length, 0, 'nothing after stop')
})

test('⛔ a feed that cannot be listed is skipped, not fatal', async (t) => {
  const fetched = []
  const bad = { async pointers () { throw new Error('view not ready') } }
  const good = fakeFeed(['a'], { onFetch: (n) => fetched.push(n) })
  const m = createMirror({ feeds: () => [{ key: 'bad', feed: bad }, { key: 'ok', feed: good }], log: () => {} })

  await m.pass()

  t.alike(fetched, ['a'], 'the healthy feed still mirrored')
})

// ⛔ THE BUG THE UNIT TESTS MISSED, found by an end-to-end run on two machines.
//
// The first version swept once at boot. At boot a followed feed's log has usually NOT replicated
// yet — the swarm connection is still forming — so the sweep ran, correctly found nothing, and
// never ran again. Measured: a record posted on ashatron was still absent on tabertron 2.5 minutes
// and one restart later, and appeared instantly the moment something READ it.
//
// Every fake above hands back its pointers immediately, so none of them could express "the log
// arrives later". This one does.
test('★★ a log that advances AFTER the boot pass still gets mirrored', async (t) => {
  let names = []
  const fetched = []
  const feed = {
    async pointers () {
      return names.map((n) => ({ key: 'post/' + n, collection: 'post', name: n, pointer: { $oid: 'oid-' + n } }))
    },
    store: {
      async get () { return null },
      async ensureLocal (_c, n) { fetched.push(n); return { body: n } }
    }
  }

  // ⛔ A WRITER CORE, not the autobee. autobee emits no view-advanced event — see mirror.js.
  const base = fakeBase()

  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], debounceMs: 10, log: () => {} })
  m.watch(base)

  await m.pass()   // the boot pass: finds nothing, and attaches the append hooks
  t.is(fetched.length, 0, 'boot pass finds nothing — the log has not arrived')
  t.is(m.stats().hooked, 1, 'but it DID hook the writer core — the thing 0.0.42 silently did not')

  names = ['late']            // the log replicates
  base.emit()                 // autobee says the view advanced
  await new Promise((r) => setTimeout(r, 60))

  t.alike(fetched, ['late'], 'and it is mirrored without anyone reading it')
})

test('★ a burst of updates coalesces into one pass', async (t) => {
  let passes = 0
  const feed = {
    async pointers () { passes++; return [] },
    store: { async get () { return null }, async ensureLocal () { return null } }
  }
  const base = fakeBase()

  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], debounceMs: 20, log: () => {} })
  m.watch(base)
  await m.pass()   // ⛔ hooks attach during a PASS, not at watch() — writers do not exist yet at open
  const after = passes
  for (let i = 0; i < 10; i++) base.emit()
  await new Promise((r) => setTimeout(r, 80))

  t.is(passes - after, 1, 'ten appends, one sweep — a core appends far more often than records land')
})

test('★ stop() detaches the watcher', async (t) => {
  let passes = 0
  const feed = {
    async pointers () { passes++; return [] },
    store: { async get () { return null }, async ensureLocal () { return null } }
  }
  const base = fakeBase()

  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], debounceMs: 10, log: () => {} })
  m.watch(base)
  m.stop()
  base.emit()
  await new Promise((r) => setTimeout(r, 40))

  t.is(passes, 0, 'nothing sweeps after stop')
  t.is(base.listeners(), 0, 'and the listener is gone')
})

// ⛔ The view only advances when apply runs, so replicated blocks can be sitting in the writer
// cores with `pointers()` still reporting the old set. A sweep that skipped the refresh would keep
// finding "nothing new" while the log had grown — which is indistinguishable from the 0.0.40 bug
// and would have been just as invisible.
test('★★ a sweep refreshes the view before reading pointers', async (t) => {
  const order = []
  let names = []
  const feed = {
    async update () { order.push('update'); names = ['fresh'] },
    async pointers () {
      order.push('pointers')
      return names.map((n) => ({ key: 'post/' + n, collection: 'post', name: n, pointer: { $oid: 'o' } }))
    },
    store: { async get () { return null }, async ensureLocal (_c, n) { order.push('fetch:' + n); return { body: n } } }
  }

  await createMirror({ feeds: () => [{ key: 'f1', feed }], log: () => {} }).pass()

  t.alike(order, ['update', 'pointers', 'fetch:fresh'], 'refresh, then read, then fetch')
})

// ⛔ A PEER COMING BACK IS AN EVENT, NOT AN ABSENCE. A record stays unresolved because nobody
// holding its bytes is online; their return is a swarm 'connection'. Re-checking that on a clock
// was polling for data — the part of the rule that IS absolute.
test('★★ a peer connecting retries what could not be fetched', async (t) => {
  let peerHere = false
  const fetched = []
  const feed = {
    async update () {},
    async pointers () { return [{ key: 'post/a', collection: 'post', name: 'a', pointer: { $oid: 'o' } }] },
    store: {
      async get () { return null },
      async ensureLocal (_c, n) { if (!peerHere) return null; fetched.push(n); return { body: n } }
    }
  }
  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], debounceMs: 10, log: () => {} })

  await m.pass()
  t.is(fetched.length, 0, 'nobody has it yet')

  peerHere = true
  m.onPeer()
  await new Promise((r) => setTimeout(r, 60))

  t.alike(fetched, ['a'], 'fetched when the holder came back')
  t.is(m.stats().connection, 1, 'and the connection trigger is what did it')
})


// ⛔ LEVEL 2. A record's own bytes are one oid in the log; the pictures and files it names are more
// oids, inside its markdown. Mirroring only the first leaves a post you can read with an image that
// will not load — the same failure as §0's outage, one level down. Following means holding what
// somebody published, and a picture is part of what they published.
//
// ⚠ Both levels are the SAME hyper-overlay: files.js says "There is NO blob store", and a record
// and an image are both registerFile/fetchFile by content hash. There is one store here, not two.
test('★★ it mirrors the files a record names, not just the record', async (t) => {
  const gotOids = []
  const body = 'look\n\n![a cat](favela://blob/' + 'a'.repeat(32) + ')\n\n' +
               '[the pdf](favela://blob/' + 'b'.repeat(32) + ')\n'
  const feed = {
    async update () {},
    async pointers () { return [{ key: 'post/p1', collection: 'post', name: 'p1', pointer: { $oid: 'rec' } }] },
    store: {
      async get () { return null },
      async ensureLocal () { return { frontmatter: {}, body } }
    }
  }
  const files = { async get (oid) { gotOids.push(oid); return { path: '/x', mimeType: 'image/png', size: 1 } } }

  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], files, log: () => {} })
  await m.pass()

  t.alike(gotOids.sort(), ['a'.repeat(32), 'b'.repeat(32)], 'the image AND the linked file')
  t.is(m.stats().files, 2, 'counted separately from records')
})

test('★ a record naming no files costs no extra fetches', async (t) => {
  let calls = 0
  const feed = {
    async update () {},
    async pointers () { return [{ key: 'post/p1', collection: 'post', name: 'p1', pointer: { $oid: 'r' } }] },
    store: { async get () { return null }, async ensureLocal () { return { frontmatter: {}, body: 'just words' } } }
  }
  const m = createMirror({ feeds: () => [{ key: 'f1', feed }], files: { async get () { calls++; return null } }, log: () => {} })
  await m.pass()
  t.is(calls, 0)
})

// ⛔ A file we cannot fetch must not fail the record that named it. The record is already held and
// readable; the picture is simply missing, which is the honest UNRESOLVED state one level down.
test('⛔ an unfetchable file does not undo the record', async (t) => {
  const feed = {
    async update () {},
    async pointers () { return [{ key: 'post/p1', collection: 'post', name: 'p1', pointer: { $oid: 'r' } }] },
    store: {
      async get () { return null },
      async ensureLocal () { return { frontmatter: {}, body: '![x](favela://blob/' + 'c'.repeat(32) + ')' } }
    }
  }
  const m = createMirror({
    feeds: () => [{ key: 'f1', feed }],
    files: { async get () { throw new Error('no holder online') } },
    log: () => {}
  })

  const r = await m.pass()
  t.is(r.fetched, 1, 'the record still counts as mirrored')
  t.is(m.stats().files, 0, 'and the file did not')
})