fix: detach channel in removeFromCache to mirror deleteNote

removeFromCache() relied solely on note.inGatherers to detach a pruned
note from its channels, while deleteNote() additionally resolved the
channel via getAnyChannel() and removed the note there too. inGatherers
is normally authoritative (Channel.addNote always calls addGatherer), so
this is defensive rather than a confirmed live leak — but it closes the
divergence so both removal paths detach channels identically. Guards
against any future consume path that adds a note to a getAnyChannel-
resolvable channel without the gatherer link: otherwise the note would
linger in the channel's notes map after leaving the cache, leaking it and
letting a relay echo mint a duplicate Note with the same id.

https://claude.ai/code/session_01RqJPYzmjb1pR3NBeH2yY3s
This commit is contained in:
Claude
2026-06-04 16:17:14 +00:00
parent 11591826f0
commit c4bfdc72dc
@@ -2785,6 +2785,13 @@ object LocalCache : ILocalCache, ICacheProvider {
note.inGatherers?.forEach { it.removeNote(note) }
// Mirror deleteNote(): inGatherers is normally authoritative for channel
// membership (Channel.addNote always calls note.addGatherer), but resolve
// the channel from the event as a belt-and-suspenders detach so a note can
// never linger in a channel's notes map after it leaves the cache — that
// would leak the note and let a relay echo mint a duplicate with the same id.
getAnyChannel(note)?.removeNote(note)
val noteEvent = note.event
if (noteEvent is ReportEvent) {