mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
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
65 lines
3.4 KiB
Markdown
65 lines
3.4 KiB
Markdown
# TODO: Deletion Requests (kind 5) for Gift Wraps
|
|
|
|
> **Status:** queued — none of the four steps landed: `HostStub` still lacks a `recipient` field and `DeletionIndex` has 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
|
|
|
|
1. **Insertion blocking (quartz, small):** in `DeletionIndex.hasBeenDeleted`,
|
|
when the event is a `GiftWrapEvent`, additionally check
|
|
`DeletionRequest(event.id, event.recipientPubKey())`. A
|
|
recipient-authored tombstone then blocks the wrap in `justConsume`
|
|
before it is ever unwrapped, which blocks the message.
|
|
|
|
2. **Recipient on the stub (commons, tiny):** add `recipient: HexKey?` to
|
|
`HostStub` (one shared-string reference per rumor), populated from
|
|
`GiftWrapEvent.recipientPubKey()` at unseal time, so the validation
|
|
below works after the wrap note is GC'd.
|
|
|
|
3. **Live cascade (amethyst `LocalCache.consume(DeletionRequestEvent)`):** the
|
|
wrap note that knew its `innerEventId` is GC'd by the time a deletion
|
|
arrives, so find the rumor by reverse lookup: scan notes for
|
|
`note.rumorHost?.id == deletedId` (precedent: the addressable pass in
|
|
the same function already does a full `notes.forEach`; deletions are
|
|
rare). Validate `deletion.pubKey == note.rumorHost.recipient`, then
|
|
`deleteNote(rumor)` — envelope cleanup and chatroom removal already
|
|
follow from the existing deleted-notes pipeline.
|
|
|
|
4. **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.
|