refactor(shared): unify gateway event payloads across server and bot-sdk #106

Merged
icub3d merged 1 commit from feature/82-gateway-event-payloads into main 2026-07-11 03:14:05 +00:00
Owner

Completes the final checkbox of #82 (gateway event payloads), following #105 which did the REST wire types and SDK client commands.

Problem

The server broadcaster and the bot-sdk each defined their own gateway event payload types, and they had drifted. serde only ignores extra fields, not missing required ones, so this wasn't cosmetic:

  • Silent field loss: bot-sdk's MessageEvent/ChannelEvent/RoleEvent/MemberEvent were trimmed subsets — a bot's on_message never saw attachments/reactions/thread/deleted.
  • Outright decode failures: MEMBER_LEAVE ({user_id, pubkey, left_at}), MEMBER_BAN ({pubkey, banned_by, reason, banned_at} — no user_id), MEMBER_UPDATE (no is_bot/joined_at), and THREAD_MESSAGE_CREATE ({thread_id, message} vs. the SDK decoding ThreadEventData) did not match what the server sends, so those events failed to decode in every bot.

Fix — one definition per payload

  • New shared::gateway payload structs: ThreadMessageData, MessageDeleteData, DeletedId (shared by channel + role delete), MemberLeaveData, MemberKickData, MemberBanData, MemberUpdateData. Resource-carrying events reuse the shared::rest types (Message, Channel, Role, Member).
  • Server handlers construct these typed structs instead of ad-hoc serde_json::json! literals and local one-off structs, so the broadcast shape is now compile-time checked against the shared definition.
  • bot-sdk Event enum, handler-type aliases, Context::reply, and the example bots (auditbot/modbot/welcomebot) all consume the shared types. bot-sdk keeps zero local event copies. Member join/leave/kick/ban/update now carry distinct, correct payload types (previously three of them shared one trimmed MemberEvent).

Wire compatibility

The server's outgoing JSON is byte-identical to before — I checked each broadcast against the React/Tauri client's actual field reads (client/src/stores/members.ts, threadStore.ts). This PR only fixes the bot-sdk decode side. A new shared test locks the wire field names of the small event payloads so they can't silently drift again.

Also removes a stray unused serde::Serialize import in profiles/handlers.rs that surfaced from the #104 + #105 merge interaction (would fail clippy -D warnings on current main).

Verification

  • cargo build --workspace — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --workspace — all pass (server 131, shared 10, SDK 8, bot-sdk 10 incl. rewritten member-event decode tests, example bots build)

Closes #82

🤖 Generated with Claude Code

Completes the final checkbox of #82 (gateway event payloads), following #105 which did the REST wire types and SDK client commands. ## Problem The server broadcaster and the bot-sdk each defined their own gateway event payload types, and they had drifted. serde only ignores **extra** fields, not missing required ones, so this wasn't cosmetic: - **Silent field loss:** bot-sdk's `MessageEvent`/`ChannelEvent`/`RoleEvent`/`MemberEvent` were trimmed subsets — a bot's `on_message` never saw `attachments`/`reactions`/`thread`/`deleted`. - **Outright decode failures:** `MEMBER_LEAVE` (`{user_id, pubkey, left_at}`), `MEMBER_BAN` (`{pubkey, banned_by, reason, banned_at}` — no `user_id`), `MEMBER_UPDATE` (no `is_bot`/`joined_at`), and `THREAD_MESSAGE_CREATE` (`{thread_id, message}` vs. the SDK decoding `ThreadEventData`) did **not** match what the server sends, so those events failed to decode in every bot. ## Fix — one definition per payload - **New `shared::gateway` payload structs:** `ThreadMessageData`, `MessageDeleteData`, `DeletedId` (shared by channel + role delete), `MemberLeaveData`, `MemberKickData`, `MemberBanData`, `MemberUpdateData`. Resource-carrying events reuse the `shared::rest` types (`Message`, `Channel`, `Role`, `Member`). - **Server** handlers construct these typed structs instead of ad-hoc `serde_json::json!` literals and local one-off structs, so the broadcast shape is now compile-time checked against the shared definition. - **bot-sdk** `Event` enum, handler-type aliases, `Context::reply`, and the example bots (auditbot/modbot/welcomebot) all consume the shared types. bot-sdk keeps **zero** local event copies. Member join/leave/kick/ban/update now carry distinct, correct payload types (previously three of them shared one trimmed `MemberEvent`). ## Wire compatibility The server's outgoing JSON is **byte-identical** to before — I checked each broadcast against the React/Tauri client's actual field reads (`client/src/stores/members.ts`, `threadStore.ts`). This PR only fixes the bot-sdk **decode** side. A new `shared` test locks the wire field names of the small event payloads so they can't silently drift again. Also removes a stray unused `serde::Serialize` import in `profiles/handlers.rs` that surfaced from the #104 + #105 merge interaction (would fail `clippy -D warnings` on current main). ## Verification - `cargo build --workspace` — clean - `cargo clippy --workspace --all-targets -- -D warnings` — clean - `cargo test --workspace` — all pass (server 131, shared 10, SDK 8, bot-sdk 10 incl. rewritten member-event decode tests, example bots build) Closes #82 🤖 Generated with [Claude Code](https://claude.com/claude-code)
refactor(shared): define gateway event payloads once, kill server/bot drift
Some checks failed
Security Audit / Gitleaks (Secrets) (pull_request) Successful in 6s
Security Audit / Trivy (SCA & IaC) (pull_request) Failing after 16s
Security Audit / PNPM Audit (JS SCA) (pull_request) Failing after 23s
CI / Client — test & lint (pull_request) Successful in 45s
Security Audit / Semgrep (SAST) (pull_request) Failing after 1m8s
CI / Server — test & lint (pull_request) Successful in 2m50s
Security Audit / Cargo Audit (Rust SCA) (pull_request) Failing after 3m12s
1aed69fd5d
Completes the last checkbox of #82. The server broadcaster and the bot-sdk
each defined their own gateway event payload types, and they had silently
drifted: the bot-sdk's `MessageEvent`/`ChannelEvent`/`RoleEvent`/`MemberEvent`
were trimmed subsets that dropped fields, and worse, several bot-sdk decode
types did not match the server's wire shape at all — `MEMBER_LEAVE`,
`MEMBER_BAN`, `MEMBER_UPDATE`, and `THREAD_MESSAGE_CREATE` would fail to
decode in any bot, because serde only ignores *extra* fields, not missing
required ones.

Define each gateway payload once in `shared` and have both ends use it:
- New payload structs in `shared::gateway`: `ThreadMessageData`,
  `MessageDeleteData`, `DeletedId`, `MemberLeaveData`, `MemberKickData`,
  `MemberBanData`, `MemberUpdateData`. The resource-carrying events reuse the
  `shared::rest` types (`Message`, `Channel`, `Role`, `Member`).
- Server handlers now construct these typed structs instead of ad-hoc
  `serde_json::json!` literals and local one-off structs, so the broadcast
  shape is checked at compile time.
- bot-sdk's `Event` enum, handler types, `Context::reply`, and the example
  bots (auditbot/modbot/welcomebot) consume the shared types; bot-sdk no
  longer keeps any local copies. Member join/leave/kick/ban/update now carry
  distinct, correct payload types.

The server's outgoing JSON is byte-identical to before (verified against the
current React/Tauri client's field usage), so this is wire-compatible; the
change only fixes the bot-sdk decode side. A new `shared` test locks the wire
field names of the small event payloads.

Also removes a stray unused `serde::Serialize` import in profiles/handlers.rs
that surfaced from the #104+#105 merge interaction.

Closes #82

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
icub3d merged commit 8ea42c5e48 into main 2026-07-11 03:14:05 +00:00
icub3d deleted branch feature/82-gateway-event-payloads 2026-07-11 03:14:05 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
icub3d/decentcom!106
No description provided.