// ★ An interaction rings the target's doorbell — hyper-social #61 §11.2 test 11.
//
// ⛔ THE FAILURE THIS CLOSES, observed 2026-09-15. An interaction lives in its AUTHOR's feed, so the
// target learns of it by replication — and only if they hold that feed. A like from anyone else is
// invisible and NOTHING RINGS. §11.2 wrote that down in advance as the falsification: "A never
// learns ⇒ the doorbell is mandatory, not optional." It is now mandatory.
//
// ⚠ THE EDGE STILL LIVES IN THE LIKER'S FEED. The doorbell carries no content and grants nothing —
// it is one row saying a feed would like to be seen, in a namespace that can never become a record.
// Writing the interaction itself into the target's feed would be authoring as them, which #61 §4
// removed the room primitive to avoid.
//
// ⚠ Bounded by construction: applyKnock keys on the FEED, so a thousand likes from one person are
// one row. That is the same structural bound a knock flood already had, inherited rather than added.
import test from 'brittle'
import fs from '#node:fs'
import os from '#node:os'
import { join } from '#node:path'
import Corestore from 'corestore'
import { RecordStore } from '../lib/record-store.js'
import { Feed } from '../lib/feed-base.js'
import { Peer, POST } from '../lib/peer.js'
import { identity } from './helpers.js'
const tmp = () => fs.mkdtempSync(join(os.tmpdir(), 'favela-ring-'))
const rec = (body, createdAt = '2026-09-15T00:00:00Z') => ({ frontmatter: { createdAt }, body })
async function mkPeer () {
const id = await identity()
const d = await id.device()
const f = new Feed(new Corestore(tmp()), null, { store: new RecordStore(tmp()), optimistic: true })
await f.ready()
await f.addDevice(d.bind(f.deviceKey))
await f.update()
const p = await new Peer(f).ready()
p.bind = (writerKeyHex) => d.bind(writerKeyHex) // how this peer proves who is knocking (node.js sets it in prod)
return p
}
// Bob holds Alice's feed — he must, to have seen the post he is about to like.
async function pair () {
const alice = await mkPeer()
const bob = await mkPeer()
await alice.feed.put(POST, 'p1', rec('worth reacting to'))
await alice.update()
bob.feeds.set(Peer.nameFor(alice.feed.key), alice.feed)
await bob.update()
return { alice, bob, target: { feed: Peer.nameFor(alice.feed.key), collection: POST, name: 'p1' } }
}
const rang = async (peer, who) =>
(await peer.feed.knocks()).some((k) => k.feed === Peer.nameFor(who.feed.key))
test('★★★ a like rings the target\'s doorbell, so they learn of it holding nothing of ours', async (t) => {
const { alice, bob, target } = await pair()
t.absent(await rang(alice, bob), 'nothing has rung yet')
await bob.like(target)
await alice.update()
t.ok(await rang(alice, bob), 'her doorbell names his feed')
// ⛔ And the edge is still HIS. Nothing was authored in her feed.
t.is((await bob.likesOn(target)).length, 1, 'the like lives in the liker\'s feed, as it always did')
})
test('a repost and a reply ring it too', async (t) => {
for (const act of ['repost', 'reply']) {
const { alice, bob, target } = await pair()
if (act === 'repost') await bob.repost(target)
else await bob.reply(target, 'well said', 'r1')
await alice.update()
t.ok(await rang(alice, bob), act + ' rings')
}
})
test('⛔ reacting to my OWN post rings nothing — being told about yourself is not a notification', async (t) => {
const alice = await mkPeer()
await alice.feed.put(POST, 'mine', rec('my own'))
await alice.update()
await alice.like({ feed: Peer.nameFor(alice.feed.key), collection: POST, name: 'mine' })
await alice.update()
t.is((await alice.feed.knocks()).length, 0)
})
test('⚠ it is idempotent — a hundred likes are one row, the bound applyKnock already had', async (t) => {
const { alice, bob, target } = await pair()
await bob.like(target)
await bob.react(target, '🎉')
await bob.repost(target)
await alice.update()
t.is((await alice.feed.knocks()).length, 1, 'one row for the feed, not one per act')
})
test('⛔ undoing does not ring — a retraction is not an approach', async (t) => {
const { alice, bob, target } = await pair()
await bob.like(target)
await alice.update()
await alice.dismissKnock(bob.feed.key) // a knock is keyed by root; the peer maps bob's feed to it
await alice.update()
t.is((await alice.feed.knocks()).length, 0, 'she dismissed it')
await bob.unlike(target)
await alice.update()
t.is((await alice.feed.knocks()).length, 0, 'un-liking does not ring it again')
})
test('⛔ a doorbell we cannot reach never fails the interaction', async (t) => {
const { bob, target } = await pair()
// The feed we hold stops accepting appends — an offline target, a closed base, a broken pipe.
bob.feeds.get(target.feed).knock = async () => { throw new Error('unreachable') }
const name = await bob.like(target)
t.ok(name, 'the like was still written — the edge is ours and does not depend on reaching them')
t.is((await bob.likesOn(target)).length, 1)
})