fix(api): preserve message identities and report pending push-name sync

Merged
JL
25d ago
+703-244

Blocklist lookup currently treats equal PN/LID user strings as the same identity, message app-state actions reconstruct raw addressing tuples, and push-name updates hide failed app-state synchronization. This change addresses report findings F05, F09, F10 and F13 together, including their existing consumer and E2E call sites.

Behavior and migration

  • blocking().is_blocked(&jid) validates PN/LID before lookup/IQ, ignores device IDs while preserving the server, and considers the alternate namespace only through an explicit mapping. Mapping backend errors remain typed causes.
  • star_message / unstar_message now take &MessageRef; delete_message_for_me takes &MessageRef, delete_media, timestamp. Use a received/sent reference directly. The positional APIs are removed. Syncd indices retain chat/content ID/from-me/participant order; incoming group authors use bare PN/LID account identities, while DM and own-message participants project to "0". Newsletter references remain a separate domain.
  • Message-label methods take &MessageId after label and chat. Their existing schema and fixed "0", "0" tail remain unchanged.
  • Client::generate_message_id() -> MessageId; MessageRetransmission::new accepts/stores MessageId, and its getter returns &MessageId. Existing raw boundaries use as_str() / into_string(). Generation bytes and the distinction between original content IDs and new operation StanzaIds remain unchanged. Rustdoc uses Client::send / SendRequest.
  • set_push_name(&str) -> Result<PushNameOutcome, ProfileError>, with Synced or SyncPending { source: AppStateError }. Presence transport failures still return errors. sync_push_name(&str) -> Result<(), AppStateError> retries app-state only. Successful presence means transport acceptance, not remote display or durable storage confirmation; pending sync remains usable during pairing.

External documentation migration is coordinated by the report campaign's docs owner. No compatibility aliases, dependencies, manifests, workflows, or reserved optimization files are added/changed.

Protocol evidence

Static inspection of original WhatsApp Web 2.3000.1045368834, indexed by whatspec 1a441f0, schema 4.3.0. Verified all 516 JS files, 77,347,149 bytes, against the same lock. Archive SHA-256 2edd4b3f8dae50b503e0b15920adc774e2680cdb542396d57608bb0374196ee1. Modules extracted with Acorn AST; offsets below are original bytes, end-exclusive.

  • WAWebBlocklistCollection: bundle ffe69f61924eb23c1d65cb89a152e4ce35ee404bd52a7edcced9c037b236f490, 4025020..4027615. Full Wid keys and explicit PN/LID conversion.
  • WAWebSyncdIndexUtils: same bundle, 1593317..1595162. Received non-user messages need a valid participant.
  • WAWebSyncdActionUtils: bundle 657a32007737e50b0cb0292ce9c364e21e48291b558129753b25e3ec2a4c13b7, 1851726..1852909. Delete-for-me index projection. WAWebStarMessageSync uses WAWebSyncdUtils.constructMsgKeySegmentsFromMsgKey with the same author tail.
  • WAWebStarMessageSync: bundle 034314e49fa571a8e2e8491843a15536326b4a47d1f66ac7704d2332ee1b391e, 4280701..4286392; delete-for-me 4185669..4193279. Their duplicate definitions were also extracted.
  • WAWebPushNameBridge: bundle 469e8e15743d3d0d694e5157379c9d8e7b55ab57a790d1b129112021619f7e02, 764244..764730, calls presence and returns the sync lock promise. WAWebPushNameSync and presence builder dependencies were checked too. This is version-specific static evidence, not an authenticated server test.

Validation

Regressions exercise public blocking operations with synthetic IQ replies, backend mapping failure, invalid-target zero-send behavior, decoded syncd patches across DM/group PN/LID/from-me/device variants, generated ID format, downstream typed retransmission, push-name partial and transport failures, and sync-only retry without a second presence. Existing message-label tests retain exact wire indices. The device-qualified-author regression failed before the review fix and passes afterward. The consumer retains the complete PushNameOutcome and pending cause.

At final head 5b1752fb9a6d251d99cfe273ab58205ab616a1fd:

  • Rust CI passes all jobs: 6,915 workspace tests, doctests, MSRV, Clippy, all-feature build/lint/tests, Rustdoc, formatting, generated artifacts, serde representations, and every feature matrix.
  • API Consumers, WASM, Miri, CodSpeed, autofix, and Supply Chain conclude successfully. Local validation also passed 2,482 library tests, 57 consumer tests/doctests, Clippy, official Rust 1.94.1 and WASM consumer checks. Local WASM retains two pre-existing qualification warnings in send/mod.rs and upload.rs.
  • E2E passes all 168 tests (11 existing skips) after one same-head rerun. The first attempt passed 167 and timed out in test_fresh_chatstate_delivered_on_reconnect; the rerun passed it in 1.408 s without code changes. Its cause remains unconfirmed. These are established mock-server E2E tests, not authenticated production-server evidence.
  • Binary Size passes the budget against validated main: total stripped binary 11.03 MiB and total text 8.91 MiB, both unchanged; 607 dependencies, unchanged; SDK library LLVM lines decrease by 58. The job tested merge commit a47730bf6, containing this head and base 8f711a9bf. This measures the native fixture, not ESP32 image size.

Both Greptile findings are fixed and their threads resolved; Greptile re-reviewed this head with confidence 5/5. CodeRabbit completed a full review of all 24 files at the same head with no actionable findings or concrete merge-blocking defect. Its retained limits match the documented static protocol evidence and caller-owned retry supersession.

Supply Chain's existing informational SemVer job fails against published 0.7.0: 23 lint categories for wacore, 3 for wacore-binary, and 7 for waproto. Those crate trees, manifests, and lockfile are unchanged from base 8f711a9bf, so these reported failures are baseline differences. The job only checks those three crates; it does not validate this PR's intentional SDK API breakages documented above. No gate is weakened.

An earlier local full run hit repeat_dm_sends_serve_memoized_signal_addressing once; it passed isolated and in three subsequent full runs without changes to the test or cache implementation. Its cause remains unconfirmed. Independent static review also caught an extra typed-to-string allocation, removed from internal raw generation sites.