Skip to content

feat(abprops): vendor typed A/B-props registry, drop hand-maintained config_codes - #729

Merged
jlucaso1 merged 2 commits into
mainfrom
feat/abprops-typed-vendored
Jun 5, 2026
Merged

feat(abprops): vendor typed A/B-props registry, drop hand-maintained config_codes#729
jlucaso1 merged 2 commits into
mainfrom
feat/abprops-typed-vendored

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Replaces the hand-curated config_codes (11 bare u32s) with the whatspec-generated typed A/B-props registry, vendored as wacore::iq::abprops. Three modules mirror WA Web's own split (web 1775, hybrid 312, group 13 = 2100 flags); each flag is a pub const AbProp { name, code, value_type, default }. Refresh by re-copying whatspec's generated/abprops/abprops.rs and running cargo fmt.

Type-safety + usability

Cache getters (get / is_enabled / is_enabled_or / get_int / watch / watch_many) now take a typed AbProp instead of a magic u32. Callsites reference named flags with metadata:

self.ab_props.is_enabled(abprops::web::PRIVACY_TOKEN_SENDING_ON_GROUP_CREATE).await

apply_props stays keyed by the wire u32 (the server sends codes). props::WATCHED (the 11 flags this client reads) seeds the cache interest set, replacing config_codes::ALL.

Zero binary cost for unused flags

consts emit no symbol unless referenced, so the ~2089 unused flags cost nothing in the binary (this is const semantics, not even LTO-dependent). Confirmed nothing references the per-module or top-level ALL aggregates, so those 2100-entry arrays are never emitted either. Only the flags reachable from WATCHED (and their name strings) materialize.

Stale flags

Two flags the current WA Web bundle no longer ships (privacy_token_only_check_lid 15491, profile_pic_privacy_token 9666) are absent from the registry. They live in props::stale as explicit consts to preserve behavior: the server never sends them, so gating on them always falls to the callsite default (this was already the case). Worth revisiting whether those gated features still make sense, but out of scope here.

LID default now matches upstream

LID_TRUSTED_TOKEN_ISSUE_TO_LID previously used a custom true default; it now follows the upstream default (false). The server overrides A/B-props per-account at runtime, so the custom default was both divergent from WA Web and unnecessary.

Verification

cargo build, cargo clippy --all-targets -- -D warnings, and the full test suite (excluding e2e) all pass. The drift cross-check confirmed the 9 live codes match the registry (the old curated names were stale: _SENDING_ infix, WA_ prefix).

…config_codes

Replace the hand-curated `config_codes` (11 bare `u32`s) with the whatspec-generated typed registry, vendored as `wacore::iq::abprops` (modules `web`/`hybrid`/`group`, 2100 flags). Each flag is a `pub const AbProp { name, code, value_type, default }`. Refresh by re-copying whatspec's `generated/abprops/abprops.rs` and running `cargo fmt`.

Zero binary cost for the unused flags: `const`s emit no symbol unless referenced (not even LTO-dependent), and nothing references the per-module or top-level `ALL` aggregates, so only the flags in `props::WATCHED` (and their name strings) land in the binary.

The cache getters (`get`/`is_enabled`/`is_enabled_or`/`get_int`/`watch`) now take a typed `AbProp` instead of a magic `u32`; callsites reference named flags (`abprops::web::PRIVACY_TOKEN_SENDING_ON_GROUP_CREATE`, ...). `apply_props` stays keyed by the wire `u32`. `props::WATCHED` (the 11 flags this client reads) seeds the interest set, replacing `config_codes::ALL`.

Two flags the current WA Web bundle no longer ships (`privacy_token_only_check_lid` 15491, `profile_pic_privacy_token` 9666) are absent from the registry; they live in `props::stale` as explicit consts to preserve behavior (the server never sends them, so gating always falls to the callsite default).

`LID_TRUSTED_TOKEN_ISSUE_TO_LID` now follows the upstream default (`false`) instead of our custom `true`: the server overrides it per-account at runtime, so the custom default was both divergent and unnecessary.

Verified: build, clippy --all-targets -D warnings, and the full test suite (excluding e2e) all pass.
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Refined privacy-token behavior for 1:1 messages, group operations, and profile pictures to follow updated feature flags and timing rules.
  • Chores

    • Migrated A/B property/config handling to a new AbProp-based system and updated related caches and defaults for more consistent config behavior.

Walkthrough

Wacore props are migrated from numeric config codes to typed AbProp values. AbPropsCache API and WATCHED seed are updated, and all privacy-token gating (contacts, groups, send paths) now reads AbProp constants from abprops::web or props::stale.

Changes

Privacy-Token Feature Flag Type Migration

Layer / File(s) Summary
AbProp module structure and property definitions
wacore/src/iq/mod.rs, wacore/src/iq/props.rs
Adds pub mod abprops;, removes numeric config_codes, introduces stale with explicit AbProp constants and a WATCHED slice used to seed interest.
AbPropsCache public API migration to AbProp types
wacore/src/store/ab_props.rs
Cache is seeded from WATCHED; watch/watch_many accept AbProp; get, is_enabled, and get_int take AbProp; fallbacks derive from AbProp::default; tests updated to use synthetic flags and production web::* AbProps.
Contacts privacy-token check
src/features/contacts.rs
PROFILE_PIC_PRIVACY_TOKEN gate switched to wacore::iq::props::stale::PROFILE_PIC_PRIVACY_TOKEN and uses is_enabled(...).
Groups privacy-token checks
src/features/groups.rs
Three group-related privacy-token flags now reference wacore::iq::abprops::web (group-create, participant-add) and wacore::iq::props::stale (PRIVACY_TOKEN_ONLY_CHECK_LID).
Send path token gating and config
src/send.rs
maybe_include_tc_token and tc-token bucket config now read wacore::iq::abprops::web constants (tctoken/cstoken gates, durations, buckets, sender-specific variants); resolve_issuance_jid() uses web::LID_TRUSTED_TOKEN_ISSUE_TO_LID with upstream default false.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

api-design, breaking-change

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: vendoring a typed A/B-props registry and removing hand-maintained config_codes.
Description check ✅ Passed The description provides comprehensive detail about the changes, objectives, type-safety improvements, binary cost implications, and verification steps.
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 feat/abprops-typed-vendored

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.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 2,925 2,925 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,446 8,446 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 31,317 31,317 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 13,827 13,827 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 49,485 49,485 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 55,001 55,001 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 1,679 1,679 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 4,393 4,393 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 113,204 113,202 +0.0%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,622 1,656,730 -0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 651,796 651,752 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 875,731 875,891 -0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,083,456 2,083,607 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 748,255 748,882 -0.1%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,326,026 1,329,679 -0.3%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,391,528 4,373,617 +0.4%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 514,780 518,241 -0.7%
binary_benchmark::marshal_group::bench_marshal_allocating 45,395 45,395 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,445 45,445 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,348 66,348 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,506 43,506 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,501 45,501 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,936 4,936 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,967 4,967 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,738 6,738 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,539 528,539 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,152 528,152 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,398 529,398 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,742 5,417,742 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,091 5,362,091 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,430 13,276,430 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 1,850 1,850 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 29,217 29,217 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 618 618 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 672,890 672,890 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 3,736 3,736 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 3,840 3,840 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 48,274 48,274 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 3,866 3,866 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 48,335 48,335 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 5,206 5,206 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 66,659 66,659 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 310,312 310,312 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 8,282 8,282 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 254 254 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 91 91 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 292 292 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 137 137 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 317 317 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 145 145 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 27,425 27,425 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 10,725 10,725 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 4,140,826 4,141,817 -0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,131 100,133 -0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 4,264,189 4,264,189 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 100,399 100,399 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 210,262 210,262 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 496,921 496,921 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 508,528 507,669 +0.2%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,979,811 11,976,630 +0.0%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 2,466,138 2,466,138 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 4,902,492 4,904,752 -0.0%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,043,397 2,043,397 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 37,414 37,414 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 3,617,967 3,617,967 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 230,648 230,648 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 9,980,959 9,980,959 +0.0%
No significant changes detected.

@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: 8938f585d0

ℹ️ 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/send.rs

@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

🤖 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/store/ab_props.rs`:
- Around line 84-87: The is_enabled method currently calls is_enabled_or(prop,
false) which ignores the AbProp's typed default; change is_enabled(&self, prop:
AbProp) to call is_enabled_or(prop, prop.default().as_bool()) (or equivalent
retrieval of the AbProp default) so missing flags use AbProp.default; keep
is_enabled_or for explicit overrides. Update references in tests to add a
regression that asserts stale::PROFILE_PIC_PRIVACY_TOKEN (declared with
AbDefault::Bool(true)) returns true from is_enabled() when not set.
🪄 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: 1b46432a-7370-4921-9a22-0219a0b4724d

📥 Commits

Reviewing files that changed from the base of the PR and between a0c7bc2 and 8938f58.

📒 Files selected for processing (7)
  • src/features/contacts.rs
  • src/features/groups.rs
  • src/send.rs
  • wacore/src/iq/abprops.rs
  • wacore/src/iq/mod.rs
  • wacore/src/iq/props.rs
  • wacore/src/store/ab_props.rs

Comment thread wacore/src/store/ab_props.rs Outdated
…from the registry

Address the adversarial review of this PR. The typed `AbProp` already carries its
upstream `default`, but the getters only read `.code`, so every callsite re-supplied
the default by hand — two sources of truth that could silently diverge on a registry
regen.

`is_enabled(prop)` now falls back to `prop.default` (Bool), and `get_int(prop)` to
`prop.default` (Int), dropping the explicit-default `is_enabled_or` and the 2-arg
`get_int`. Callsites lose their literals: the `604800`/`4` tctoken magic numbers and
the `false`/`true` flags now come from the registry. Behavior is unchanged — every
removed literal already equaled the registry default (verified).

Also refresh the stale `ab_props.rs` doc comments (`config_code` terminology and the
outdated "~1,200 props" count).

The reviewer's other note (getters take `AbProp` by value vs `&AbProp`) is declined:
`AbProp` is `Copy` and a 48-byte copy on an async RwLock + HashMap path is negligible,
and by-value reads cleaner than threading `&` through every callsite.

Verified: build, clippy --all-targets -D warnings, full test suite (excluding e2e) pass.

@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.

Caution

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

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

133-174: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Add a regression test for a default-true flag returning true when missing.

Look, the core is_enabled() fix is solid now — it actually respects AbProp.default like it should. But we're still missing test coverage for the default-true scenario. stale::PROFILE_PIC_PRIVACY_TOKEN has AbDefault::Bool(true), and we should verify is_enabled() returns true when the server never sent that prop.

Meta built its empire on test coverage. This is how we make sure nobody accidentally breaks this behavior again.

🧪 Suggested test addition
#[tokio::test]
async fn is_enabled_honors_default_true_flag() {
    use crate::iq::props::stale;
    
    let cache = AbPropsCache::new();
    // PROFILE_PIC_PRIVACY_TOKEN has default: AbDefault::Bool(true)
    // Server never sent it, so is_enabled must return true
    assert!(cache.is_enabled(stale::PROFILE_PIC_PRIVACY_TOKEN).await);
}
🤖 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/store/ab_props.rs` around lines 133 - 174, Add a regression test
that constructs an AbPropsCache and asserts that is_enabled returns true for a
flag whose AbDefault is Bool(true) when no prop was sent; specifically, create a
#[tokio::test] that uses AbPropsCache::new() and calls
cache.is_enabled(stale::PROFILE_PIC_PRIVACY_TOKEN).await to assert true,
referencing AbPropsCache and is_enabled so the test verifies the default-true
behavior is preserved.
🤖 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.

Outside diff comments:
In `@wacore/src/store/ab_props.rs`:
- Around line 133-174: Add a regression test that constructs an AbPropsCache and
asserts that is_enabled returns true for a flag whose AbDefault is Bool(true)
when no prop was sent; specifically, create a #[tokio::test] that uses
AbPropsCache::new() and calls
cache.is_enabled(stale::PROFILE_PIC_PRIVACY_TOKEN).await to assert true,
referencing AbPropsCache and is_enabled so the test verifies the default-true
behavior is preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 446c53ba-a5c6-467f-b486-ae9d01c85523

📥 Commits

Reviewing files that changed from the base of the PR and between 8938f58 and bf7c903.

📒 Files selected for processing (3)
  • src/features/contacts.rs
  • src/send.rs
  • wacore/src/store/ab_props.rs

@jlucaso1
jlucaso1 merged commit 3f6ffe4 into main Jun 5, 2026
12 checks passed
@jlucaso1
jlucaso1 deleted the feat/abprops-typed-vendored branch June 5, 2026 16:40
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