OverviewFilesCommitsIssuesDocsReleasesPeers

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

118 lines · javascript ref:
// The ingest-time collection ledger (org #33, spec/convergence.md §2) — Bare-native, offline.
//
// ⛔ THE BUG THIS PROVES CLOSED. `applyCommit` ended with `if (!POST_COLLECTIONS.includes(op.collection))
// continue`. A collection with no branch above it was dropped with no log, no counter and no record —
// so a NEW LEXICON appearing in a repo we follow was indistinguishable from one we had deliberately
// decided to skip, and both were indistinguishable from nothing having happened at all. That is the same
// silence, in a fourth costume, that let a node sit broken for five days under four green checks.
//
// The claim under test is not "unknown collections are logged". It is that the THREE CLASSES spec §2
// defines are kept apart at ingest:
//   TRACKED           — a view indexed it; never reaches the ledger
//   IGNORED-BY-POLICY — a decision on file with a reason; counted, never warned about
//   UNKNOWN           — nobody has classified it; counted AND surfaced
// A test that only checked "the unknown one was recorded" would pass on an implementation that recorded
// all three identically, which is the collapse the spec exists to prevent.
//
// Run: `bare test/appview-collections.test.mjs`
import '../lib/bare-globals.js'
import { storeDir, rmStoreDir } 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-collections-test')
let store = new Corestore(dir)
let av = await new Appview(store, { getRecordStore: mkGetRecordStore(dir + '/rec') }).ready()

const DID = 'did:plc:ledger'
const DID2 = 'did:plc:ledger2'
const UNKNOWN = 'app.bsky.graph.list'          // real lexicon, no view here, not on the ignore list
const IGNORED = 'net.favela.test'              // on the ignore list (lib/hosted-dids.js) with a reason
const TRACKED = 'app.bsky.feed.post'
const byName = (l, c) => l.find((e) => e.collection === c) || null

// --- baseline: an untouched node claims nothing ------------------------------------------------------
// The ledger must start EMPTY rather than pre-seeded with the ignore list. A node that has never seen a
// collection and one that sees it constantly are different facts, and pre-seeding would erase the first.
assert((await av.collectionLedger()).length === 0, 'a fresh index has an empty ledger — nothing is pre-seeded')
assert((await av.unknownCollections()).length === 0, 'and no unknown collections')

// --- UNKNOWN: recorded, and applyCommit still reports it as unapplied --------------------------------
const n1 = await av.applyCommit(DID, [{ action: 'create', collection: UNKNOWN, rkey: 'l1', record: { name: 'x' } }])
assert(n1 === 0, 'applyCommit still returns 0 for a collection it cannot index (no behaviour change)')

let ledger = await av.collectionLedger()
let u = byName(ledger, UNKNOWN)
assert(u !== null, 'the UNKNOWN collection is now IN the ledger (it used to vanish through a bare continue)')
assert(u.class === 'unknown', 'classified as unknown — not tracked, not on the ignore list')
assert(u.count === 1, 'counted once')
assert(u.dids.includes(DID), 'the ledger records WHICH repo sent it')
assert(u.sample && u.sample.rkey === 'l1', 'and a sample rkey, so an operator can go look at one')
assert(typeof u.firstSeen === 'string' && typeof u.lastSeen === 'string', 'first/last seen are recorded')

// --- IGNORED-BY-POLICY: counted, but NOT reported as unknown -----------------------------------------
// The distinction that matters. Both are dropped; only one is a gap in our knowledge.
await av.applyCommit(DID, [{ action: 'create', collection: IGNORED, rkey: 't1', record: { note: 'debris' } }])
ledger = await av.collectionLedger()
const g = byName(ledger, IGNORED)
assert(g !== null && g.class === 'ignored', 'an ignore-listed collection is recorded as `ignored`, not `unknown`')
const unknowns = await av.unknownCollections()
assert(unknowns.length === 1 && unknowns[0].collection === UNKNOWN,
  'unknownCollections() returns ONLY the unclassified one — the ignored one is a decision, not a gap')

// --- TRACKED never enters the ledger -----------------------------------------------------------------
// Guards against the opposite failure: a ledger that fills with everything says nothing.
await av.applyCommit(DID, [{ action: 'create', collection: TRACKED, rkey: 'p1', record: { text: 'hi', createdAt: '2026-08-07T00:00:00.000Z' } }])
assert(byName(await av.collectionLedger(), TRACKED) === null, 'a TRACKED collection is indexed, never recorded as unhandled')
assert(await av.noteUnhandled(DID, TRACKED, 'p1') === 'tracked',
  'noteUnhandled refuses a tracked collection outright — a malformed record is not an unclassified lexicon')

// --- accumulation: count and lastSeen advance, firstSeen does not ------------------------------------
// The ledger is keyed by COLLECTION, so it must grow with distinct lexicons and never with traffic.
const before = byName(await av.collectionLedger(), UNKNOWN)
await new Promise((r) => setTimeout(r, 5)) // so lastSeen can differ from firstSeen
await av.applyCommit(DID2, [{ action: 'create', collection: UNKNOWN, rkey: 'l2', record: { name: 'y' } }])
await av.applyCommit(DID2, [{ action: 'delete', collection: UNKNOWN, rkey: 'l3' }])
ledger = await av.collectionLedger()
u = byName(ledger, UNKNOWN)
assert(u.count === 3, 'three ops on one unknown collection counted 3 — deletes count as sightings too')
assert(ledger.filter((e) => e.collection === UNKNOWN).length === 1, 'and produced exactly ONE ledger entry, not three')
assert(u.firstSeen === before.firstSeen, 'firstSeen is immutable once set')
assert(u.lastSeen >= before.lastSeen, 'lastSeen advances')
assert(u.dids.includes(DID) && u.dids.includes(DID2), 'both sending repos are recorded')
assert(u.sample.rkey === 'l1', 'the sample stays the FIRST one seen — a stable thing to go look at')

// --- ordering: UNKNOWN sorts first -------------------------------------------------------------------
assert(ledger[0].class === 'unknown', 'the ledger puts UNKNOWN first — it is the class that should make someone look')

// --- the snapshot path has the same coverage ---------------------------------------------------------
// indexPeer's store branch had an `else if` chain with an identical silent tail. A _noRepo peer reads the
// whole repo out of the shared store, so it is the only snapshot path that can SEE an unknown lexicon.
const SNAP = 'com.example.brandnew'
const rs = mkGetRecordStore(dir + '/rec2')
const av2 = await new Appview(store, { name: 'snap-appview', getRecordStore: rs }).ready()
const snapStore = await rs('did:plc:snap')
await snapStore.put(SNAP, 's1', { $type: SNAP, hello: 'world' })
await av2.indexPeer({ did: 'did:plc:snap', _noRepo: true })
const snapU = byName(await av2.collectionLedger(), SNAP)
assert(snapU !== null && snapU.class === 'unknown', 'indexPeer\'s snapshot path records an unknown collection too')

// --- PERSISTENCE across a restart --------------------------------------------------------------------
// The whole reason the ledger is in the Hyperbee and not a Map: an operator who restarts must not thereby
// lose the fact that something unclassified has been arriving. Proven by actually reopening from disk,
// not by asserting the intent.
await store.close()
store = new Corestore(dir)
av = await new Appview(store, { getRecordStore: mkGetRecordStore(dir + '/rec') }).ready()
const after = await av.unknownCollections()
assert(after.length === 1 && after[0].collection === UNKNOWN, 'the UNKNOWN entry survived a full close + reopen')
assert(after[0].count === 3, 'with its count intact — a restart does not reset the evidence')
assert(byName(await av.collectionLedger(), IGNORED) !== null, 'and the ignored entry survived too')

await store.close()
rmStoreDir(dir)
console.log('\nappview-collections: all assertions passed')