fix(api): roundtrip receipts and unify presence activity models

Merged
JL
25d ago
+328-248

ReceiptType serialized ReadSelf as "ReadSelf" but deserialized it as Other("ReadSelf"); serialized Other objects could not be read at all. Derive matching Serde implementations while preserving the existing externally tagged JSON and enum order. Wire parsing remains separate, including absent delivery.

This also removes overlapping availability/activity models from the same core module (report F02/R02 and F12/R11). One semantic ChatActivity now reaches the sender, parser, event bus, and callback without mapping through a second pair of enums. The three outgoing stanzas and existing parser fallbacks remain unchanged.

Migration

  • wacore::types::presence::Presence → PresenceStatus. The SDK's existing whatsapp_rust::PresenceStatus path re-exports that same core type.
  • PresenceUpdate.unavailable → status: PresenceStatus. Compare with PresenceStatus::Unavailable; pass status directly to client.presence().set.
  • ChatStateType::{Composing, Recording, Paused} → ChatActivity::{Typing, RecordingAudio, Idle}, exported from the SDK and wacore::types::presence.
  • ReceivedChatState and the ChatPresence/ChatPresenceMedia pair are removed. ChatstateStanza.state, ChatPresenceUpdate.state, and ChatStateEvent.state all use ChatActivity; ChatPresenceUpdate.media is removed.
  • ChatActivity's Serde values are semantic typing, recording_audio, and idle. Its explicit child-node projections encode/decode wire payloads; there is no misleading literal recording wire string.
  • ReceiptType::variant_name is removed. Repository consumers only used it in the removed serializer and its tests. Use Serde for persisted JSON and parse/as_wire_str for protocol values.
  • Event identities, participants, last-seen timestamps, and prior unknown-input fallbacks are preserved. The existing parser was already lossy for unknown/additional child attributes; this PR does not claim raw-node roundtrips.

The public event JSON changes are intentionally breaking, unlike ReceiptType's preserved JSON. For persisted or relayed event payloads, migrate these field fragments while keeping source/identity and last-seen fields:

Old event fieldsNew event fields
"state":"composing","media":"""state":"typing"
"state":"composing","media":"audio""state":"recording_audio"
"state":"paused","media":"""state":"idle"
"unavailable":false"status":"available"
"unavailable":true"status":"unavailable"

ReceiptType's "ReadSelf", "Delivered", and {"Other":"..."} representations do not change. Deserialize that JSON with Serde; use ReceiptType::parse for protocol strings.

No dependency, manifest, registry, workflow, or compatibility-alias additions. External migration names have been sent to the campaign's documentation owner.

Protocol evidence

Read the original modules extracted with Acorn, after verifying all 516 JS files / 77,347,149 bytes against whatspec 1a441f0, schema 4.3.0, WA Web 2.3000.1045368834. Set hash 99a75bd7a4961e15051172c8b99fc460d57e58f7eb47ea9c46824657dd083e00; archive SHA-256 2edd4b3f8dae50b503e0b15920adc774e2680cdb542396d57608bb0374196ee1.

Byte offsets below are start-inclusive/end-exclusive in untouched bundles:

  • Bundle 657a32007737e50b0cb0292ce9c364e21e48291b558129753b25e3ec2a4c13b7: WASendChatStateProtocol 2051041–2051533 maps semantic states; WASmaxOutChatstateComposingMixin 2048926–2049312 emits composing with optional audio; WASmaxOutChatstatePausedMixin 2049314–2049598 emits paused; WASmaxChatstateClientNotificationRPC 2050575–2051039 sends through WAComms.
  • Bundle 034314e49fa571a8e2e8491843a15536326b4a47d1f66ac7704d2332ee1b391e: WAHandleChatStateProtocol 672122–672345 maps to typing/recording_audio/idle; WACreateHandleChatState 677362–678305 forwards user/group identity; WAWebHandleChatState 1006744–1007991 forwards activity and returns NO_ACK. Duplicate incoming definitions in bundle 38cc03362dc9c17195fd420f16b8ada76b9abefb2da1be675f57e093c2da8495 have identical module hashes.
  • The Web's unknown-input validation is stricter than the existing Rust fallback. That difference is outside these API changes.

This is static evidence for the pinned Web version, not a claim about authenticated current-server acceptance.

Validation

  • Reproduced the original bug from baseline source: ReadSelf roundtrips to Other("ReadSelf"). Seven focused receipt tests pass after the fix, covering all variants, empty payloads, and collisions.
  • Added exact wire child projection tests, captured SDK send tests, availability identity/last-seen checks, and a downstream receipt/relay contract in the existing api-consumer event module.
  • Existing callback/event-bus and E2E call sites migrated.
  • Local core: 1,736 tests passed, one skipped. SDK: 2,473 tests passed, three skipped; all 16 focused presence/chatstate tests passed.
  • Existing api-consumer: native profile 46 tests passed; MSRV 1.94.1 sdk/core/contracts profile 23 passed; 61 consumer doctests passed; WASM sdk/core/contracts/js library check passed. WASM reports two existing unused-qualification warnings in unchanged request.rs/upload.rs.
  • Clippy passed for wacore and whatsapp-rust, all targets, with warnings denied. Their doctests passed: 58 passed, 26 ignored. Formatting and diff checks passed.
  • Greptile reviewed final head 8354a04d with confidence 5/5 and no findings. CodeRabbit completed its review with one documentation finding for the breaking event JSON. Verified the fix in docs commit 6c7fbdfd93df53018c7f840cfcadf1decbaff06f, linked it in the review, and resolved the thread; CodeRabbit approved at 23:02 UTC. All 20 current-head Rust CI jobs passed, including all 11 feature-matrix legs. All nine current-head workflow runs finished successfully (the informational SemVer job failure is detailed below). No unresolved review threads remain. Ready for the campaign owner to remove draft; no merge performed.
  • Current-head Miri (all three jobs), WASM, and independent native/MSRV API-consumer jobs passed. CodSpeed's four jobs passed; its completed comparison reports 842 unchanged and 12 skipped benchmarks. Runner differences and syscall exclusions limit interpretation; no performance gain is claimed.
  • Coordinated documentation: whatsapp-rust-docs#616. Its examples and API descriptions cover this head; commit 6c7fbdf adds the explicit breaking-event-JSON migration with before/after examples and ReceiptType compatibility.
  • Current-head E2E passed: 168 tests, 11 skipped. Binary Size passed: stripped binary and .text both decreased by 64 bytes, allocated sections unchanged; measured against main 8f711a9 using the PR merge commit.
  • Current-head informational SemVer failed. Compared its logs with main's existing failure: both report 23/3/7 major-check categories for core/binary/proto, but this PR adds nine specific core diagnostics for the four removed enums, two builder setters, two event fields, and ReceiptType::variant_name. The unchanged category counts do not mean zero new breakages. These removals require the planned breaking release; no gates were changed.

No merge requested.