> ## Documentation Index
> Fetch the complete documentation index at: https://whatsapp-rust.jlucaso.com/llms.txt
> Use this file to discover all available pages before exploring further.

# June 11, 2026 — History sync: secret-presence pre-scan, Arc chat ids, schema-pinned wire tags

> History sync skips prost decode for secret-free messages, shares conversation ids via Arc<str>, and replaces magic wire-tag numbers with generated schema-pinned consts. HistoryMsgSecretRecord field types changed.

## Performance

**History sync: gate prost decode behind secret-presence scan ([#836](https://github.com/oxidezap/whatsapp-rust/pull/836))**

`extract_conversation_fields` previously decoded every `HistorySyncMsg` through prost — a 30-field struct with `String` allocations — to discover that most messages carry no `message_secret` and yield nothing useful. The CodSpeed profile showed 66% of `bench_process_history_sync` inside that decode path, with 56K allocations and 14.3 MB allocated per run on a 500-conversation × 40-message fixture.

A shallow varint walk now runs before prost as a fast-path gate. It checks `WebMessageInfo.message_secret` and `Message.message_context_info.message_secret`; messages with no secret at any level are discarded immediately — no struct decode, no allocation. The scan mirrors prost merge semantics (repeated field occurrences count, malformed bytes degrade to the same skip a failed decode produces), and the flag logic for forwarded/poll/bot messages still runs inside prost on the subset that passes the scan.

**Real-world impact is larger than the bench numbers suggest.** The fixture is secret-dense (2 of 3 messages carry a secret). Production `InitialBootstrap` blobs are secret-sparse, so the pre-scan skips proportionally more full decodes there.

**Benchmark (500 convos × 40 messages, 20k messages, secret-dense fixture):**

| Metric                     | Before   | After            |
| -------------------------- | -------- | ---------------- |
| Allocations / run          | 56,020   | 39,520 (−29.5%)  |
| Allocated bytes / run      | 14.97 MB | 14.32 MB (−4.3%) |
| Instructions (core-pinned) | 2,358.7M | 2,301.7M (−2.4%) |

**Shared conversation id ([#836](https://github.com/oxidezap/whatsapp-rust/pull/836))**

`HistoryMsgSecretRecord.chat_id` is now `Arc<str>`, allocated once per conversation and reference-counted into every record within that conversation. On the bench fixture: 10k clones → 500. `msg_id` switches to `CompactString` (inline up to 24 bytes for the typical 20–22 character WA message IDs), and `secret` switches to `SecretBytes` (inline up to 32 bytes, heap for larger).

## Type safety

**Schema-pinned wire tags ([#836](https://github.com/oxidezap/whatsapp-rust/pull/836))**

`waproto` now generates `tags.rs` at build time (alongside `whatsapp.rs`) from the compiled protobuf descriptor. The file exposes one `pub mod` per proto message with one `pub const FIELD_NAME: u32 = N;` per field. The history-sync wire walkers now reference these consts instead of magic numeric literals, and every `#[prost(tag)]` literal in the hand-written mirror structs is pinned by a compile-time `assert!` block. If `whatsapp.proto` renumbers a field the consts update automatically on next build; if a field referenced by the walkers is renamed or removed, compilation fails rather than the decoder silently reading the wrong wire data.

```rust theme={null}
use waproto::tags;

// Field number consts derived directly from whatsapp.proto
let _ = tags::web_message_info::KEY;           // 1
let _ = tags::web_message_info::MESSAGE;       // 2
let _ = tags::message::MESSAGE_CONTEXT_INFO;   // 35
let _ = tags::history_sync::CONVERSATIONS;     // 2
let _ = tags::history_sync::PUSHNAMES;         // 7
```

## Breaking changes

**`HistoryMsgSecretRecord` field types (`wacore`)**

Three fields changed type. Consumers of `HistoryMsgSecretRecord` need the following updates:

```rust theme={null}
// chat_id: String → Arc<str>
// Borrow as &str (no move):
let chat_str: &str = &*record.chat_id;
// Clone the Arc (cheap refcount bump):
let chat_arc: Arc<str> = Arc::clone(&record.chat_id);
// Convert to owned String:
let chat_string: String = record.chat_id.to_string();

// msg_id: String → CompactString (compact_str::CompactString)
// Borrow as &str:
let id_str: &str = &*record.msg_id;
// Convert to owned String (consumes field):
let id_string: String = record.msg_id.into_string();

// secret: Vec<u8> → SecretBytes
// Borrow as &[u8]:
let key_slice: &[u8] = record.secret.as_slice();
// Convert to Vec<u8> (consumes field):
let key_vec: Vec<u8> = record.secret.into_vec();
```

`SecretBytes` stores secrets ≤32 bytes inline (no heap allocation) and falls back to `Vec<u8>` for larger values. It implements `Deref<Target=[u8]>`, `From<&[u8]>`, `From<Vec<u8>>`, `PartialEq`, `Eq`, and `Debug`. The `INLINE_CAP: usize = 32` constant is public.

**`waproto` build process**

The generated `src/whatsapp.rs` is no longer committed to the repository. It now lives exclusively in `OUT_DIR`. Normal `cargo build` invocations are unaffected.

The `generate` feature flag is removed. `prost-build`, `heck`, and `prost-types` are now unconditional `[build-dependencies]`. If you have `--features generate` in any build scripts, remove it.

To regenerate after modifying `whatsapp.proto`, update the descriptor and rebuild:

```bash theme={null}
scripts/regenerate-proto-desc.sh   # writes waproto/src/whatsapp.desc + .sha256
cargo build -p waproto             # regenerates whatsapp.rs and tags.rs in OUT_DIR
```

The build verifies the descriptor's SHA-256 against `whatsapp.desc.sha256` and aborts with a clear message on mismatch.
