Skip to content

fix(mesh-store): converge state sync by per-entity version - #29

Merged
Mearman merged 1 commit into
mainfrom
feat/state-sync-versioning
Sep 9, 2026
Merged

fix(mesh-store): converge state sync by per-entity version#29
Mearman merged 1 commit into
mainfrom
feat/state-sync-versioning

Conversation

@Mearman

@Mearman Mearman commented Sep 9, 2026

Copy link
Copy Markdown
Member

Fixes #27.

The state_sync receive path merged add-only, so an entity that changed while a peer was fully disconnected could never be corrected by a sync — the returning peer kept its stale copy, and anyone receiving that stale snapshot kept whatever they already had. Room member lists had the mirror problem: the union merge could add members but never remove a leaver.

Agents and rooms now carry a monotonic revision the mutating store bumps and embeds in the record. Merges — both snapshot syncs and incremental patches — are tri-state:

  • strictly higher version replaces the record wholesale — what heals renames, edits, and leaves;
  • equal version unions memberships and subscriptions — concurrent joins from the same base both survive;
  • lower version is rejected — a stale holder cannot regress a current one.

Message and DM histories merge by message id, adding unseen entries and unioning read receipts. Owner-authority merging was considered and rejected during design (a rejoining peer can carry an old copy of a third peer's entity and would clobber it); the reasoning is on #27.

The merge is extracted to a public applyStateSync(state) seam so the convergence contract is unit-testable without transports: stale holder heals, stale snapshot rejected, room membership healing including leaves, and history/read-receipt merging. Wired into pnpm test (17 tests total, five clean consecutive rounds during development plus lint/typecheck/build).

Note: the version travels inside the entity records, so a mesh needs all peers on builds that understand it — no wire negotiation, same as the rest of the protocol.

The state_sync receive path merged add-only, so an entity that changed
while a peer was fully disconnected could never be corrected by a sync:
the returning peer kept its stale copy, and a peer receiving that stale
snapshot kept whatever it already had. Room member lists had the mirror
problem — the union merge could add members but never remove a leaver.

Give agents and rooms a monotonic revision the mutating store bumps and
embeds in the record. Merges (both state_sync snapshots and incremental
patches) are now tri-state: a strictly higher version replaces the
record wholesale (which is what heals renames, edits, and leaves), an
equal version unions memberships and subscriptions (so concurrent joins
from the same base both survive), and a lower version is rejected (so a
stale holder cannot regress a current one). Message and DM histories
merge by message id, adding unseen entries and unioning read receipts.
Owner-authority merging was rejected during design: a rejoining peer
can carry an old copy of a third peer's entity and would clobber it.

The merge itself is extracted to a public applyStateSync seam so the
convergence contract is unit-testable without transports; the tests
cover both directions (stale holder heals, stale snapshot rejected),
room membership healing including leaves, and history/read-receipt
merging.

The wire format carries the version inside the entity records, so a
mesh must be on a single build; the serial number fix and this change
ship together.
@Mearman
Mearman marked this pull request as ready for review September 9, 2026 09:52
@Mearman
Mearman merged commit 4ea4a73 into main Sep 9, 2026
5 checks passed
@Mearman
Mearman deleted the feat/state-sync-versioning branch September 9, 2026 09:52
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review Completed 2026-09-09T10:06:23.497795Z 086ac60 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

state_sync cannot converge entities that changed while a peer was fully disconnected

1 participant