Files
amethyst/commons
Claude 1c9585e5e4 fix(marmot): look up KeyPackageBundle by Nostr event id + add MarmotDbg logs
While adding diagnostic logs to trace why invitees were receiving
nothing on Marmot group adds, I found the actual root cause: a
fundamental hash mismatch between the value the Welcome event carries
and the value the receiver looks up by.

### The bug

A Marmot WelcomeEvent (kind:444) carries a tag `["e", <eventId>]`
referencing the kind:30443 KeyPackage event that was consumed — see
`KeyPackageEventTag` and `MarmotWelcomeSender.wrapWelcome`. That
`<eventId>` is the *Nostr event id* (a hash of the signed event JSON).

`MarmotInboundProcessor.processWelcome` then called
`keyPackageRotationManager.findBundleByRef(hexToBytes(eventId))`,
which compares each stored bundle's `keyPackage.reference()` —
**the MLS-spec KeyPackageRef, an entirely different hash computed by
`MlsCryptoProvider.refHash("MLS 1.0 KeyPackage Reference", encoded)`
over the TLS-encoded KeyPackage**.

Those two values are never equal, so the lookup ALWAYS missed and
every invitee path returned `WelcomeResult.Error("No matching
KeyPackageBundle found")`. Combined with the previous in-memory-only
storage, this is why nothing ever appeared on the invitee's screen.

### The fix

`KeyPackageRotationManager` now also indexes bundles by the Nostr
event id of the corresponding kind:30443 event:

- `eventIdToSlot: Map<HexKey, String>` — populated by
  `recordPublishedEventId(slot, eventId)`, called from
  `MarmotManager.generateKeyPackageEvent` immediately after signing
  the event template (which is when the event id is first known).
- `findBundleByEventId(eventId)` — looks up the slot via the new
  index, then returns the bundle.
- `markConsumedByEventId(eventId)` — symmetric consume-by-event-id
  for the welcome receive path.
- The persisted snapshot format is bumped from v1 → v2 to include
  the eventId map. v1 snapshots are still readable (loaded as if
  the eventId map were empty); republishing a KeyPackage will refill
  it. Also cleans the index when slots are consumed.

`MarmotInboundProcessor.processWelcome` now uses
`findBundleByEventId(keyPackageEventId)` instead of
`findBundleByRef(hexToBytes(...))`, and `markConsumedByEventId` for
the consume call. The dead `hexToBytes` helper + import are removed.

### Diagnostic logging

Added `MarmotDbg`-tagged logs across the entire add-member / send /
receive chain so the user can `adb logcat -s MarmotDbg` to see
exactly what's being sent and what's being received:

- `Account.fetchKeyPackageAndAddMember` — querying relays, KP found
  / not found, kind, author
- `Account.addMarmotGroupMember` — commit publish target, welcome
  delivery presence, full relay union with sources
- `Account.sendMarmotGroupMessage` — group, inner kind, target relays
- `Account.publishMarmotKeyPackage` + `ensureMarmotKeyPackagePublished`
  — publish target relays, signed event id
- `DecryptAndIndexProcessor.processNewGiftWrap` — gift wrap unwrap
  result, inner kind, route to Marmot welcome handler
- `DecryptAndIndexProcessor.processMarmotWelcome` — manager null,
  WelcomeResult, synced metadata, group id
- `DecryptAndIndexProcessor.GroupEventHandler.add` — kind:445 arrival,
  membership check, processGroupEvent result, decrypt path
- `MarmotInboundProcessor.processWelcome` — eventId lookup,
  bundle-not-found details, MLS join success
- `MarmotGroupEventsEoseManager.updateFilter` — active groups,
  per-group relay set, fallback usage, total emitted filters
- `AccountGiftWrapsEoseManager.updateFilter` — pubkey + dmRelay set
  used for kind:1059 subscription

Compile-checked: `:quartz:compileKotlinJvm`, `:commons:compileKotlinJvm`,
`:amethyst:compilePlayDebugKotlin` — BUILD SUCCESSFUL.
2026-04-15 23:04:58 +00:00
..
2026-04-06 09:22:21 -04:00