Skip to content

fix(event-buffer): release consolidated events in upstream's order - #51

Merged
jlucaso1 merged 1 commit into
mainfrom
fix/event-buffer-release-order
Aug 10, 2026
Merged

fix(event-buffer): release consolidated events in upstream's order#51
jlucaso1 merged 1 commit into
mainfrom
fix/event-buffer-release-order

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

makeEventBuffer released the consolidated kinds in a different order than upstream. A flush walks Object.keys() of the map consolidateEvents builds, so that map's insertion order is two observable things at once: the key order a process() handler iterates, and the order the individual events are re-dispatched to .on() listeners.

Measured against baileys/lib/Utils/event-buffer.js on a buffer holding all eleven kinds:

upstream                    baileyrs (before)
chats.upsert                chats.upsert
chats.update                chats.update
chats.delete                chats.delete
messages.upsert             contacts.upsert        <-- moved up
messages.update             contacts.update        <-- moved up
messages.delete             messages.update
messages.reaction           groups.update          <-- moved up
message-receipt.update      messages.upsert
contacts.upsert             messages.delete
contacts.update             messages.reaction
groups.update               message-receipt.update

No event was lost — buffer:conservation was always clean — but a consumer whose handlers assume upstream's sequence (messages released before the contacts that reference them) saw a different interleaving. The fix reorders the writes in consolidateEvents to match upstream.

Root cause

consolidateEvents grouped its writes by helper rather than by upstream's sequence: every assignArray(...) call ran first, then the four branches that build a non-array payload (messages.upsert, messages.delete, messages.reaction, message-receipt.update). Upstream interleaves the two. The writes are now interleaved the same way, with a comment saying the order is the contract so a later tidy-up does not regroup them.

Registry

This closes event-buffer-release-order, which documented the divergence. isPermutation, its only predicate, goes with it.

event-buffer-merge-precedence composed with it: that entry paired released observations by kind and identity rather than by position, precisely because a field difference and a reordering co-occurred. With the release sequences aligned that slack is unjustified — an entry that still tolerated a reordering would be the thing excusing one — so mergePrecedenceFieldsOnly now pairs positionally and fails outright if a release moved. The harness test that asserted the old composition asserts the opposite; two near-miss pins around it are unchanged.

Registry: 21 open / 25 total → 20 open / 24 total.

Validation

npx tsc --noEmit -p tsconfig.json                    clean
node --test                                          1185 tests, 0 fail
node --test src/__fuzz__/bridge-events.fuzz.test.ts  8/8 (fixed-seed mode, what PRs run)
npx oxlint / oxfmt                                   clean

The new test reads the expectation from upstream at run time rather than hardcoding the list — a frozen list would go stale the moment upstream reorders and would then assert compatibility with a version nobody runs. It also asserts the seed actually produced all eleven kinds, so the order it pins is not the order of whatever survived consolidation.

Negative test. Reverting only the reordering in consolidateEvents and re-running:

not ok 9 - releases the consolidated kinds in upstream’s order
# pass 8  # fail 1

Performance

The change moves statements; it adds no allocation, no branch and no call. Measured to confirm that, rather than asserted.

Harness: one emitter reused across iterations (flush() swaps in a fresh buffer), so each iteration is exactly buffer() → 11 emit() → consolidate → release, with no emitter construction diluting the signal. 20k warm-up iterations, then the measured window. The two implementations alternate within one process so they share JIT warmth and any drift in machine state.

Allocation pressure — 500k flushes, --trace-gc, three independent process pairs:

impl scavenges major GCs cpu-user peak RSS
before 543 1 3032 ms 130.9 MiB
after 543 1 3103 ms 131.7 MiB
before 543 1 3210 ms 131.2 MiB
after 543 1 3039 ms 138.4 MiB
before 543 1 2907 ms 133.6 MiB
after 543 1 3031 ms 129.8 MiB

Scavenge count is identical in every pair — young-generation allocation volume over 500k flushes is unchanged to the GC event, which is the strongest form this measurement takes. RSS varies by ±4% across runs of the same implementation, so it carries no signal here.

Throughput and CPU — 300k flushes per sample, 8 alternating samples each:

before after
wall, median 1745 ms 1759 ms
wall, range 1635–1849 ms 1672–1892 ms
throughput, median 171.9k flush/s 170.6k flush/s
cpu-user, median 1781 ms 1786 ms
heap retained after forced GC −13 … +10 KiB −13 … +29 KiB

The 0.8% median gap is inside the run-to-run spread of either implementation (13%), and the ranges overlap almost completely — the fastest "after" sample beats six of the eight "before" samples. No regression.

Not in this PR

Running the buffer differential in deep mode surfaces one further divergence, present on main before this change (verified by stashing): a contacts.update buffered before a messaging-history.set for the same id resolves to a different name than upstream's ("" here, the later upsert's value upstream). Fixed-seed mode — what pull requests run — is green on both sides. Worth its own investigation rather than widening this one.


Generated by Claude Code


Summary by cubic

Fixes the event buffer to release consolidated events in the same order as upstream baileys, so message events come before related contacts/groups. This aligns handler iteration and listener dispatch order with upstream and removes the temporary ordering allowlist.

  • Bug Fixes

    • Reordered writes in consolidateEvents to match upstream key insertion order used by Object.keys() during flush.
    • No new allocations, branches, or calls; performance is unchanged in measured runs.
  • Tests & Harness

    • Added a compatibility test that reads upstream’s order at runtime via baileys/lib/Utils/event-buffer.js and asserts exact match.
    • Removed the event-buffer-release-order allowlist and isPermutation; tightened mergePrecedenceFieldsOnly to be position-sensitive and updated harness tests accordingly.

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

Summary by CodeRabbit

  • Bug Fixes

    • Improved event processing order so message updates are emitted before contact and group changes.
    • Improved consistency with upstream event ordering during event consolidation.
  • Tests

    • Added compatibility coverage to verify consolidated events follow the expected ordering.
    • Strengthened diagnostics for detecting unexpected event reordering and merge differences.

A flush walks `Object.keys()` of the consolidated map, so the order
`consolidateEvents` writes its keys is the order a `process()` handler
iterates and the order the individual events reach `.on()` listeners.
This buffer wrote contacts and groups before the message kinds, where
upstream writes them after, so a consumer whose handlers assume
upstream's sequence saw a different interleaving. No event was lost —
the conservation target was always clean — but the ordering is part of
what "upstream-compatible" means for this buffer.

Reordering the writes is the whole fix; the work done per flush is
unchanged. The differential entry that documented the divergence is
removed, and the merge-precedence predicate it composed with is
tightened from multiset pairing to positional pairing, since a
reordering is a finding again rather than something an entry excuses.

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

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b7c1a279-9900-40d1-acd0-66f25b9ddfa7

📥 Commits

Reviewing files that changed from the base of the PR and between f2e34d1 and de2874f.

📒 Files selected for processing (5)
  • src/Utils/event-buffer.ts
  • src/__fuzz__/bridge-events.fuzz.test.ts
  • src/__fuzz__/harness/__tests__/harness.test.ts
  • src/__fuzz__/harness/divergence.ts
  • src/__tests__/event-buffer-compatibility.test.ts

📝 Walkthrough

Walkthrough

The event buffer now emits message events before contact and group events. A compatibility test compares local ordering with the upstream implementation. Differential merge checks now pair events by position and reject reordered sequences.

Changes

Event ordering compatibility

Layer / File(s) Summary
Align consolidated event ordering
src/Utils/event-buffer.ts, src/__tests__/event-buffer-compatibility.test.ts
Message events are registered before contact and group events. The compatibility test compares all supported event kinds with the upstream buffer ordering.
Enforce positional divergence matching
src/__fuzz__/harness/divergence.ts, src/__fuzz__/harness/__tests__/harness.test.ts, src/__fuzz__/bridge-events.fuzz.test.ts
Merge-precedence checks now pair events by position, reject reordered sequences, and accept only pinned field differences. Related diagnostics and test expectations were updated.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Poem

I’m a rabbit guarding the event stream,
Keeping message keys in upstream order.
No shuffled hops pass the merge check,
Pinned differences stay on record.
All eleven kinds now march together.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: releasing consolidated events in upstream Baileys order.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@jlucaso1
jlucaso1 merged commit 31c8576 into main Aug 10, 2026
6 checks passed
@jlucaso1
jlucaso1 deleted the fix/event-buffer-release-order branch September 9, 2026 23:31
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