Skip to content

smax: fix optional-mixin discriminator bleed + capture <ack> publish read-shapes - #26

Merged
jlucaso1 merged 5 commits into
mainfrom
claude/whatspec-protocol-analysis-7a7kn5
Jul 6, 2026
Merged

smax: fix optional-mixin discriminator bleed + capture <ack> publish read-shapes#26
jlucaso1 merged 5 commits into
mainfrom
claude/whatspec-protocol-analysis-7a7kn5

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Two self-contained smax improvements, plus a version refresh to give them a reproducible base on top of the merged #25.

This started as "capture incoming message content-types" but the investigation showed that premise was already satisfied: regular incoming message bodies are legacy protobuf (proto + incoming domains), and the smax WASmaxInMessageDeliver* family is newsletter-only with a single root that srvreq already captures — including the full 14-way content-type union. What was actually left is a real correctness bug and one genuinely-uncaptured smax surface (the send acks). This PR does both.

1. fix(smax) — don't bubble an optionally-consumed mixin's discriminators

A smax parser's same-node cross-module mixins bubble their discriminating assertions up to the caller (so an error variant inherits the type="error" its error mixin pins). But a mixin whose result the tail consumes optionally (X.success ? X.value : null) does not constrain the node — the parse succeeds whether or not it matched — so its asserts are not invariants and must not bubble.

They were bubbling unconditionally, stamping discriminators a message need not satisfy:

  • srvreq WASmaxInMessageDeliverNewsletterRequest gained a bogus type="media" from the optional ReceiverContentTypeMediaRCATMixin, contradicting the 14-way newsletterMessageFanoutContent disjunction right below it.
  • iq union variants inherited stray discriminators from optional mixins: NewsletterWAMOEmpty carried both edit="3" and edit="8" (impossible for one node); CreateParticipantAdded falsely required addressable="false" (from an optional participant mixin), colliding with the real ParticipantNotAddressable variant that legitimately pins it.

The fix computes, per parser, the vars its tail consumes optionally and suppresses the bubble for those — a var also hard-guarded by if(!X.success) return X stays required. This is the safe direction: bubble by default (preserving all existing behavior), suppress only the optional case. Every genuine discriminator is preserved (guarded-mixin bubbles and direct literal(...) pins are untouched); only the bogus bleed is removed. Verified case-by-case against a full regen — 1 srvreq + 6 iq assertion removals, all confirmed bogus, zero legitimate discriminators lost. Adds a regression test covering the optional-vs-required split.

2. feat(incoming) — capture smax <ack> publish read-shapes

A client publish/send receives an <ack> parsed by the newer WASmaxParseUtils railway (WASmaxIn*Publish*Response{Success,Negative}), not the legacy WADeprecatedWapParser — so the incoming domain, which only ran the legacy pass, was blind to every send-ack read-shape.

Adds a smax pass to the incoming scan: for each WASmaxIn*Response{Success,Negative} root asserting the top-level <ack>, recover its field tree through the shared Resolver (the same machinery srvreq/iq use), folding in the ack envelope (from/class/id/t), the edit/revoke/pin sub-mixins, the paid-conversation and question-response/message-ack disjunctions, and the negative-ack error/backoff fields. iq …Response* modules assert iq and are dropped by the ack tag gate; …Mixin fragments are folded into a root, never catalogued standalone.

Recovers 5 ack read-shapes (incoming 11→16): message-publish newsletter ack (success + negative), status-publish newsletter status ack (success + negative), and receipt-publish view ack. Purely additive to the IR (new entries only, no schema change). Adds a scan test.

3. chore — refresh generated specs to WhatsApp 2.3000.1042692684

Mechanical regen against current bundles so the two changes above sit on a reproducible base after #25 merged and main auto-bumped. Version-string bump across domains plus real WA content churn (mexOperations 131→129, abPropsConfigs 2047→2050, wamEvents 428→429). No code — the fix and ack domain land in the following commits, keeping each concern isolated.

Validation

Full CI-equivalent gate green locally: fmt, clippy --workspace --all-targets --all-features -D warnings, cargo test --workspace (incl. the two new smax tests), wa-ir --features schema, whatspec --no-default-features, C-free (cargo tree shows no ring/aws-lc/openssl-sys), validate-schemas.py generated, and the byte-identical determinism --check.

🤖 Generated with Claude Code

https://claude.ai/code/session_013111jePsSca7epxMugRn2A


Generated by Claude Code


Summary by cubic

Fixes smax optional-mixin discriminator bleed, adds smax <ack> publish read-shapes to incoming, and refreshes generated specs to WhatsApp 2.3000.1042692684.

  • Bug Fixes

    • Stop bubbling discriminators from same-node mixins when their result is used optionally; tighten detection to only suppress on X.success ? X.value : null (same var) and handle bare ternary spreads in babelHelpers.extends(...).
    • Preserve guarded-mixin and direct literal(...) asserts; only bogus asserts are removed.
    • IR cleanup: remove false type="media" in srvreq newsletter deliver and stray edit/addressable="false" asserts in iq. Adds tests for the optional-vs-required split and bare-extends case.
    • Perf: precompute the guarded-var set once per function (no behavior change).
  • New Features

    • Capture smax <ack> read-shapes by scanning WASmaxIn*Publish*Response{Success,Negative} roots via the shared Resolver.
    • Adds 5 incoming acks: newsletter message publish (success/negative), newsletter status publish (success/negative), and receipt publish view. Ignores …Mixin exports and iq responses; dedupes repeated tag asserts. Additive only.

Written for commit ca5bf13. Summary will update on new commits.

claude added 2 commits July 6, 2026 21:29
Mechanical regen against current bundles to give the follow-up smax fix and
the new message-publish-ack domain a reproducible base on top of the merged
#25. Version-string bump across all domains plus real WA content churn:
mexOperations 131→129, abPropsConfigs 2047→2050, wamEvents 428→429. No code
change — the smax fix and ack domain land in the following commits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013111jePsSca7epxMugRn2A
A smax parser's same-node cross-module mixins bubble their discriminating
assertions up to the caller, so an error variant inherits the `type="error"`
its error mixin pins. But a mixin whose result the tail consumes *optionally*
(`X.success ? X.value : null`) does not constrain the node — the parse
succeeds whether or not it matched — so its asserts are not invariants and
must not bubble.

They were bubbling unconditionally, stamping discriminators a message need
not satisfy:
- srvreq `WASmaxInMessageDeliverNewsletterRequest` gained a bogus
  `type="media"` from the optional `ReceiverContentTypeMediaRCATMixin`, even
  though the newsletter's real content type is the 14-way disjunction below
  it (`newsletterMessageFanoutContent`).
- iq union variants inherited stray discriminators from optional mixins:
  `NewsletterWAMOEmpty` carried both `edit="3"` and `edit="8"` (impossible for
  one node); `CreateParticipantAdded` falsely required `addressable="false"`
  (from an optional participant mixin), colliding with the real
  `ParticipantNotAddressable` variant that legitimately pins it.

Compute, per parser, the vars its tail consumes optionally and suppress the
bubble for those (a var that is also hard-guarded by `if(!X.success) return X`
stays required). Every genuine discriminator — the guarded-mixin and direct
`literal(...)` pins — is preserved; only the bogus bleed is removed. Adds a
regression test covering the optional-vs-required split.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013111jePsSca7epxMugRn2A
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jlucaso1, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 36 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 93bd16e5-b414-4e9c-b99d-d56950a2d32d

📥 Commits

Reviewing files that changed from the base of the PR and between e68867e and ca5bf13.

⛔ Files ignored due to path filters (13)
  • generated/abprops/index.json is excluded by !**/generated/**
  • generated/appstate/index.json is excluded by !**/generated/**
  • generated/enums/index.json is excluded by !**/generated/**
  • generated/incoming/index.json is excluded by !**/generated/**
  • generated/iq/index.json is excluded by !**/generated/**
  • generated/manifest.json is excluded by !**/generated/**
  • generated/mex/index.json is excluded by !**/generated/**
  • generated/notif/index.json is excluded by !**/generated/**
  • generated/proto/WAProto.proto is excluded by !**/generated/**
  • generated/srvreq/index.json is excluded by !**/generated/**
  • generated/stanza/index.json is excluded by !**/generated/**
  • generated/tokens/index.json is excluded by !**/generated/**
  • generated/wam/index.json is excluded by !**/generated/**
📒 Files selected for processing (2)
  • crates/wa-scan/src/incoming.rs
  • crates/wa-scan/src/response_smax.rs

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.

A client publish/send receives an `<ack>` parsed by the newer WASmaxParseUtils
railway (`WASmaxIn*Publish*Response{Success,Negative}`), not the legacy
WADeprecatedWapParser — so the incoming domain, which only ran the legacy pass,
was blind to every send-ack read-shape.

Add a smax pass to the incoming scan: for each `WASmaxIn*Response{Success,
Negative}` root asserting the top-level `<ack>`, recover its field tree through
the shared Resolver (the same machinery srvreq/iq use), folding in the ack
envelope (from/class/id/t), the edit/revoke/pin sub-mixins, the paid-conversation
and question-response/message-ack disjunctions, and the negative-ack
error/backoff fields. iq `…Response*` modules assert `iq` and are dropped by the
ack tag gate; `…Mixin` fragments are folded into a root, never catalogued
standalone.

Recovers 5 ack read-shapes (incoming 11→16): the message-publish newsletter ack
(success + negative), status-publish newsletter status ack (success + negative),
and receipt-publish view ack. Additive to the IR (new entries only, no schema
change). Adds a scan test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013111jePsSca7epxMugRn2A
@jlucaso1
jlucaso1 force-pushed the claude/whatspec-protocol-analysis-7a7kn5 branch from abbb992 to df252ca Compare July 6, 2026 21:48

@cubic-dev-ai cubic-dev-ai 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.

1 issue found and verified against the latest diff

Confidence score: 4/5

  • In crates/wa-scan/src/response_smax.rs, recomputing guarded_success_vars(body) inside the filter can repeatedly traverse the AST and allocate a new HashSet per optional variable, which risks avoidable CPU/memory overhead in larger scans; this looks like a performance inefficiency rather than a functional break, so precompute the set once per function before merging to de-risk runtime cost.

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread crates/wa-scan/src/response_smax.rs Outdated
`guarded_success_vars(body)` was called inside the `suppressed` filter
closure, so it re-traversed the AST and allocated a fresh HashSet for every
optionally-consumed var. Hoist it to a single binding before the filter.
Behavior-identical (same set) — generated output unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013111jePsSca7epxMugRn2A

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Auto-approved: Bug fix for optional-mixin discriminator bleed, new incoming ack read-shape scanning, and a mechanical version refresh. All changes are well-tested and additive to the IR; no production or security impact.

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

Fixes a discriminator-bubbling bug in the smax parser where optional same-node mixins were incorrectly stamping assertions on nodes that need not satisfy them, and adds incoming coverage for the <ack> read-shapes produced by WASmaxIn*Publish*Response{Success,Negative} roots that the legacy parser never reached.

  • Bug fix: analyze_function now precomputes a suppressed set of vars whose mixin result is consumed optionally in the tail makeResult; these vars' discriminators are no longer bubbled. Vars still hard-guarded by if (!X.success) return X remain required and are excluded from suppression. Includes two regression tests (required vs optional mixin, bare-ternary spread in extends).
  • Feature: scan_incoming_smax_acks scans WASmaxIn*Response{Success,Negative} modules via the shared Resolver, filters to roots asserting the top-level ack tag, deduplicates repeated tag assertions, and adds them to the incoming catalog — recovering 5 new ack shapes (11→16).
  • Chore: Regenerated specs against WhatsApp 2.3000.1042692684 (version bumps, mexOperations 131→129, abPropsConfigs 2047→2050, wamEvents 428→429).

Confidence Score: 5/5

Safe to merge — both the discriminator-bleed fix and the ack-shape capture are logically sound, conservatively implemented, and covered by new regression tests.

The optional-mixin fix suppresses only the bubbling step for vars confirmed to be consumed optionally in the tail, falling back to the existing bubble-by-default behavior in every ambiguous case. The ack scanner is purely additive and guarded by both a name filter and an assertion-tag gate. The one naming inconsistency in the module filter is a future-proofing concern, not a current defect.

No files require special attention — the logic changes are self-contained to response_smax.rs and incoming.rs, both with direct test coverage.

Important Files Changed

Filename Overview
crates/wa-scan/src/response_smax.rs Adds optional-mixin suppression logic: guarded_success_vars, optionally_consumed_vars, tail_make_result_arg, optional_ternary_var, and collect_optional_vars. The module filter uses contains("Mixin") instead of ends_with("Mixin"), slightly over-broad relative to the stated intent but safe for current WA naming conventions.
crates/wa-scan/src/incoming.rs Adds scan_incoming_smax_acks to recover read-shapes via the shared Resolver; correctly gates on the ack tag assertion, deduplicates repeated asserts, and excludes Mixin and iq modules. Well-tested with a new scan test.
generated/incoming/index.json Adds 5 new ack read-shapes (11 to 16): newsletter message publish success/negative, newsletter status publish success/negative, and receipt publish view. Purely additive.
generated/iq/index.json Removes 6 bogus optional-mixin discriminators (stray edit and addressable=false asserts) as a direct consequence of the bug fix.
generated/srvreq/index.json Removes bogus type=media from WASmaxInMessageDeliverNewsletterRequest and refreshes version strings.
generated/manifest.json Version bump to WhatsApp 2.3000.1042692684 and domain entry count updates reflecting the new incoming ack shapes.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[analyze_function] --> B[guarded_success_vars]
    A --> C[tail_return]
    C --> D[optionally_consumed_vars]
    D --> E[tail_make_result_arg]
    E --> F[collect_optional_vars]
    F --> G{arg shape?}
    G -->|X.success ? X.value : _| H[optional_ternary_var - add X]
    G -->|babelHelpers.extends| I[recurse each arg]
    G -->|object literal| J[walk properties]
    B --> K[suppressed = optionally_consumed minus guarded]
    D --> K
    K --> L[classify_call suppress_bubble?]
    L -->|same-node and not suppress| M[bubble assertions]
    L -->|same-node and suppress| N[skip bubble]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[analyze_function] --> B[guarded_success_vars]
    A --> C[tail_return]
    C --> D[optionally_consumed_vars]
    D --> E[tail_make_result_arg]
    E --> F[collect_optional_vars]
    F --> G{arg shape?}
    G -->|X.success ? X.value : _| H[optional_ternary_var - add X]
    G -->|babelHelpers.extends| I[recurse each arg]
    G -->|object literal| J[walk properties]
    B --> K[suppressed = optionally_consumed minus guarded]
    D --> K
    K --> L[classify_call suppress_bubble?]
    L -->|same-node and not suppress| M[bubble assertions]
    L -->|same-node and suppress| N[skip bubble]
Loading

Reviews (2): Last reviewed commit: "fix(smax): tighten optional-ternary dete..." | Re-trigger Greptile

Comment thread crates/wa-scan/src/response_smax.rs
Comment thread crates/wa-scan/src/response_smax.rs
…ession

Two edge-case hardenings in `collect_optional_vars` (no current bundle hits
either — output unchanged — but they keep the discriminator-bleed fix robust
against future WA patterns):

- An optional read is now recognized only when the ternary's test is the same
  var's `.success` bit as the consequent's `.value` (`X.success ? X.value :
  null`). A `cond ? X.value : fallback` with an unrelated test no longer marks
  X optional, which would have wrongly suppressed a genuine discriminator.
- A bare `X.success ? X.value : null` spread straight into
  `babelHelpers.extends(…)` (not wrapped in an object literal) is now detected
  as optional; previously it matched neither the object nor the extends arm, so
  such a mixin's discriminator would still bubble.

Factored the check into `optional_ternary_var` and applied it in both the
object-property and extends-arg paths. Adds a regression test for the bare
extends spread.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013111jePsSca7epxMugRn2A

@cubic-dev-ai cubic-dev-ai 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.

0 issues found across 1 file (changes from recent commits).

Requires human review: Non-trivial changes to core parser analysis logic (mixin discriminator fix and new ack scanning), affecting generated spec correctness. Requires human review despite clean cubic report.

Re-trigger cubic

@jlucaso1
jlucaso1 merged commit fdfe824 into main Jul 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants