// ⛔ A knock written by 0.0.57 is not a knock under 0.0.60, and an upgraded node must agree.
//
// MEASURED 2026-10-12 (scratch compat/reopen.mjs, the installed 0.0.57 tree vs this one): reopening a
// 0.0.57 store under 0.0.60 does NOT reindex the view. The old apply's row stays as written,
// `__knocks__/<their feed key> → { device }`, and the new reader took the key segment for an identity
// root: knocks() returned { root: <a feed key>, feed: undefined }. A FRESH 0.0.60 node computing the
// same log returns no knock at all, because the op carries no proof. So two nodes on the same version
// showed different doorbells, and the upgraded one showed a knock that could never be admitted
// (Peer._knockRootFor matches on `feed`, which the row does not have).
//
// Live at the time: stortron's feed held one such row and fleeky's another. No listings existed
// anywhere, so only knocks are affected.
//
// The rule: a row without the `feed` field the proven-knock apply always writes is a legacy, unproven
// knock, and the read side drops it exactly as a fresh apply would. The knocker asks again, with proof.
import test from 'brittle'
import b4a from 'b4a'
import { Feed } from '../lib/feed-base.js'
import { KNOCKS } from '../lib/feed.js'
const ROOT = 'aa'.repeat(32)
const THEIR_FEED = 'ab'.repeat(32)
const LEGACY_FEED = '5271cdd4b5a4c478ce3274f1fb4f1be2b944dfc2e69a968dd6d23209ff3eda23'
const DEVICE = '10505577bfa35efbb2ef244968b2662a7a817932cb6ccc1532deddafb07cb6f5'
// A Feed over a fixed view: the bytes an upgraded store actually holds, read by this version's code.
function feedOver (rows) {
const sorted = Object.entries(rows).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
const bee = {
async get (k) { const v = rows[b4a.toString(k, 'utf8')]; return v === undefined ? null : { value: b4a.from(JSON.stringify(v)) } },
async * createReadStream ({ gte, lt } = {}) {
for (const [k, v] of sorted) {
const kb = b4a.from(k, 'utf8')
if (gte && b4a.compare(kb, gte) < 0) continue
if (lt && b4a.compare(kb, lt) >= 0) continue
yield { key: kb, value: b4a.from(JSON.stringify(v)) }
}
}
}
const f = Object.create(Feed.prototype)
f.base = { view: bee }
return f
}
test('⛔ a 0.0.57 knock row in an upgraded view is not reported as a knock', async (t) => {
const f = feedOver({ [KNOCKS + '/' + LEGACY_FEED]: { device: DEVICE } })
t.alike(await f.knocks(), [], 'no proven root, so no knock: the same answer a fresh node computes')
})
test('a proven knock beside a legacy row is still reported, and only it', async (t) => {
const f = feedOver({
[KNOCKS + '/' + LEGACY_FEED]: { device: DEVICE },
[KNOCKS + '/' + ROOT]: { device: DEVICE, feed: THEIR_FEED }
})
t.alike(await f.knocks(), [{ root: ROOT, feed: THEIR_FEED, device: DEVICE }])
})