Follow-up to the event class renames: the names around those classes now
match too.
Quartz (published API, every old name kept as a @Deprecated alias/forwarder):
- nip51Lists packages moved: followList -> starterPack, hashtagList -> interestList,
peopleList -> followSet, labeledBookmarkList -> bookmarkSet. The old packages
keep a Deprecated.kt with typealiases and forwarding extension functions
(ReplaceWith points at the new package).
- LnZapPrivateEvent -> PrivateZapEvent, LnZapReceiptValidator -> ZapReceiptValidator,
ChannelListDiff -> PublicChatListDiff, HashtagListDiff -> InterestListDiff.
- Kind 30063 now indexes NIP-82 release notes for search (never NIP-51 content,
which can hold encrypted private items). searchable-events docs updated.
App code (amethyst, commons, desktopApp, cli; no aliases needed):
- model packages nip51Lists.{hashtagLists,peopleList,labeledBookmarkLists,relayFeeds}
-> {interestLists,followSets,bookmarkSets,favoriteRelays}.
- State/cache/UI names derived from the old event names: HashtagListState ->
InterestListState, PeopleListsState -> FollowSetsState, FollowListsState ->
StarterPacksState, LabeledBookmarkList -> BookmarkSet, RelayFeedListState ->
FavoriteRelayListState, ContactCardsState -> UserAssertionsState, NIP90* view
models/renderers -> Dvm*, LnZap* handlers -> Zap*, SealedRumor* -> Seal*, and
Account.peopleLists/followLists/hashtagList -> followSets/starterPacks/interestList.
- String literals were left untouched, so preference keys and @SerialName values
stored on disk are unchanged.
- The generic PeopleList UI model (shared by follow sets and starter packs) and the
EmojiPackSelection route (which shows a kind 30030 pack) keep their names.
Docs: plans, brainstorms, changelogs, skills and READMEs use the new names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015VvJ8XpeYqBe8bYT3bJ7UD
3.4 KiB
TODO: Deletion Requests (kind 5) for Gift Wraps
Status: queued — none of the four steps landed:
HostStubstill lacks arecipientfield andDeletionIndexhas no gift-wrap recipient-authored handling; header itself reads "Open — not implemented". Audited 2026-06-30.
Date: 2026-06-12
Status: Open — design agreed, not implemented
Modules: quartz (DeletionIndex), amethyst (LocalCache)
The special case
Gift wraps (kind 1059) are signed by a discarded throwaway key, so the
normal NIP-09 rule — a deletion only applies when its author equals the
target's author — can never match a wrap. The intended rule: a kind-5
authored by the wrap's p tag (the recipient) may delete it. A
recipient deleting their own received wrap is also the only deletion a
client can express without leaking the private rumor id (the rumor id
must never appear in a public kind-5).
Current behavior (verified 2026-06-12)
DeletionIndex is strictly author-keyed (DeletionRequest(targetId, deleterPubkey)), the live-delete path requires deleteNote.author == deletion.pubKey, and no downward cascade (wrap → seal → rumor) exists —
deleteEnvelopes only walks upward via Note.rumorHost.
| Deletion e-tags | authored by | live message deleted? | blocks later insert? |
|---|---|---|---|
| wrap id | recipient (p tag) | no (key mismatch + no cascade + wrap note usually GC'd) | no (tombstone keyed (wrapId, recipient), check uses (wrapId, throwawayKey)) |
| seal id | sender | seal note only; message survives (no cascade) | yes — accidental: seals are sender-signed, and GiftWrapEventHandler gates the unwrap on justConsume(seal) |
| rumor id | sender | yes (private un-react path; deleteEnvelopes cascades upward) |
yes |
Only the rumor-id direction works; the wrap-id direction — the one the special case describes — does nothing.
Implementation sketch
-
Insertion blocking (quartz, small): in
DeletionIndex.hasBeenDeleted, when the event is aGiftWrapEvent, additionally checkDeletionRequest(event.id, event.recipientPubKey()). A recipient-authored tombstone then blocks the wrap injustConsumebefore it is ever unwrapped, which blocks the message. -
Recipient on the stub (commons, tiny): add
recipient: HexKey?toHostStub(one shared-string reference per rumor), populated fromGiftWrapEvent.recipientPubKey()at unseal time, so the validation below works after the wrap note is GC'd. -
Live cascade (amethyst
LocalCache.consume(DeletionRequestEvent)): the wrap note that knew itsinnerEventIdis GC'd by the time a deletion arrives, so find the rumor by reverse lookup: scan notes fornote.rumorHost?.id == deletedId(precedent: the addressable pass in the same function already does a fullnotes.forEach; deletions are rare). Validatedeletion.pubKey == note.rumorHost.recipient, thendeleteNote(rumor)— envelope cleanup and chatroom removal already follow from the existing deleted-notes pipeline. -
Tests: block-before-unwrap (tombstone first, wrap second → message never materializes) and delete-after-unwrap (message in a chatroom, recipient-authored kind-5 for the wrap id → rumor and envelopes gone).
Note: only wraps addressed to the local user are ever in the cache, so the live-cascade case in practice means "another of my devices retracted a DM" — rare, not a hot path.