fix(meshcore): persist contact advType to meshcore_nodes (#3092)#3107
Conversation
MeshCore contacts learned via the companion's advert stream were never persisted to the `meshcore_nodes` SQL table. The only writer to that table was `setNodeTelemetryConfig`, which seeds a publicKey-only row with `advType=NULL`. As a result the remote-telemetry scheduler always read `target.advType === null`, computed `isRepeaterLike === false` (typeof null !== 'number'), and routed every target through the legacy LPP `GetTelemetryData` path — silently skipping the `SendStatusReq` + guest-login paths added in #3094. In production this meant: enabling Telemetry Retrieval on a Repeater or Room Server would only ever produce timeouts on a node whose firmware doesn't anonymously answer the LPP request. Three complementary edits make the table accurate enough for the scheduler (and any future per-source consumer) to classify targets correctly: 1. `meshcoreManager.handleBridgeEvent` — call a new `persistContact` helper from the `contact_advertised`, `contact_added`, and `contact_path_updated` handlers so every advert update mirrors to `meshcore_nodes` via `databaseService.meshcore.upsertNode`. 2. `meshcoreManager.refreshContacts` — bulk-persist all contacts when a full refresh runs. This backfills existing deployments without requiring the user to re-toggle telemetry-retrieval on each node. 3. `meshcoreRoutes` PATCH `.../telemetry-config` — look up the in-memory contact via the new `MeshCoreManager.getContact` getter and upsert advType/advName immediately, so a node that gets enabled before its first advert refresh still picks up the right classification on the very next scheduler tick. Persistence is fire-and-forget at the event level (failures logged, never thrown) so a transient DB error on a single advert can't break the contact-event pipeline. The full-refresh path awaits to keep the "Refreshed N contacts" log accurate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @Yeraze's task —— View job PR Review: fix(meshcore): persist contact advType to meshcore_nodesReview Progress:
SummaryThis PR successfully addresses issue #3092 by implementing proper persistence of MeshCore contact metadata to the Code Quality ✅Excellent implementation with several strengths: Implementation Design
Error Handling
Code Style
Test Coverage ✅Comprehensive test suite covering all critical paths: Contact Persistence Tests (
|
* chore(release): bump version to 4.6.3 Five user-visible files bumped per the CLAUDE.md version recipe: package.json, package-lock.json (regenerated), helm/meshmonitor/Chart.yaml, desktop/src-tauri/tauri.conf.json, desktop/package.json. CLAUDE.md banner line bumped to match. CHANGELOG entry covers the five PRs since 4.6.2-1: - #3105 unified tapback metadata fix - #3106 docs: drop worktree restriction - #3107 meshcore contact advType persistence - #3108 MQTT channel permissions via channel_database - #3109 hint banner Catppuccin restyle - #3110 node.channel ingest + traceroute/neighbor channel gate Companion blog post (docs/blog/2026-05-20-v4.6.3-permissions.md) walks operators through the new Virtual Channel Permissions flow, the map-visibility behavior change, and the floating-lines fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): regenerate desktop/package-lock.json for 4.6.3 The desktop sub-project carries its own lockfile and the bump to 4.6.3 left it pinned to 4.6.1. The Windows Desktop CI job runs `npm install` without `--legacy-peer-deps` and fails on the package.json / package-lock.json version mismatch. Regenerate to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Closes the user-visible part of #3092 by making sure the remote-telemetry scheduler can actually classify a target as a Repeater or Room Server.
Root cause
MeshCore contacts learned via the companion's advert stream were never persisted to the
meshcore_nodesSQL table. The only writer to that table wassetNodeTelemetryConfig, which seeds a publicKey-only row withadvType=NULL. Consequently the remote-telemetry scheduler readtarget.advType === null, computedisRepeaterLike === false(becausetypeof null !== 'number'), and routed every target through the legacy LPPGetTelemetryDatapath — silently skipping theSendStatusReq+ guest-login paths added in #3094.HougeDK's logs in #3092 confirmed this: every telemetry-request line printed
(companion: LPP)even though the targets were repeaters.Fix
Three complementary edits:
meshcoreManager.handleBridgeEvent— call a newpersistContacthelper from thecontact_advertised,contact_added, andcontact_path_updatedhandlers so every advert update mirrors tomeshcore_nodesviadatabaseService.meshcore.upsertNode.meshcoreManager.refreshContacts— bulk-persist all contacts when a full refresh runs. This backfills existing deployments without requiring users to re-toggle telemetry-retrieval on each node.meshcoreRoutesPATCH.../telemetry-config— look up the in-memory contact via the newMeshCoreManager.getContactgetter and upsertadvType/advNameimmediately, so a node that gets enabled before its first advert refresh still picks up the right classification on the very next scheduler tick.Persistence at the event level is fire-and-forget (failures logged, never thrown) so a transient DB error on a single advert can't break the contact-event pipeline. The full-refresh path awaits so the "Refreshed N contacts" log stays accurate.
Tests
meshcoreManager.contactPersistence.test.tscovers all three event types, thecontact_path_updatedpreserves-advType invariant, the DB-failure swallow path, and the newgetContactgetter (5 tests).PATCH /api/sources/test-source/meshcore/nodes/:publicKey/telemetry-configblock inmeshcoreRoutes.test.tscovers both the contact-known backfill path AND the contact-unknown skip path (2 tests).mqttBrokerManageraccept-client test fails identically on clean main).Test plan
(repeater: status + LPP)instead of(companion: LPP)for repeater targets on the next tick.SendStatusReqstatus fields (battery, uptime, queue len, RSSI, etc.) start landing asmc_status_*telemetry rows.setNodeTelemetryConfigPATCH for a freshly-seen contact backfillsadvTypein the same request (no need to wait for next refresh).advType=NULLrows gets backfilled automatically on the nextrefreshContactscycle.🤖 Generated with Claude Code