fix(snapshot): NIP-09-filter beachtet zeitliche reihenfolge

Per NIP-09 darf ein deletion nur events mit created_at <= deletion.created_at
loeschen. Vorher wurde ein re-publizierter post nach geloeschtem vorgaenger
stumm wegfiltern. Code-review-feedback aus etappe 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jörg Lohrer 2026-04-28 08:19:20 +02:00
parent 0755498937
commit 848cdf763e
2 changed files with 35 additions and 3 deletions

View File

@ -5,18 +5,23 @@ export function filterDeleted(
deletions: SignedEvent[],
authorPubkey: string,
): SignedEvent[] {
const deletedCoords = new Set<string>()
const deletedAtByCoord = new Map<string, number>()
for (const del of deletions) {
if (del.kind !== 5) continue
if (del.pubkey !== authorPubkey) continue
for (const tag of del.tags) {
if (tag[0] === 'a' && tag[1]) deletedCoords.add(tag[1])
if (tag[0] !== 'a' || !tag[1]) continue
const previous = deletedAtByCoord.get(tag[1])
if (previous === undefined || del.created_at > previous) {
deletedAtByCoord.set(tag[1], del.created_at)
}
}
}
return events.filter((ev) => {
const d = ev.tags.find((t) => t[0] === 'd')?.[1]
if (!d) return true
const coord = `${ev.kind}:${ev.pubkey}:${d}`
return !deletedCoords.has(coord)
const deletedAt = deletedAtByCoord.get(coord)
return deletedAt === undefined || ev.created_at > deletedAt
})
}

View File

@ -28,3 +28,30 @@ Deno.test('filterDeleted ignoriert kind:5 fremder pubkeys', () => {
const out = filterDeleted([post('alive', 'a')], [fremde], 'P')
assertEquals(out.length, 1)
})
Deno.test('filterDeleted: re-publizierter post (post.created_at > deletion.created_at) bleibt erhalten', () => {
const oldDelete: SignedEvent = {
id: 'del', pubkey: 'P', created_at: 100, kind: 5, sig: 's', content: '',
tags: [['a', '30023:P:resurrected']],
}
const newPost: SignedEvent = {
id: 'new', pubkey: 'P', created_at: 200, kind: 30023, sig: 's', content: '',
tags: [['d', 'resurrected']],
}
const out = filterDeleted([newPost], [oldDelete], 'P')
assertEquals(out.length, 1)
assertEquals(out[0].id, 'new')
})
Deno.test('filterDeleted: post mit created_at <= deletion.created_at wird entfernt', () => {
const newDelete: SignedEvent = {
id: 'del', pubkey: 'P', created_at: 200, kind: 5, sig: 's', content: '',
tags: [['a', '30023:P:dead']],
}
const oldPost: SignedEvent = {
id: 'old', pubkey: 'P', created_at: 100, kind: 30023, sig: 's', content: '',
tags: [['d', 'dead']],
}
const out = filterDeleted([oldPost], [newDelete], 'P')
assertEquals(out.length, 0)
})