Fix multi-hop replication dedup exclusion to cover directional (sendsTo) peers - #809
Fix multi-hop replication dedup exclusion to cover directional (sendsTo) peers#809ldt1996 wants to merge 2 commits into
Conversation
…p exclusion Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces the qualifiesForMultiHopExclusion helper function to optimize multi-hop deduplication exclusion for directional peers, preventing redundant write deliveries and potential performance degradation during restart replays. It also updates the design documentation and adds comprehensive unit tests. The feedback suggests improving type safety in qualifiesForMultiHopExclusion by replacing the any type for the node parameter with a more specific type like Partial<NodeRecord> | null | undefined.
|
Cross-linking the receive-side half, since this PR refers to it as "concurrent-delivery dedup race, They are complementary, and this PR is the one that removes the field cost. It stops the redundant
#2485's own fix has been sequenced behind harper#2412 (the dual-clock identity model), so this PR is — Claude Fable 5.1 |
…arper#2485 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replication's multi-hop dedup exclusion (
SUBSCRIPTION_UPDATE excludeNodes) only qualified origins withreplicates === trueor a blanket directionalsends, so a directional peer advertising{ sendsTo: [...] }(every config-route peer and add_node directional peer) was never excluded from relay: each subscriber received that origin's writes once per mesh member. On a 16-node production mesh fed ~350 writes/sec through a one-way bridge route, every peer received each write ~15x; each redundant delivery also persisted a same-version audit entry (duplicate transaction-log persistence under concurrent delivery, HarperFast/harper#2485 — confirmed deterministic per delivery, no double-apply; its own fix is sequenced behind HarperFast/harper#2412, so this PR closes the observed cost in the meantime), so a fleet-wide rolling restart made resume replay O(peers^2) and wedged the data database: remote-time pinned at each peer's restart moment for 75+ minutes with sockets healthy andredirects/systemcurrent.The fix extracts the qualifier into
qualifiesForMultiHopExclusion(knownNodes.ts) and adds the subscriber+database-aware branch via the existingrouteEntriesIncludePeer, the exact shape replication/DESIGN.md prescribed for this gap ("a correct fix must be subscriber+database aware"). Both call sites (the initial excluded-list build and the dynamic hdb_nodes updater) now share the predicate, and the receive gate in knownNodes already used equivalentsendsTomatching, so exclusion and delivery agree on what a row advertises. The DESIGN.md bullet documenting the gap is updated to describe the fixed behavior.Verification
unitTests/replication/multiHopExclusionQualifier.test.mjs(16 tests) pins both directions: rows that prove direct delivery to the subscriber+database qualify (string entry, object entry, wildcard database, broadcast target, blanketsends,replicates: true, subscriptions, and the one-way bridge row shape), and rows targeting another peer, another database, or onlyreceivesFromnever qualify (the perf(replication): fast-skip leading duplicates on resume #370/Replication: transitive/proxied re-delivery floods peers with already-applied out-of-order writes (reduce volume; complements harper#1310) #399 data-loss direction).unitTests/security/injectedKeyCustody.test.mjs, which fails identically on an unmodified checkout on the authoring machine (environmental ENOENT), unrelated to this change.For the human reviewer
routeEntriesIncludePeer: a truthy non-string entry with notarget/source/databasefields matches any peer+database (pinned in the new tests). Chosen for consistency: the same helper authorizes receive/send direction, and its docstring requires authorization and exclusion to agree on what an entry covers. The alternative is hardening the exclusion side to ignore malformed entries, which is safer for the data-loss direction but diverges from the auth gates. Cheap to reverse; a "no" costs a small follow-up to the shared helper.replicates === true: if an origin advertisessendsTocovering a subscriber it cannot actually reach, that subscriber loses the relay path until the row changes. Deliberate parity rather than a new risk class, but it is the judgment call at the heart of the change.Cross-model review: no outside reviewer legs were available on the authoring machine (codex/gemini/cursor CLIs not installed), so this PR carries no independent AI review coverage. Generated by Claude Fable 5.
Lavinia, via Claude
Human-Review-Need: 4 @ 6351fdd