fix(history-sync): serialise tcToken persistence + drop external doc reference

Addresses 4 PR #390 review findings (Codex P1 + Copilot ×3):

#1 Codex P1 (real, MAJOR) — `storeTcTokensFromHistorySync` fire-and-forget
   created a write race when multiple history-sync chunks arrived in parallel
   (common during reconnect/QR scan). The function does a non-atomic
   read-then-merge-then-write: chunk B could read `existing` before chunk A
   wrote, then commit later and overwrite chunk A's newer timestamp. Even
   worse, the merged `__index` write could drop JIDs added by the other
   chunk, leaving stale tcTokens and persistent 463 send errors.

   FIX: introduced `scheduleHistoryTcTokenSync()` — a module-scoped
   single-concurrency promise chain that sequentialises all calls in order.
   The chain swallows prior errors so a single failure can't stall future
   runs. Call site stays fire-and-forget so the `messaging-history.set`
   emit is still instant (which is the whole point of PR #389).

#2 Copilot (same root as #1) — already covered by the chain.

#3+#4 Copilot — code comments referenced
   `Downloads/InfiniteAPI-Inbound-Latency-Fix-Documentation.md` which is
   intentionally out-of-tree (user keeps it locally). Replaced the broken
   refs with self-contained explanations covering the same rationale —
   future maintainers no longer need an external file to understand the
   trade-offs.

Skipped: CodeRabbit nitpick to extract a `fireAndForgetMappingStore` helper
(stylistic, current explicit form makes the lid/pn argument order obvious to
reviewers — keeping it).

Tests: 35/35 suites, 824/824 still passing. Customizations untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Renato Alcara
2026-04-26 10:25:27 -03:00
parent a631c8c3c3
commit 9e3af35b54
2 changed files with 67 additions and 15 deletions
+8 -3
View File
@@ -2345,10 +2345,15 @@ export const makeMessagesRecvSocket = (config: SocketConfig) => {
//
// HISTORICAL: this restores the intent of d73cd28d39 (2026-02-03) which was
// partially reverted by c3fc792351 the same day due to a race-condition concern
// with migrateSession (kept sync here). storeLIDPNMappings was over-protected.
// with migrateSession (kept sync here). storeLIDPNMappings was over-protected:
// it persists a mapping that downstream consumers can re-derive from key.*Alt,
// while migrateSession actually moves the Signal session record that decrypt()
// will load microseconds later — those two have very different criticality.
//
// DO NOT make migrateSession async — decrypt() depends on it.
// See Downloads/InfiniteAPI-Inbound-Latency-Fix-Documentation.md for full context.
// DO NOT make migrateSession async — decrypt() depends on the session being at
// the correct identifier (LID vs PN) when it runs. Other code paths (USync
// device lookup in messages-send.ts) create LID/PN mappings without migrating
// the session, so we cannot skip migration even when the mapping already exists.
if (!!alt) {
const altServer = jidDecode(alt)?.server
const primaryJid = msg.key.participant || msg.key.remoteJid!