fix(stability): per-source Passive Mode + createdAt chat ordering (#3122)#3125
Conversation
) Two related fixes for large/fragile Meshtastic TCP nodes reported in #3122. Track A — channel chat orders by server arrival time (global): * Replace COALESCE(rxTime, timestamp) with createdAt in all message repository ORDER BY sites and the unified channel cursor, so a future-skewed device clock can no longer pin an old message at the visible "newest" slot and hide subsequent traffic. Updates the unified API + client to carry createdAt and use it for sort + pagination. Track B — opt-in per-source Passive Mode: * New passiveMode flag on Meshtastic TCP sources (UI toggle in the source edit dialog) for router-class nodes with large NodeDBs. * When ON, handleDisconnected preserves localNodeInfo / actualDeviceConfig / actualModuleConfig / initConfigCache across socket bounces. * On reconnect with a fresh (<4h) cached snapshot, skip the want_config_id handshake — the source of repeated NodeDB resync loops that the reporter observed on a ~1183-node router. * Skip the post-config outbound burst (LoRa config request, all-module-configs request, remote admin scanner, time sync scheduler) when passiveMode is on. Receive-side processing is untouched. * Default remains the existing behavior; large nodes opt in. Tests: createdAt ordering tests across all repo entry points (including the sync SQLite variants); passive-mode regression tests for config wiring + state retention + the 4h staleness window default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review: Passive Mode & Message OrderingComprehensive review of this PR covering code quality, potential issues, performance, security, and test coverage. Review Tasks
Track A Review: Message Ordering (createdAt vs rxTime)✅ Architecture Alignment: The change from ✅ Code Quality: The implementation is clean and well-documented. Comments clearly explain the rationale for the change ( ✅ Performance: Using
Track B Review: Passive Mode Implementation✅ Configuration Handling: The passive mode flag is properly threaded through:
✅ State Preservation Logic: The
✅ Staleness Window: 4-hour threshold is reasonable and matches issue discussion. Logic correctly skips ✅ Selective Outbound Suppression: Lines 1275-1279 correctly skip the outbound burst while preserving receive-only schedulers. ✅ Manager Restart Logic: Source route properly restarts managers when UI Implementation✅ Clear User Interface: The passive mode toggle in
Test Coverage Analysis✅ Comprehensive New Tests:
✅ Existing Test Compatibility: The Security & Performance Considerations✅ No Security Issues: The changes don't expose new attack vectors or sensitive data. ✅ Performance Improvements:
Minor Issues
Final Recommendations✅ Approve with Minor Suggestions:
Overall Assessment: This is a well-implemented, thoroughly tested fix that addresses a real operational issue with large Meshtastic nodes. The changes are architecturally sound, follow project conventions, and include comprehensive test coverage. The trade-offs are clearly documented and acceptable for the target use case. |
#3128) Follow-up to #3125 / #3126. Closes the third item from the reporter's feedback on #3122. On large/fragile TCP nodes the *first* config-sync session frequently closes mid-stream but the second attempt works cleanly. Under normal exponential backoff that recovery would wait 8–16s before retrying; this short startup-grace window cuts that gap. * TcpTransport.setStartupGraceReconnect(graceMs, fastDelayMs): for the next graceMs after the call, scheduleReconnect uses fastDelayMs instead of the exponential backoff. After the window expires, normal backoff resumes automatically. Disabled by default (graceMs=0). * MeshtasticManager opt-in: passive-mode sources get a 2-minute grace window with a 3-second reconnect delay during initial startup. Other sources keep the legacy backoff. Tests: 6 new tcpTransport tests cover default-disabled, in-window fast delay regardless of attempt count, post-window exponential fallback, explicit disable, sanity, and that the reconnect fires after the configured delay. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#3122) (#3129) Follow-up to #3125 / #3126 / #3128. Closes item 2 of the reporter's feedback on #3122 (advanced per-source setting for the staleness window). * SourceConfig gains optional passiveResyncStaleMs (ms). Falls back to the 4h class default when absent. * MeshtasticManager.effectivePassiveResyncStaleMs() resolves the active threshold, clamping out-of-range overrides ([1 min, 7 days]) so a 0 or astronomical value can't disable the safeguard. * Plumbed through all 5 MeshtasticManager construction sites in sourceRoutes; a change in the value triggers a transport restart alongside the existing passiveMode toggle. * UI: numeric "Resync staleness window (hours)" input appears under the Passive Mode toggle when enabled. Blank = use 4h default. Tests: 9 new tests cover default, constructor override, configureSource override + clear, below-floor / above-ceiling rejection, exact boundary acceptance, and NaN rejection. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes #3122.
Two related fixes for large/fragile Meshtastic TCP nodes, in line with the plan agreed in the issue discussion and refined by the reporter (TheWISPRer).
Track A — channel chat orders by
createdAt(global, no flag)Channel chat sort + cursor now use server DB arrival time (
createdAt) instead of device-reportedrxTime/timestamp. A node with a future-skewed clock can no longer pin an old message at the visible "newest" slot and hide subsequent traffic.Touched:
MessagesRepository.getMessages,getMessagesByChannel,getMessagesBeforeInChannel(cursor + ORDER BY),getDirectMessages,getMessagesSqlite,getMessagesByChannelSqlite— six sites./api/unified/messages: response now includescreatedAt, sort +beforecursor use it. Doc-comment updated.UnifiedMessagesPage: client sort + infinite-query cursor switched tocreatedAt.searchMessagesdate-range filtering intentionally left onrxTime— searching by date implies the user means sent time.Trade-off (noted by the reporter and acceptable for a live monitoring UI): store-and-forward messages that arrive long after they were sent display at receive time, not original send time.
Track B — opt-in per-source Passive Mode
A new
passiveModeflag on Meshtastic TCP sources (UI toggle on the source edit dialog). Default off — small nodes keep the existing handshake. When on:handleDisconnectedpreserveslocalNodeInfo/actualDeviceConfig/actualModuleConfig/initConfigCacheacross socket bounces. With a Virtual Node attached, the init capture buffer is still cleared so VN replay stays fresh.sendWantConfigId()— the source of the repeated NodeDB resync loops the reporter observed on a ~1183-node router. Mesh traffic flows without it.requestConfig(LoRa),requestAllModuleConfigs,startRemoteAdminScanner,startTimeSyncScheduler. Receive-only schedulers (geofence, local stats, auto-favorite sweep, etc.) keep running.The 4-hour staleness window matches the reporter's recommendation.
Out of scope (follow-ups suggested by the reporter)
want_config_ideven when the cache is fresh; recovery after the forced sync should not auto-repeat. Worth a separate PR.Test plan
npx tsc --noEmit— cleansrc/db/repositories/messages.createdAt-ordering.test.ts(5 tests covering both async + sync entry points + the cursor filter),src/server/meshtasticManager.passiveMode.test.ts(8 tests covering config wiring + state retention + the staleness-window constant + the lastDisconnectAt watermark)mkMsgtest helper inunifiedRoutes.test.tsto defaultcreatedAtfromrxTime/timestampso the ~30 existing fixture-based ordering tests keep their intent under the new sort key.messages.exclude-portnums.test.tsinsert helper to userxTimeascreatedAtfor deterministic ordering.npx vitest run— 5264 pass / 3 pre-existing fails (mqttBrokerManager.test.tszero-hop injection —encode failed, reproduces on cleanmainwith no diff applied; unrelated to this change).🤖 Generated with Claude Code