fix(mesh-store): replay pending deliveries to an agent returning from downtime - #30
Merged
Merged
Conversation
… downtime Delivery queues were per-store in-memory maps excluded from serialised state, so a restarted bridge found downtime messages in synced history but was never woken for them: no push, no drain, until something new arrived. Every peer already replicates delivery events in its own queues, so the queues now travel in state snapshots, and applying a snapshot fires onDelivery for events targeting the receiving peer's own agent: a returning process replays exactly what accumulated while it was down. Replay is deduplicated two ways: room messages and DMs this agent has already read are skipped (read receipts mutate the event between snapshots, so a structural key alone would miss and re-fire), and a bounded structural key set covers the rest. An event fired locally is also dropped from the local pending queue, so steady-state snapshots stop carrying what a peer has already consumed; peers that never fired the event keep their copies, which is what a restart replays from. Queues are bounded per target agent (oldest dropped first) and are purged with their agent by the stale cleanup, which previously leaked queue entries for purged agents indefinitely. The wire format's SerialisedState gains the deliveryQueues field, so a mesh must be on a single build, as with the entity revisions.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
🎉 This PR is included in version 1.25.6 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Mearman
added a commit
that referenced
this pull request
Sep 10, 2026
A legacy peer's state_sync payload can genuinely be missing deliveryQueues (#30) or an entity's version field (#29), since nothing validates a state_sync message's shape beyond isMeshMessage's bare method check. normaliseWireState fills in the defaults (empty collections, version 1) before the payload reaches applyStateSync, so an old-build snapshot parses to a complete state instead of throwing. WireStateInput models exactly that tolerance as its own type rather than widening SerialisedState itself, so domain code elsewhere keeps working with the fully-populated type.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #28 — the design decision recorded on the issue: implement replay, because for push bridges the missing wake-up is not noise, it is the "pi never wakes up" failure class from #14 wearing a different hat.
Delivery queues were per-store maps excluded from serialised state, so a restarted bridge found downtime messages in synced history but was never woken for them. Every peer already replicates delivery events in its own queues, so the queues now travel in
SerialisedState, and applying a snapshot firesonDeliveryfor events targeting the receiving peer's own agent — a returning process replays exactly what accumulated while it was down. Drain bridges get the same benefit through their own queue after the merge.Replay dedup is two-layered:
An event fired locally is dropped from the local pending queue, so steady-state snapshots stop carrying what a peer already consumed; peers that never fired keep their copies — that is what a restart replays from. Queues are bounded per agent (oldest dropped first), and the stale-agent cleanup now purges queue entries with their agent, fixing a pre-existing unbounded leak.
Tests: unit replay + dedup + queue bound (wire-accurate snapshots), plus the end-to-end acceptance — a real TLS restart over a persisted identity receives the push for a message sent while it was down. Full suite 20 tests, three consecutive clean rounds, lint/typecheck/build green.
Wire note:
SerialisedStategainsdeliveryQueues, so a mesh needs all peers on builds that understand it, same single-build contract as the entity revisions in #29.