OverviewFilesCommitsIssuesDocsReleasesPeers

favela-web / test / appview-dedup.test.mjs

61 lines · javascript ref:
// favela appview DEDUP GATE (#15 balloon fix) — proves a re-index of UNCHANGED records appends ZERO blocks.
// Hyperbee sub-bees do NOT honor alwaysDuplicate:false (verified empirically), so indexPeer's full re-fold
// on every baseline/#sync would balloon the log on EACH reconnect (this is what grew a web-did index to
// 2.6G). putPost now skips unchanged records by content signature. Asserts: (1) core length is FLAT across
// repeated identical re-indexes; (2) a real content edit still applies, at bounded cost; (3) reconcile
// delete-correctness is preserved.
// Run: `node test/appview-dedup.test.mjs`
import '../lib/bare-globals.js'
import { storeDir } from './_store-dir.mjs'
import Corestore from 'corestore'
import { Appview } from '../lib/appview.js'
import { mkGetRecordStore } from './mk-record-store.mjs'

const assert = (cond, msg) => { if (!cond) throw new Error('FAIL: ' + msg); console.log('  ok -', msg) }

const dir = storeDir('appview-dedup-test')
const store = new Corestore(dir)
const av = await new Appview(store, { getRecordStore: mkGetRecordStore(dir + '/rec') }).ready()
const core = av.bee.core
const DID = 'did:plc:dedup'

const N = 15
const mk = (rkey, text, createdAt, tags) => ({ uri: `${DID}/app.bsky.feed.post/${rkey}`, value: { $type: 'app.bsky.feed.post', text, createdAt, ...(tags ? { tags } : {}) } })
const base = () => Array.from({ length: N }, (_, i) => mk('p' + i, 'post ' + i, `2026-07-${String(i + 1).padStart(2, '0')}T00:00:00.000Z`, i % 3 === 0 ? ['topic'] : null))
let recs = base()
const peer = { did: DID, listRecords (c) { return c === 'app.bsky.feed.post' ? recs : [] } }

// --- baseline ---
assert(await av.indexPeer(peer) === N, `baseline indexed ${N} records`)
const lenBaseline = core.length
assert(lenBaseline > N, `core grew on baseline (len=${lenBaseline})`)

// --- THE BALLOON FIX: re-index the SAME repo many times → the core must not grow one block ---
for (let i = 0; i < 8; i++) await av.indexPeer(peer)
assert(core.length === lenBaseline, `EIGHT re-indexes of unchanged records appended ZERO blocks (len still ${lenBaseline}) — balloon fixed`)
let tl = await av.getTimeline({ limit: 100 })
assert(tl.posts.length === N, `all ${N} still queryable after the no-op re-indexes`)

// --- a real content edit (same createdAt/tags) must apply, at a cost of ONE record, not the whole set ---
recs = base(); recs[0].value.text = 'post 0 EDITED'
const lenBeforeEdit = core.length
await av.indexPeer(peer)
const editCost = core.length - lenBeforeEdit
assert(editCost > 0, 'a content edit appended something (the change was recorded)')
assert(editCost < lenBaseline / 3, `edit cost bounded to ~one record (${editCost} blocks << full re-fold)`)
tl = await av.getTimeline({ limit: 100 })
assert(tl.posts.some(p => p.record.text === 'post 0 EDITED'), 'the edit is reflected in the index')
const lenAfterEdit = core.length
await av.indexPeer(peer)
assert(core.length === lenAfterEdit, 'the edited record re-indexes at zero growth (new signature stored)')

// --- reconcile delete-correctness still holds (drop half the repo) ---
recs = base().slice(0, 5)
await av.indexPeer(peer)
tl = await av.getTimeline({ limit: 100 })
assert(tl.posts.length === 5, 'reconcile dropped the stragglers (delete-correctness intact)')

console.log(`\nAPPVIEW-DEDUP OK: 8 re-indexes = 0 growth; edit bounded (${editCost} blocks) + applied; reconcile intact`)
await av.close()
try { await store.close() } catch {}