Skip to content

audit (round 2): more WA Web protocol compliance fixes - #624

Merged
jlucaso1 merged 12 commits into
mainfrom
audit/wa-web-compliance-round-2
May 14, 2026
Merged

audit (round 2): more WA Web protocol compliance fixes#624
jlucaso1 merged 12 commits into
mainfrom
audit/wa-web-compliance-round-2

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented May 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

Round-2 audit against docs/captured-js/. Eight isolated fixes (one per fix commit) plus four review-followup commits incorporating reviewer feedback. No public API breaks: new optional fields default to existing behavior.

Fixes

fix(client): stop double-acking regular messages

WAWebSendDeliveryReceiptJob emits a single <receipt> per incoming message; <ack class="message"> is reserved for newsletter deliveries (OutMessageDeliverCommonAckMixin). Rust was emitting both. should_ack now returns true for the "message" tag only when from is a newsletter or status@broadcast JID. The status branch stays acked because send_delivery_receipt still skips status; otherwise the server would get no acknowledgement at all.

fix(profile): omit <picture> child from profile-picture remove IQ

WAWebSendProfilePictureJob builds the set IQ with <picture type="image">{bytes}</picture> and the remove IQ with no child at all. Rust was emitting <picture type="image"/> on remove. SetProfilePictureSpec::build_iq now produces empty content when image_data is None.

fix(client): drop redundant participant attr from ack when equal to from

WAWebReceiptAck: participant: r && r !== e ? DEVICE_JID(r) : DROP_ATTR. Rust always echoed participant if present in the source stanza, even when it duplicated to (the flipped from). The filter now lives in both encode_ack_bytes (hot path) and build_ack_node (test helper).

feat(group): expose disappearing-mode trigger on SetGroupEphemeralIq

OutGroupsCreateRequest documents <ephemeral expiration trigger> where trigger (0..=20) carries the disappearing-mode source. The existing builder only emitted expiration. New enable_with_trigger constructor sets it; range is asserted at construction (EPHEMERAL_TRIGGER_MAX = 20) and debug_asserted in build_iq for defense.

feat(group): parse <add_request> child on ParticipantChangeResponse 403

WAWebInGroupsParticipantRequestCodeCanBeSentMixin: on error="403" the server attaches a V4 invite token: <add_request code="..." expiration="N"/>. The app can fall back to GroupInviteMessage carrying those fields when privacy blocks a direct add.

ParticipantChangeResponse was dropping the child silently. Replaced the derive with a hand-rolled ProtocolNode impl that captures it into an AddRequestInfo { code, expiration }. Missing jid and malformed <add_request> now hard-fail (mirrors GroupParticipantResponse).

feat(groups): add 14 missing w:gp2 notification variants

WAWebHandleGroupNotificationConst lists 45 tags; GroupNotificationAction covered 24. The rest collapsed into Unknown, so consumers couldn't react to community-admin churn or community-policy toggles.

New variants:

  • LinkedGroupPromote / LinkedGroupDemote
  • Suspended / Unsuspended
  • AutoAddDisabled, IsCapiHostedGroup, GroupSafetyCheck
  • LimitSharingEnabled { trigger: Option<u32> }
  • AllowAdminReports / NotAllowAdminReports / Reports
  • AllowNonAdminSubGroupCreation / NotAllowNonAdminSubGroupCreation
  • CreatedSubGroupSuggestion { raw } / RevokedSubGroupSuggestions { raw }
  • ChangeNumber { participants }

feat(group): support linked_parent + inline description on GroupCreateIq

OutGroupsCreateRequest accepts <linked_parent jid=...> and <description id=... body>...</body></description> children on <create>. We only emitted a subset. Add both as optional fields:

  • linked_parent: subgroup created atomically as a child of a community
  • description: inline description, avoids a follow-up SetGroupDescription IQ

<parent> (this group IS a community) and <linked_parent> (this group is a subgroup of X) are mutually exclusive. When both are set, linked_parent wins on both the build path and the parse_response overlay — a linked subgroup is never promoted to is_parent_group even when the request flag is set.

The description-id helper is shared with SetGroupDescriptionIq, so RNG/seeding stays centralized.

fix(send): promote bare conversation to extended_text when timer needs to attach

WAWebMessageSendUtils upgrades a bare conversation body to extended_text_message whenever a ContextInfo field needs to attach (mention, quote, expiration). The Rust set_ephemeral_expiration and set_context_info returned false, silently dropping the data. Both now promote the message and return true after promotion.

Review-followup commits

  • 60910cae: status broadcast ack safety net, LimitSharingEnabled.trigger payload, set_context_info promotion parity, description-id format alignment with SetGroupDescriptionIq.
  • d87b531a: trigger range 0..=20 validation, type-safe matches in toggle test, shared generate_description_id helper, hard-fail parse for missing jid / malformed add_request, try_into for u64→u32 (Ephemeral, GrowthLocked, LimitSharingEnabled).
  • b444e352: subgroup parse precedence fix, doc lists updated for .to_owned() callers, samples Vec covers all 16 new variants, comment trim per project style.

Skipped

  • inactive receipt for decrypt-fail="hide": needs threading decrypt_fail_mode through several dispatch_parsed_message callsites. Impact is server-side analytics only; deferred to a focused follow-up.

Test plan

  • cargo test --workspace --exclude e2e-tests --exclude bench-integration
  • cargo clippy --workspace --tests --exclude e2e-tests -- -D warnings
  • Every fix ships with regression tests covering its branch

jlucaso1 added 8 commits May 14, 2026 00:00
WA Web only emits `<ack class="message">` for newsletter deliveries
(WAWebOutMessageDeliverCommonAckMixin). Regular DMs / groups / status
broadcasts rely on the <receipt> stanza alone — sending both the ack
and the delivery receipt is redundant and wire-divergent.

Narrow should_ack:
  message → only ack when from is a newsletter JID
  receipt | notification | call → unchanged

Existing test extended to lock in the new matrix (DM/group must not
ack, newsletter must).
WAWebSendProfilePictureJob builds the set vs remove IQ as:
  set:    <iq xmlns="w:profile:picture" type="set"><picture type="image">{bytes}</picture></iq>
  remove: <iq xmlns="w:profile:picture" type="set"/>   (no <picture> child)

Rust was emitting an empty <picture type="image"/> on remove, which is
spec-divergent and can yield 400/406 from strict server-side parsers.

Existing remove-own test was asserting the buggy shape; replaced by two
tests pinning the WA-Web-correct empty-IQ shape for both own and group.
WAWebReceiptAck: `participant: r && r !== e ? DEVICE_JID(r) : DROP_ATTR`
— participant is dropped when it duplicates the destination (the
flipped `from`). Rust was always echoing the participant attribute when
present in the source stanza, even when redundant.

Apply the filter in both encode_ack_bytes (hot path) and build_ack_node
(test helper). Two new tests pin the drop / keep matrix.
WA Web's OutGroupsCreateRequest documents `<ephemeral expiration trigger>`
where `trigger` (0..20) carries the disappearing-mode source (per-chat
setting vs device-wide default). The existing builder only emitted
`expiration`, so callers couldn't propagate that signal.

Add SetGroupEphemeralIq::trigger field and an enable_with_trigger
constructor; existing enable / disable stay backwards-compatible
(default trigger = None, attribute omitted on the wire).
WAWebInGroupsParticipantRequestCodeCanBeSentMixin: when an add fails
with error=403 (privacy-blocked), the server attaches a V4 invite
token: <add_request code="..." expiration="N"/>. The app can fall
back to sending a GroupInviteMessage with those fields instead of
silently failing the add.

ParticipantChangeResponse was dropping the child, so the recovery path
was unreachable. Replaced the derive with a hand-rolled ProtocolNode
impl that captures it into an AddRequestInfo { code, expiration }.

Two new tests pin the 403-with-token / non-403 matrix.
WAWebHandleGroupNotificationConst lists 45 GROUP_NOTIFICATION_TAGs;
GroupNotificationAction covered 24, falling through the rest to the
Unknown catch-all so consumers couldn't react to community-admin churn
or community-policy toggles.

Add:
  - LinkedGroupPromote / LinkedGroupDemote (community parent admin changes)
  - Suspended / Unsuspended (Meta-moderation toggles)
  - AutoAddDisabled, IsCapiHostedGroup, GroupSafetyCheck, LimitSharingEnabled
  - AllowAdminReports / NotAllowAdminReports / Reports
  - AllowNonAdminSubGroupCreation / NotAllowNonAdminSubGroupCreation
  - CreatedSubGroupSuggestion / RevokedSubGroupSuggestions (raw node)
  - ChangeNumber (carries new <participant>)

Three new tests cover the toggle table, the participant-carrying
variants, and the change_number flow.
OutGroupsCreateRequest accepts ~17 child types on <create>. We only
emitted a subset. Add the two most useful that the audit flagged:

  - linked_parent: lets a subgroup be created atomically as a child of
    an existing community, removing an extra round-trip
  - description: inline <description id="<token>"><body>{text}</body></description>
    so callers don't have to follow up with a separate set-description IQ

Both are optional; existing callers are unaffected.
…s to attach

WAWebMessageSendUtils upgrades a bare `conversation` body to
`extended_text_message` whenever a ContextInfo field needs to be
populated, so the ephemeral timer can ride on it. The Rust helper
returned false instead, silently dropping the timer.

set_ephemeral_expiration now moves the conversation text into a new
extended_text_message with the timer attached, matching WA Web's wire
output. Two new tests pin the promote + empty-message paths.
@coderabbitai

coderabbitai Bot commented May 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 54ac8310-553b-48a0-9b5b-2d0cdf8ea5c6

📥 Commits

Reviewing files that changed from the base of the PR and between b444e35 and e499836.

📒 Files selected for processing (3)
  • wacore/src/iq/groups.rs
  • wacore/src/proto_helpers.rs
  • wacore/src/stanza/groups.rs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Link groups to communities with optional inline descriptions and add-request support for participant changes.
    • Configure ephemeral-message triggers with an enforced maximum.
    • Synchronous terminal shutdown trigger.
  • Bug Fixes

    • Profile-picture removal now emits no empty picture node.
    • ACKs restricted to intended stanza types and avoid redundant participant echoes.
  • Improvements

    • Bare conversation messages promoted to extended text when adding context or ephemeral info.
    • Expanded group notification/action variants.
  • Tests

    • Expanded unit tests covering ACKs, picture removal, promotion behavior, group features, add-request parsing, triggers and notification parsing.

Walkthrough

Tightens client ACK rules and encoding, adds a synchronous flag-only shutdown, emits no <picture> on profile-picture removal, extends group IQs/notifications (linked_parent, description, ephemeral trigger, add_request), and promotes bare conversations into extended wrappers for context/ephemeral metadata.

Changes

Protocol & IQ Updates

Layer / File(s) Summary
Client ACK Refinement and Synchronous Shutdown
src/client.rs
should_ack() now only acknowledges receipt, notification, and call stanzas; <message/> ACKs require id/from and are limited to newsletter/status-broadcast senders. ACK encoding omits participant when equal to from. Added pub fn signal_shutdown_sync(&self) for synchronous, flag-only shutdown paths. Tests and helper build_ack_node() updated.
Contact Profile Picture Removal
wacore/src/iq/contacts.rs
SetProfilePictureSpec::build_iq emits no <picture> child for removal (iq.content = None); removal tests updated to assert iq.content.is_none().
Group Create: linked_parent & description, Ephemeral trigger, AddRequest
wacore/src/iq/groups.rs
GroupCreateOptions adds linked_parent: Option<Jid> and description: Option<GroupDescription> with generated description IDs; build_create_group_node emits <linked_parent/> (suppresses community parent) and inline <description> when set. SetGroupEphemeralIq adds trigger: Option<u32> with EPHEMERAL_TRIGGER_MAX = 20 and enable_with_trigger(...). AddRequestInfo added and ParticipantChangeResponse gains add_request: Option<AddRequestInfo> with manual ProtocolNode serialization/parsing. Tests added/expanded.
MessageExt context/ephemeral promotion
wacore/src/proto_helpers.rs
Removed macro; set_context_info and set_ephemeral_expiration try existing wrapper fields then promote a bare conversation into extended_text_message to attach context_info or expiration. Tests added for promotion and empty-message behavior.
GroupNotificationAction variants & parser
wacore/src/stanza/groups.rs
Expanded GroupNotificationAction with linked subgroup promote/demote, multiple toggle/state variants, LimitSharingEnabled { trigger: Option<u32> }, suggestion raw-node variants, and ChangeNumber { participants }. parse_action updated to parse these and numeric conversions use try_into(); tests added.

Sequence Diagram (client ACK & shutdown)

sequenceDiagram
  participant Client
  participant IncomingStanza
  participant AckBuilder
  Client->>IncomingStanza: should_ack(stanza)
  IncomingStanza-->>Client: eligibility (receipt/notification/call OR newsletter/status@broadcast)
  Client->>AckBuilder: encode_ack_bytes(stanza, participant?)
  AckBuilder-->>Client: ack bytes (omit redundant participant when participant == from)
  Client->>Client: signal_shutdown_sync() sets flags and notifiers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • Ari4ka

You want this to work right — review the ACK rules, group IQ changes, and MessageExt promotions with priority.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the primary change: a round-2 audit delivering multiple WhatsApp Web protocol compliance fixes across the codebase.
Description check ✅ Passed The description comprehensively details eight isolated fixes, review-followup commits, testing approach, and explicit deferred items, all directly related to the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/wa-web-compliance-round-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
wacore/src/iq/contacts.rs (1)

196-201: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Update the Remove wire format documentation.

Look, we need our documentation to match reality. The code now emits an empty IQ with NO <picture> child when removing (content is None), but this doc example still shows <picture type="image"/>. That's unacceptable—developers will read this and implement the wrong thing.

📝 Fix the wire format example
 /// ## Wire Format (Remove)
 /// ```xml
 /// <iq xmlns="w:profile:picture" type="set" to="s.whatsapp.net" id="...">
-///   <picture type="image"/>
 /// </iq>
 /// ```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/iq/contacts.rs` around lines 196 - 201, Update the "Remove wire
format" docblock to match runtime behavior: the IQ emitted when removing a
profile picture contains no <picture> child (content is None), so remove the
`<picture type="image"/>` line from the example in the doc comment in
contacts.rs (the doc block starting "## Wire Format (Remove)") and show an empty
<iq ...>...</iq> example instead to reflect that the IQ has no child elements.
wacore/src/proto_helpers.rs (1)

156-159: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the trait doc: behavior changed for bare conversation.

The docs still state this returns false for bare conversation, but Line 361 now promotes to extended_text_message and returns true.

Suggested fix
-    /// Sets `context_info.expiration` on the first message type found.
-    /// Creates a default `context_info` if needed. Returns `false` for
-    /// bare `conversation` messages (use `ExtendedTextMessage` instead).
+    /// Sets `context_info.expiration` on the first message type found.
+    /// Creates a default `context_info` if needed. Bare `conversation`
+    /// payloads are promoted to `extended_text_message` so expiration can attach.
+    /// Returns `false` only when no compatible payload exists.
     fn set_ephemeral_expiration(&mut self, expiration: u32) -> bool;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/proto_helpers.rs` around lines 156 - 159, Update the trait doc for
set_ephemeral_expiration to reflect the changed behavior: it now promotes bare
conversation messages to extended_text_message (creating a default context_info
if needed) and returns true instead of false; modify the sentence that currently
says "Returns `false` for bare `conversation` messages (use
`ExtendedTextMessage` instead)" to describe the promotion and the true return
value, referencing the set_ephemeral_expiration method name so reviewers can
find the implementation.
wacore/src/iq/groups.rs (1)

421-445: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent conflicting create payloads (<parent/> + <linked_parent/>).

This path can emit both tags when is_parent and linked_parent are both set, which creates an ambiguous wire shape for group creation.

Suggested fix
 pub fn build_create_group_node(options: &GroupCreateOptions) -> Node {
     let mut children = Vec::new();
+    debug_assert!(
+        !(options.is_parent && options.linked_parent.is_some()),
+        "`linked_parent` is only valid for subgroup creation (is_parent = false)"
+    );
@@
-    if let Some(parent_jid) = &options.linked_parent {
+    if !options.is_parent
+        && let Some(parent_jid) = &options.linked_parent
+    {
         children.push(
             NodeBuilder::new("linked_parent")
                 .attr("jid", parent_jid)
                 .build(),
         );
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/iq/groups.rs` around lines 421 - 445, The code can emit both a
<parent/> and a <linked_parent/> tag when options.is_parent is true and
options.linked_parent is Some, causing conflicting payloads; update the logic
around NodeBuilder usage so these are mutually exclusive (e.g., only build the
parent block when options.is_parent && options.linked_parent.is_none(), or if
both are set choose one canonical behavior such as preferring linked_parent and
skipping the parent block); adjust the checks around options.is_parent,
options.linked_parent and the NodeBuilder calls (references: options.is_parent,
options.linked_parent, NodeBuilder::new("parent"),
NodeBuilder::new("linked_parent"), children.push(...)) so the resulting children
never include both tags simultaneously.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 1773-1779: The enable_with_trigger constructor currently accepts
any u32 for trigger; add a validation that trigger is within WA's allowed range
(0..=20) and reject out-of-range values instead of allowing them through.
Concretely, update enable_with_trigger to check if trigger <= 20 and return a
Result (e.g., Result<Self, Error> or Option<Self>) or otherwise surface an
explicit error when invalid; also add the same range check in the encoder path
that serializes the ephemeral IQ (the encoder/serializer that reads this struct)
so invalid trigger values cannot be encoded on-wire; keep the check in both the
constructor (enable_with_trigger) and the encoder to ensure safety.

In `@wacore/src/stanza/groups.rs`:
- Around line 975-1005: The test test_parse_suspended_toggle_variants currently
asserts by comparing Debug strings; replace that brittle check with type-safe
pattern matches: after building notif via GroupNotification::try_from_node_ref,
use matches! (or a match) on notif.actions[0] to assert it is the expected enum
variant for each tag (e.g., assert matches!(notif.actions[0], Action::Suspended
| Action::Unsuspended | Action::AutoAddDisabled | Action::IsCapiHostedGroup |
Action::GroupSafetyCheck | Action::LimitSharingEnabled |
Action::AllowAdminReports | Action::NotAllowAdminReports | Action::Reports |
Action::AllowNonAdminSubGroupCreation |
Action::NotAllowNonAdminSubGroupCreation) as appropriate per table entry), so
the test fails only when the parser produces a different enum variant; keep
using GroupNotification::try_from_node_ref and notif.actions to locate the value
being matched.

---

Outside diff comments:
In `@wacore/src/iq/contacts.rs`:
- Around line 196-201: Update the "Remove wire format" docblock to match runtime
behavior: the IQ emitted when removing a profile picture contains no <picture>
child (content is None), so remove the `<picture type="image"/>` line from the
example in the doc comment in contacts.rs (the doc block starting "## Wire
Format (Remove)") and show an empty <iq ...>...</iq> example instead to reflect
that the IQ has no child elements.

In `@wacore/src/iq/groups.rs`:
- Around line 421-445: The code can emit both a <parent/> and a <linked_parent/>
tag when options.is_parent is true and options.linked_parent is Some, causing
conflicting payloads; update the logic around NodeBuilder usage so these are
mutually exclusive (e.g., only build the parent block when options.is_parent &&
options.linked_parent.is_none(), or if both are set choose one canonical
behavior such as preferring linked_parent and skipping the parent block); adjust
the checks around options.is_parent, options.linked_parent and the NodeBuilder
calls (references: options.is_parent, options.linked_parent,
NodeBuilder::new("parent"), NodeBuilder::new("linked_parent"),
children.push(...)) so the resulting children never include both tags
simultaneously.

In `@wacore/src/proto_helpers.rs`:
- Around line 156-159: Update the trait doc for set_ephemeral_expiration to
reflect the changed behavior: it now promotes bare conversation messages to
extended_text_message (creating a default context_info if needed) and returns
true instead of false; modify the sentence that currently says "Returns `false`
for bare `conversation` messages (use `ExtendedTextMessage` instead)" to
describe the promotion and the true return value, referencing the
set_ephemeral_expiration method name so reviewers can find the implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 14269a6a-fbfe-46ca-a390-070f1c593e7e

📥 Commits

Reviewing files that changed from the base of the PR and between e52b2ec and 5c19276.

📒 Files selected for processing (5)
  • src/client.rs
  • wacore/src/iq/contacts.rs
  • wacore/src/iq/groups.rs
  • wacore/src/proto_helpers.rs
  • wacore/src/stanza/groups.rs

Comment thread wacore/src/iq/groups.rs Outdated
Comment thread wacore/src/stanza/groups.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c19276c45

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/client.rs Outdated
};
match tag {
"receipt" | "notification" | "call" => true,
"message" => from.to_jid().is_some_and(|j| j.is_newsletter()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep ACKs for status broadcast messages

Narrowing should_ack so <message> only acks newsletters means status messages (from="status@broadcast") no longer get any acknowledgement at all: this branch now returns false for them, and send_delivery_receipt already skips status chats (should_send_delivery_receipt filters STATUS_BROADCAST_USER). In practice that drops the only ack path that previously existed for status stanzas, which can cause repeated deliveries/retries and inconsistent status processing on long-lived sessions.

Useful? React with 👍 / 👎.

B1 status broadcast safety net: should_ack now also returns true for
status@broadcast <message> stanzas. send_delivery_receipt skips them, so
without the ack the server would have no acknowledgement at all. Pre-PR
behavior is preserved until the status-receipt path (B4 territory) lands.

B7 LimitSharingEnabled trigger: the variant now carries an
Option<u32> trigger captured from the <limit_sharing_enabled trigger=N>
attribute (range 0..20 per WASmaxInGroupsGroupInfoMixin). Empty toggle
keeps trigger = None.

B10 set_context_info parity: extends the same conversation→extended_text
promotion to set_context_info, so mentions / quotes also attach when the
body is a bare conversation. Existing test updated to assert the new
promote behavior; new test pins the false-on-empty-message path.

B14 description id format: align with SetGroupDescriptionIq's existing
8-char hex (random u32). Was 16-char hex from 8 random bytes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wacore/src/proto_helpers.rs (1)

135-138: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the doc comment—it contradicts the new implementation.

The doc says this returns false for bare conversation messages, but the code now promotes conversation to extended_text_message and returns true (lines 366-375). That's a fix we needed to prevent silently dropping ephemeral timers, but the documentation needs to reflect reality.

📝 Proposed fix
 /// Sets `context_info.expiration` on the first message type found.
-/// Creates a default `context_info` if needed. Returns `false` for
-/// bare `conversation` messages (use `ExtendedTextMessage` instead).
+/// Creates a default `context_info` if needed. Promotes bare `conversation`
+/// messages to `ExtendedTextMessage` to carry the expiration.
 fn set_ephemeral_expiration(&mut self, expiration: u32) -> bool;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/proto_helpers.rs` around lines 135 - 138, Update the doc comment
for set_ephemeral_expiration to match the implementation: state that it sets
context_info.expiration on the first message type found (creating a default
context_info if needed), and that it will promote a bare ConversationMessage
into an ExtendedTextMessage to attach the expiration (returning true); only
return false if no suitable message type exists to attach the expiration.
Reference the function name set_ephemeral_expiration and the promotion behavior
(ConversationMessage -> ExtendedTextMessage) in the comment so the docs no
longer contradict the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 438-461: This creates a fresh StdRng via rand::make_rng each time
an inline description is built; extract the 8-hex-id generation into a shared
helper (e.g., generate_description_id) and have both this creation path (in the
block that checks options.description and uses NodeBuilder::new("description"))
and SetGroupDescriptionIq::new call that helper so RNG/seeding strategy is
centralized and consistent; keep the local use rand::RngExt as _ import
placement consistent with the existing pattern.
- Around line 1140-1205: The ParticipantChangeResponse::try_from_node_ref impl
silently swallows two errors: replace attrs.jid("jid") with
attrs.optional_jid("jid").ok_or_else(|| anyhow::anyhow!("participant missing
required 'jid' attribute"))? so missing JIDs propagate an error like the
GroupParticipantResponse pattern, and change the add_request parsing (the
node.get_optional_child("add_request") closure) to fail fast instead of using
.and_then(): when the child exists, explicitly require the "code" attribute and
parse "expiration" to u64 returning an anyhow::Error on missing/invalid values
(so AddRequestInfo is Some only on successful parse, and malformed add_request
returns Err rather than silently yielding None).

In `@wacore/src/stanza/groups.rs`:
- Around line 499-528: In parse_action, avoid silently truncating large u64
values when building LimitSharingEnabled.trigger: replace the current
optional_u64(...).map(|t| t as u32) with optional_u64(...).and_then(|t|
t.try_into().ok()) so overflow yields None; apply the same try_into().ok()
pattern for similar fields such as Ephemeral and GrowthLocked if you later
update them. Use the symbols LimitSharingEnabled, parse_action, and optional_u64
to locate and change the mapping.

---

Outside diff comments:
In `@wacore/src/proto_helpers.rs`:
- Around line 135-138: Update the doc comment for set_ephemeral_expiration to
match the implementation: state that it sets context_info.expiration on the
first message type found (creating a default context_info if needed), and that
it will promote a bare ConversationMessage into an ExtendedTextMessage to attach
the expiration (returning true); only return false if no suitable message type
exists to attach the expiration. Reference the function name
set_ephemeral_expiration and the promotion behavior (ConversationMessage ->
ExtendedTextMessage) in the comment so the docs no longer contradict the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c77e8e1d-1c20-4d65-bf62-cc7be7e4fcfa

📥 Commits

Reviewing files that changed from the base of the PR and between 5c19276 and 60910ca.

📒 Files selected for processing (4)
  • src/client.rs
  • wacore/src/iq/groups.rs
  • wacore/src/proto_helpers.rs
  • wacore/src/stanza/groups.rs

Comment thread wacore/src/iq/groups.rs Outdated
Comment thread wacore/src/iq/groups.rs
Comment thread wacore/src/stanza/groups.rs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60910caead

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/iq/groups.rs Outdated
));
}
let mut attrs = node.attrs();
let jid = attrs.jid("jid");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require jid when parsing participant change responses

ParticipantChangeResponse::try_from_node_ref now reads jid via attrs.jid("jid"), which returns a default empty JID when the attribute is missing or unparsable instead of failing. Before this change, the derived parser treated jid as required and returned an error. In malformed or partial <participant> replies, this silently produces a bogus jid and lets the response look valid, which can corrupt downstream participant-update handling keyed by JID.

Useful? React with 👍 / 👎.

Round-3 review follow-ups:

* SetGroupEphemeralIq trigger: assert against
  EPHEMERAL_TRIGGER_MAX (=20) in enable_with_trigger; debug_assert at
  encode time for defense. Out-of-range values are server-rejected per
  WASmaxInGroupsGroupInfoMixin's attrIntRange(0,20).

* test_parse_suspended_toggle_variants: switch from Debug-string
  starts_with to type-safe matches! on each variant — parser
  regressions (landing in Unknown) now fail loudly.

* contacts.rs profile-pic doc: remove example now shows an empty <iq/>,
  matching the build_iq output after the earlier fix.

* GroupCreateOptions: <parent> and <linked_parent> are semantically
  exclusive (community vs subgroup-of-community). When both are set,
  linked_parent wins and the parent-only children
  (allow_non_admin_sub_group_creation, create_general_chat) are dropped.

* generate_description_id: extracted shared helper used by both
  GroupCreateIq (inline description) and SetGroupDescriptionIq, so
  RNG/seeding strategy stays centralized.

* ParticipantChangeResponse::try_from_node_ref: hard-fail on missing
  jid attr (mirrors GroupParticipantResponse) and on malformed
  <add_request> (missing code, non-numeric expiration). 403 with a
  dropped V4 invite token is a server bug we want to surface.

* parse_action u64→u32: replace  truncation with try_into().ok()
  for Ephemeral.expiration, Ephemeral.trigger, GrowthLocked.expiration,
  and LimitSharingEnabled.trigger. Out-of-range values become None / 0
  instead of silently wrapping.

Skipped: doc update for set_ephemeral_expiration — already addressed in
the previous round; current text already describes the promotion and
the true/false semantics.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d87b531a7d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/iq/groups.rs
Comment on lines +437 to +441
if let Some(parent_jid) = &options.linked_parent {
children.push(
NodeBuilder::new("linked_parent")
.attr("jid", parent_jid)
.build(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep linked_parent and is_parent response state consistent

When both linked_parent and is_parent are set, this branch intentionally serializes the request as a subgroup (<linked_parent>, no <parent>), but GroupCreateIq::parse_response still overlays is_parent_group = true from options.is_parent. That leaves the immediate create response misclassified as a community parent (and can mislead group_type()/follow-up logic) until a later metadata fetch corrects it. The overlay logic should ignore is_parent when linked_parent is present so request and parsed response stay consistent.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
wacore/src/stanza/groups.rs (3)

296-296: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update this comment - two more variants now call .to_owned().

The comment claims only Create/Link/Unlink call .to_owned(), but CreatedSubGroupSuggestion and RevokedSubGroupSuggestions also store raw: Node and clone their child node at lines 536 and 539. This matters because it documents where we pay the allocation cost.

📝 Proposed fix
-    /// Most fields are parsed zero-copy. Only `Create`/`Link`/`Unlink` actions
-    /// call `.to_owned()` on their specific child node (structurally required to store `raw: Node`).
+    /// Most fields are parsed zero-copy. Only `Create`, `Link`, `Unlink`,
+    /// `CreatedSubGroupSuggestion`, and `RevokedSubGroupSuggestions` actions
+    /// call `.to_owned()` on their specific child node (structurally required to store `raw: Node`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/stanza/groups.rs` at line 296, Update the doc comment that
currently states only Create/Link/Unlink call `.to_owned()` to also list
CreatedSubGroupSuggestion and RevokedSubGroupSuggestions as variants that clone
their child node into `raw: Node`; locate the comment near the variant
enum/documentation in groups.rs and add those two variant names (or rephrase to
"Create, Link, Unlink, CreatedSubGroupSuggestion, and
RevokedSubGroupSuggestions") so it accurately reflects the allocation points
(see the constructors storing `raw: Node` for CreatedSubGroupSuggestion and
RevokedSubGroupSuggestions around the code that clones the child node).

1117-1191: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This regression guard needs to cover the 16 new variants you just added.

The test comment says it guards against PascalCase discriminator leaks (line 1112-1113). You added LinkedGroupPromote, LinkedGroupDemote, Suspended, Unsuspended, AutoAddDisabled, IsCapiHostedGroup, GroupSafetyCheck, LimitSharingEnabled, AllowAdminReports, NotAllowAdminReports, Reports, AllowNonAdminSubGroupCreation, NotAllowNonAdminSubGroupCreation, CreatedSubGroupSuggestion, RevokedSubGroupSuggestions, and ChangeNumber — none of them are in the samples array. If the WireEnum derive breaks for these new variants, this test won't catch it. That's exactly what regression guards are supposed to prevent.

🛡️ Proposed fix - add samples for all new variants
             GroupNotificationAction::Unlink {
                 unlink_type: "x".into(),
                 unlink_reason: None,
                 raw: dummy_node,
             },
+            GroupNotificationAction::LinkedGroupPromote {
+                participants: vec![],
+            },
+            GroupNotificationAction::LinkedGroupDemote {
+                participants: vec![],
+            },
+            GroupNotificationAction::Suspended,
+            GroupNotificationAction::Unsuspended,
+            GroupNotificationAction::AutoAddDisabled,
+            GroupNotificationAction::IsCapiHostedGroup,
+            GroupNotificationAction::GroupSafetyCheck,
+            GroupNotificationAction::LimitSharingEnabled { trigger: None },
+            GroupNotificationAction::AllowAdminReports,
+            GroupNotificationAction::NotAllowAdminReports,
+            GroupNotificationAction::Reports,
+            GroupNotificationAction::AllowNonAdminSubGroupCreation,
+            GroupNotificationAction::NotAllowNonAdminSubGroupCreation,
+            GroupNotificationAction::CreatedSubGroupSuggestion {
+                raw: dummy_node.clone(),
+            },
+            GroupNotificationAction::RevokedSubGroupSuggestions {
+                raw: dummy_node.clone(),
+            },
+            GroupNotificationAction::ChangeNumber {
+                participants: vec![],
+            },
             GroupNotificationAction::Unknown {
                 tag: "future_tag".into(),
             },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/stanza/groups.rs` around lines 1117 - 1191, The samples
Vec<GroupNotificationAction> used as the regression guard is missing the 16
newly added enum variants; update the samples definition (the samples variable
in groups.rs) to include instances for LinkedGroupPromote, LinkedGroupDemote,
Suspended, Unsuspended, AutoAddDisabled, IsCapiHostedGroup, GroupSafetyCheck,
LimitSharingEnabled, AllowAdminReports, NotAllowAdminReports, Reports,
AllowNonAdminSubGroupCreation, NotAllowNonAdminSubGroupCreation,
CreatedSubGroupSuggestion, RevokedSubGroupSuggestions, and ChangeNumber so the
test exercises their WireEnum discriminators (create minimal valid variants
similar to the existing samples: use placeholder strings/zeros/empty vecs/None
where fields exist).

331-331: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update this comment too - it's duplicating the stale claim.

Same issue: the comment says only Create/Link/Unlink call .to_owned(), but you've added two more variants that do this.

📝 Proposed fix
-/// Only `Create`/`Link`/`Unlink` call `.to_owned()` because those variants store `raw: Node`.
+/// Only `Create`, `Link`, `Unlink`, `CreatedSubGroupSuggestion`, and
+/// `RevokedSubGroupSuggestions` call `.to_owned()` because those variants store `raw: Node`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/stanza/groups.rs` at line 331, Update the stale comment that
currently claims "Only `Create`/`Link`/`Unlink` call `.to_owned()`" to enumerate
all enum variants that actually call `.to_owned()` (including the newer variants
you added), and state that those variants store `raw: Node`; in short, replace
the old phrase with a correct list: `Create`, `Link`, `Unlink` and the two newly
added variants so the comment accurately reflects which variants store `raw:
Node` and call `.to_owned()`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 433-443: The response parsing currently overrides the on-wire
`linked_parent` precedence by unconditionally setting is_parent_group when
options.is_parent is true; update GroupCreateIq::parse_response() so it first
checks for a `linked_parent` element in the response (or the original
options.linked_parent) and only sets is_parent_group when no linked_parent is
present—i.e., if response.contains("linked_parent") or
options.linked_parent.is_some() then ensure is_parent_group is not set,
otherwise honor options.is_parent; adjust the logic that sets is_parent_group
accordingly.

---

Outside diff comments:
In `@wacore/src/stanza/groups.rs`:
- Line 296: Update the doc comment that currently states only Create/Link/Unlink
call `.to_owned()` to also list CreatedSubGroupSuggestion and
RevokedSubGroupSuggestions as variants that clone their child node into `raw:
Node`; locate the comment near the variant enum/documentation in groups.rs and
add those two variant names (or rephrase to "Create, Link, Unlink,
CreatedSubGroupSuggestion, and RevokedSubGroupSuggestions") so it accurately
reflects the allocation points (see the constructors storing `raw: Node` for
CreatedSubGroupSuggestion and RevokedSubGroupSuggestions around the code that
clones the child node).
- Around line 1117-1191: The samples Vec<GroupNotificationAction> used as the
regression guard is missing the 16 newly added enum variants; update the samples
definition (the samples variable in groups.rs) to include instances for
LinkedGroupPromote, LinkedGroupDemote, Suspended, Unsuspended, AutoAddDisabled,
IsCapiHostedGroup, GroupSafetyCheck, LimitSharingEnabled, AllowAdminReports,
NotAllowAdminReports, Reports, AllowNonAdminSubGroupCreation,
NotAllowNonAdminSubGroupCreation, CreatedSubGroupSuggestion,
RevokedSubGroupSuggestions, and ChangeNumber so the test exercises their
WireEnum discriminators (create minimal valid variants similar to the existing
samples: use placeholder strings/zeros/empty vecs/None where fields exist).
- Line 331: Update the stale comment that currently claims "Only
`Create`/`Link`/`Unlink` call `.to_owned()`" to enumerate all enum variants that
actually call `.to_owned()` (including the newer variants you added), and state
that those variants store `raw: Node`; in short, replace the old phrase with a
correct list: `Create`, `Link`, `Unlink` and the two newly added variants so the
comment accurately reflects which variants store `raw: Node` and call
`.to_owned()`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 570e9f97-49f7-4dc7-9a18-dc28b128e723

📥 Commits

Reviewing files that changed from the base of the PR and between 60910ca and d87b531.

📒 Files selected for processing (4)
  • wacore/src/iq/contacts.rs
  • wacore/src/iq/groups.rs
  • wacore/src/proto_helpers.rs
  • wacore/src/stanza/groups.rs

Comment thread wacore/src/iq/groups.rs Outdated
…mments

* GroupCreateIq::parse_response: a linked subgroup is never a community
  parent. When the response carries <linked_parent> (or the request
  specified one), don't promote is_parent_group even with is_parent set.

* GroupNotification: update both doc lines that list which variants
  call .to_owned() (Create/Link/Unlink) to also include
  CreatedSubGroupSuggestion and RevokedSubGroupSuggestions.

* serialize_discriminator_matches_wire_tag: extend the samples Vec with
  the 16 newly added variants so every WireEnum discriminator is exercised.

* Trim verbose docs and inline comments added in earlier rounds; drop
  em dashes and JS-file/line references per project style.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
wacore/src/iq/groups.rs (2)

430-453: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

The silent drop of closed / allow_non_admin_sub_group_creation / create_general_chat when both linked_parent and is_parent are set worries me.

The test at lines 3887-3921 locks this in as intended behavior, and the doc on linked_parent says "mutually exclusive with is_parent" — fine. But the GroupCreateOptions builder accepts both at the same time with zero feedback. If someone wires up is_parent=true, closed=true, linked_parent=Some(...) by mistake, we ship a subgroup and pretend the community flags never existed. Consider either a debug_assert!(options.linked_parent.is_none() || !options.is_parent) in build_create_group_node, or refactoring GroupCreateOptions into a sum type so the two modes can't co-exist. At minimum, a tracing::warn! so this misuse leaves a trail.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/iq/groups.rs` around lines 430 - 453, The code silently drops
parent-related flags when both options.linked_parent and options.is_parent are
set; update build_create_group_node to detect this misuse by adding a
debug_assert!(options.linked_parent.is_none() || !options.is_parent) and also
emit a tracing::warn! (including values of options.linked_parent,
options.closed, options.allow_non_admin_sub_group_creation,
options.create_general_chat) when both are present so the misuse is logged;
alternatively consider converting GroupCreateOptions into a sum type (enum) that
prevents linked_parent and is_parent from coexisting, but at minimum add the
assert+warn in build_create_group_node to surface the issue.

1762-1819: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

I want this trigger validation tightened up — release builds shouldn't be able to send junk.

enable_with_trigger panics with assert! when trigger > 20. Good. But trigger: Option<u32> is pub, so any caller can do SetGroupEphemeralIq { group_jid, expiration: Some(e), trigger: Some(99) } and walk right past the constructor. Then at line 1812 the debug_assert! is a no-op in release, and the bogus trigger="99" lands on the wire and the server rejects (or worse, accepts the wrong disappearing-mode source).

Two options that close the gap:

🔒 Option A — make the field private, force the constructor
-    /// `trigger` attr on `<ephemeral>` (0..=[`EPHEMERAL_TRIGGER_MAX`]);
-    /// identifies the disappearing-mode source. `None` omits the attr.
-    pub trigger: Option<u32>,
+    /// `trigger` attr on `<ephemeral>` (0..=[`EPHEMERAL_TRIGGER_MAX`]);
+    /// identifies the disappearing-mode source. `None` omits the attr.
+    /// Only mutable via `enable_with_trigger` to enforce the range.
+    trigger: Option<u32>,
🔒 Option B — clamp/skip in the encoder so bad values never wire
-                if let Some(trigger) = self.trigger {
-                    debug_assert!(
-                        trigger <= EPHEMERAL_TRIGGER_MAX,
-                        "ephemeral trigger out of range (0..={EPHEMERAL_TRIGGER_MAX}): {trigger}"
-                    );
-                    b = b.attr("trigger", trigger);
+                if let Some(trigger) = self.trigger
+                    && trigger <= EPHEMERAL_TRIGGER_MAX
+                {
+                    b = b.attr("trigger", trigger);
                 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/iq/groups.rs` around lines 1762 - 1819, The trigger validation
must be enforced at encode time so bad values can't be sent; in
SetGroupEphemeralIq::build_iq (where NodeBuilder is used) replace the
debug_assert! with a runtime check on self.trigger against EPHEMERAL_TRIGGER_MAX
and only add the "trigger" attr when it is within range (e.g., if let
Some(trigger) = self.trigger { if trigger <= EPHEMERAL_TRIGGER_MAX { b =
b.attr("trigger", trigger); } } ), thereby skipping any out-of-range trigger
instead of emitting it; alternatively make the trigger field private and force
construction via enable_with_trigger if you prefer the constructor-only
approach.
wacore/src/proto_helpers.rs (1)

5-10: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Move fast, but don't leave dead docs around — the set_context_info_on_message! macro is gone.

This doc comment still advertises a set_context_info_on_message! callback macro that was deleted in this PR (it's been inlined as try_attach! inside set_context_info). The comment now lies to the next person who reads it. Trim it to reflect reality.

♻️ Suggested wording
-/// Invokes a callback macro with the list of all message types that have `context_info`.
-///
-/// This macro ensures both `for_each_context_info_message!` and `set_context_info_on_message!`
-/// use the same list of message types, making it easy to add new types in one place.
-///
-/// When WhatsApp adds new message types with context_info, add them here.
+/// Invokes a callback macro with the list of all message types that carry `context_info`.
+///
+/// Single source of truth used by `for_each_context_info_message!` and the inline
+/// `try_attach!` / `try_set!` / `check!` macros in `MessageExt`. Add new variants here
+/// when WhatsApp ships new message types with `context_info`.

As per coding guidelines: "Keep code comments concise; explain why, not what".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wacore/src/proto_helpers.rs` around lines 5 - 10, The doc comment wrongly
references the removed macro set_context_info_on_message! — update the comment
above the for_each_context_info_message! macro to remove that reference and
state the current truth: say that this list is used by
for_each_context_info_message! and by set_context_info (which inlines
try_attach!), and that new WhatsApp message types with context_info should be
added here; keep the comment short and factual about why the list exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 273-277: The inline description field is currently raw
Option<String> which bypasses the GroupDescription validation used by
SetGroupDescriptionIq; change the field type to Option<GroupDescription> (or
another validated wrapper) so both create-paths share the same contract, and
update the builder to accept a string input but validate/convert into
GroupDescription during build (use GroupDescription::new / TryFrom and return a
build error on failure). Also update the emission/serialization code that
referenced the old description string to use the inner validated
GroupDescription value (e.g., its inner str) when emitting the <create> stanza,
and ensure GROUP_DESCRIPTION_MAX_LENGTH validation is enforced in that
conversion path.

In `@wacore/src/proto_helpers.rs`:
- Around line 944-952: The test
test_set_context_info_returns_false_on_empty_message only checks the boolean
return value; improve it by also asserting that no fields were mutated — call
msg.set_context_info(context) and then assert that msg.conversation.is_none()
and msg.extended_text_message.is_none() (or the equivalent absence checks on
wa::Message) so it matches the stricter contract used by
set_ephemeral_expiration_returns_false_on_empty_message and will catch
regressions that create unwanted fields.

In `@wacore/src/stanza/groups.rs`:
- Around line 522-528: Add a regression test that asserts overflow behavior for
the limit_sharing_enabled.trigger parsing: when parsing
GroupNotificationAction::LimitSharingEnabled (the code path using
node.attrs().optional_u64("trigger").and_then(|t| t.try_into().ok())), include a
case with trigger="4294967296" and assert the field is treated as None (i.e.,
overflow does not truncate to u32); do the same for the other equivalent parser
block mentioned around the 1042-1063 area to lock in the non-truncating
behavior.

---

Outside diff comments:
In `@wacore/src/iq/groups.rs`:
- Around line 430-453: The code silently drops parent-related flags when both
options.linked_parent and options.is_parent are set; update
build_create_group_node to detect this misuse by adding a
debug_assert!(options.linked_parent.is_none() || !options.is_parent) and also
emit a tracing::warn! (including values of options.linked_parent,
options.closed, options.allow_non_admin_sub_group_creation,
options.create_general_chat) when both are present so the misuse is logged;
alternatively consider converting GroupCreateOptions into a sum type (enum) that
prevents linked_parent and is_parent from coexisting, but at minimum add the
assert+warn in build_create_group_node to surface the issue.
- Around line 1762-1819: The trigger validation must be enforced at encode time
so bad values can't be sent; in SetGroupEphemeralIq::build_iq (where NodeBuilder
is used) replace the debug_assert! with a runtime check on self.trigger against
EPHEMERAL_TRIGGER_MAX and only add the "trigger" attr when it is within range
(e.g., if let Some(trigger) = self.trigger { if trigger <= EPHEMERAL_TRIGGER_MAX
{ b = b.attr("trigger", trigger); } } ), thereby skipping any out-of-range
trigger instead of emitting it; alternatively make the trigger field private and
force construction via enable_with_trigger if you prefer the constructor-only
approach.

In `@wacore/src/proto_helpers.rs`:
- Around line 5-10: The doc comment wrongly references the removed macro
set_context_info_on_message! — update the comment above the
for_each_context_info_message! macro to remove that reference and state the
current truth: say that this list is used by for_each_context_info_message! and
by set_context_info (which inlines try_attach!), and that new WhatsApp message
types with context_info should be added here; keep the comment short and factual
about why the list exists.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 64aedba6-0939-4218-ad2b-108b2b0c66b3

📥 Commits

Reviewing files that changed from the base of the PR and between d87b531 and b444e35.

📒 Files selected for processing (5)
  • src/client.rs
  • wacore/src/iq/contacts.rs
  • wacore/src/iq/groups.rs
  • wacore/src/proto_helpers.rs
  • wacore/src/stanza/groups.rs

Comment thread wacore/src/iq/groups.rs Outdated
Comment thread wacore/src/proto_helpers.rs
Comment thread wacore/src/stanza/groups.rs
* GroupCreateOptions.description: Option<String> -> Option<GroupDescription>
  so create-paths share the same length-cap contract as
  SetGroupDescriptionIq (GROUP_DESCRIPTION_MAX_LENGTH).

* build_create_group_node: debug_assert + log::warn when both
  linked_parent and is_parent are set; existing build-output test is now
  cfg_attr(debug_assertions, should_panic) so it exercises both modes.

* SetGroupEphemeralIq::build_iq: replace debug_assert! with a runtime
  guard that silently drops an out-of-range trigger instead of emitting
  it. Constructor still asserts on misuse; this is the defence-in-depth
  path for direct field assignment.

* parse_action: regression tests for >u32::MAX overflow on
  limit_sharing_enabled.trigger and ephemeral.expiration/trigger
  (try_into already covers it; tests pin the non-truncating behaviour).

* test_set_context_info_returns_false_on_empty_message: also assert
  msg.conversation and msg.extended_text_message stay None.

* with_context_info_fields macro doc: drop stale reference to the
  removed set_context_info_on_message! macro.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant