| … | … | @@ -0,0 +1,57 @@ |
| 1 | // ⛔ A knock written by 0.0.57 is not a knock under 0.0.60, and an upgraded node must agree. |
| 2 | // |
| 3 | // MEASURED 2026-10-12 (scratch compat/reopen.mjs, the installed 0.0.57 tree vs this one): reopening a |
| 4 | // 0.0.57 store under 0.0.60 does NOT reindex the view. The old apply's row stays as written, |
| 5 | // `__knocks__/<their feed key> → { device }`, and the new reader took the key segment for an identity |
| 6 | // root: knocks() returned { root: <a feed key>, feed: undefined }. A FRESH 0.0.60 node computing the |
| 7 | // same log returns no knock at all, because the op carries no proof. So two nodes on the same version |
| 8 | // showed different doorbells, and the upgraded one showed a knock that could never be admitted |
| 9 | // (Peer._knockRootFor matches on `feed`, which the row does not have). |
| 10 | // |
| 11 | // Live at the time: stortron's feed held one such row and fleeky's another. No listings existed |
| 12 | // anywhere, so only knocks are affected. |
| 13 | // |
| 14 | // The rule: a row without the `feed` field the proven-knock apply always writes is a legacy, unproven |
| 15 | // knock, and the read side drops it exactly as a fresh apply would. The knocker asks again, with proof. |
| 16 | |
| 17 | import test from 'brittle' |
| 18 | import b4a from 'b4a' |
| 19 | import { Feed } from '../lib/feed-base.js' |
| 20 | import { KNOCKS } from '../lib/feed.js' |
| 21 | |
| 22 | const ROOT = 'aa'.repeat(32) |
| 23 | const THEIR_FEED = 'ab'.repeat(32) |
| 24 | const LEGACY_FEED = '5271cdd4b5a4c478ce3274f1fb4f1be2b944dfc2e69a968dd6d23209ff3eda23' |
| 25 | const DEVICE = '10505577bfa35efbb2ef244968b2662a7a817932cb6ccc1532deddafb07cb6f5' |
| 26 | |
| 27 | // A Feed over a fixed view: the bytes an upgraded store actually holds, read by this version's code. |
| 28 | function feedOver (rows) { |
| 29 | const sorted = Object.entries(rows).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)) |
| 30 | const bee = { |
| 31 | async get (k) { const v = rows[b4a.toString(k, 'utf8')]; return v === undefined ? null : { value: b4a.from(JSON.stringify(v)) } }, |
| 32 | async * createReadStream ({ gte, lt } = {}) { |
| 33 | for (const [k, v] of sorted) { |
| 34 | const kb = b4a.from(k, 'utf8') |
| 35 | if (gte && b4a.compare(kb, gte) < 0) continue |
| 36 | if (lt && b4a.compare(kb, lt) >= 0) continue |
| 37 | yield { key: kb, value: b4a.from(JSON.stringify(v)) } |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | const f = Object.create(Feed.prototype) |
| 42 | f.base = { view: bee } |
| 43 | return f |
| 44 | } |
| 45 | |
| 46 | test('⛔ a 0.0.57 knock row in an upgraded view is not reported as a knock', async (t) => { |
| 47 | const f = feedOver({ [KNOCKS + '/' + LEGACY_FEED]: { device: DEVICE } }) |
| 48 | t.alike(await f.knocks(), [], 'no proven root, so no knock: the same answer a fresh node computes') |
| 49 | }) |
| 50 | |
| 51 | test('a proven knock beside a legacy row is still reported, and only it', async (t) => { |
| 52 | const f = feedOver({ |
| 53 | [KNOCKS + '/' + LEGACY_FEED]: { device: DEVICE }, |
| 54 | [KNOCKS + '/' + ROOT]: { device: DEVICE, feed: THEIR_FEED } |
| 55 | }) |
| 56 | t.alike(await f.knocks(), [{ root: ROOT, feed: THEIR_FEED, device: DEVICE }]) |
| 57 | }) |