| … | … | @@ -137,3 +137,65 @@ test('⛔⛔ watch() fires when a held feed advances — real append, real pipe' |
| 137 | 137 | const { rows } = await new FavelaFeed({ feed: bridge }).ownPosts() |
| 138 | 138 | t.ok(rows.some((r) => r.name === '01k2later'), 'the record the trigger was announcing') |
| 139 | 139 | }) |
| 140 | |
| 141 | // ⛔⛔ A KNOCK MUST NOT STRAND THE BRIDGE BEHIND THE OWNER (2026-10-12). |
| 142 | // |
| 143 | // FeedHolder opened every held feed with favela-peer's default of `optimistic: false`. Every favela |
| 144 | // node opens feeds with `optimistic: true` (favela-peer node.js: "the doorbell is on by default"), and |
| 145 | // the owner's apply ACKS a stranger's knock. So the owner's log links past the knock and a holder that |
| 146 | // refuses optimistic blocks can never follow it: the two compute different views of one log. MEASURED |
| 147 | // live on fleeky's feed: the bridge stuck at 106 of 113 applied, the newest posts never reaching the |
| 148 | // fediverse. (On autobee 2.1.1 the same knock also pinned a core; that half is favela-peer's |
| 149 | // holder-after-knock.test.js.) |
| 150 | // |
| 151 | // So this goes through FeedHolder.hold() itself — the place the option is chosen — and asserts the |
| 152 | // bridge sees what the owner wrote AFTER a stranger knocked. |
| 153 | test('⛔⛔ a knock on a held feed does not strand the bridge behind the owner', async (t) => { |
| 154 | const { FeedHolder } = await import('../lib/feed-holder.js') |
| 155 | const records = tmp() |
| 156 | |
| 157 | // The owner, opened the way a favela node opens it. |
| 158 | const owner = new Feed(new Corestore(tmp()), null, { store: new RecordStore(records), optimistic: true }) |
| 159 | await owner.ready() |
| 160 | await owner.addDevice(await attest(owner)) |
| 161 | await owner.update() |
| 162 | |
| 163 | // A stranger knocks from their own machine: a reader of the owner's log, a writer of nothing in it. |
| 164 | const stranger = new Feed(new Corestore(tmp()), null, { store: new RecordStore(tmp()), optimistic: true }) |
| 165 | await stranger.ready() |
| 166 | const ownerOnStranger = new Feed(new Corestore(tmp()), owner.key, { store: new RecordStore(records), optimistic: true }) |
| 167 | await ownerOnStranger.ready() |
| 168 | const unpipeStranger = pipe(owner, ownerOnStranger) |
| 169 | |
| 170 | await until(async () => { await ownerOnStranger.update(); return (await ownerOnStranger.devices()).length > 0 }, { timeout: 10000 }) |
| 171 | await ownerOnStranger.knock(stranger.key) |
| 172 | t.ok(await until(async () => { await owner.update(); return (await owner.knocks()).length }, { timeout: 10000 }), |
| 173 | 'setup: the stranger\'s knock landed in the owner\'s feed') |
| 174 | |
| 175 | await owner.put('post', 'after-knock', { frontmatter: { createdAt: '2026-10-12T00:00:00Z' }, body: 'written after the knock\n' }) |
| 176 | await owner.update() |
| 177 | |
| 178 | // The bridge holds the owner's feed through FeedHolder, with an inert swarm: replication is piped by |
| 179 | // hand, so this test is about the hold, not the DHT. |
| 180 | const swarm = { connections: [], on () {}, join () {}, async leave () {} } |
| 181 | const holder = new FeedHolder({ storage: tmp(), swarm }) |
| 182 | await holder.ready() |
| 183 | const held = await holder.hold(owner.key) |
| 184 | const unpipeBridge = pipe(owner, held.feed) |
| 185 | |
| 186 | t.teardown(async () => { |
| 187 | unpipeStranger(); unpipeBridge() |
| 188 | await Promise.all([owner.close(), stranger.close(), ownerOnStranger.close()]) |
| 189 | await holder.close() |
| 190 | }) |
| 191 | |
| 192 | const settles = (p, ms) => Promise.race([p.then(() => true), new Promise((r) => setTimeout(() => r(false), ms))]) |
| 193 | const seen = await until(async () => { |
| 194 | if (!(await settles(held.feed.update(), 2000))) return null |
| 195 | return (await held.feed.pointers('post')).some((p) => p.name === 'after-knock') || null |
| 196 | }, { timeout: 15000 }) |
| 197 | |
| 198 | t.ok(seen, 'the bridge sees what the owner wrote after the knock') |
| 199 | t.absent(held.feed.base.writable && (await held.feed.devices()).includes(held.feed.deviceKey), |
| 200 | '⛔ and holding still grants the bridge nothing on the roster') |
| 201 | }) |