fix(meshcore): populate discovered repeater names via ANON_REQ OWNER, no admin login (#3820)#3825
Conversation
…aters get their name (#3820) A repeater added via "Discover Repeaters" stays "Unknown" in the node list until an unrelated refresh (e.g. opening its admin panel) happens to run — even after it sends a zero-hop advert. Root cause (firmware-verified): NODE_DISCOVER_RESP carries no name, so discovery pre-creates a *nameless* device contact. The repeater's later zero-hop advert DOES carry the name and the firmware DOES store it on the device — but because the contact already exists, the firmware pushes a pubkey-only 0x80 advert (not the full 0x8A new-advert record), so MeshMonitor's contact_advertised event arrives with no adv_name. The device-record re-read (schedulePathRefresh -> get_contacts) was gated behind `if (!wasKnown)`, so it never ran for the already-known stub, leaving it "Unknown". Fix: move the missing-name/type refresh trigger out of the `!wasKnown` branch so it also fires for known-but-nameless contacts. Zero airtime — firmware drops nameless adverts, so every contact_advertised event means the device just stored a real name; the refresh is a local debounced get_contacts read and stops re-firing once the name is pulled. #3756's `||`-not-`??` guard can only protect a name already held; it cannot acquire one never pulled, so it was insufficient for the fresh-discovery case. Plan/analysis: docs/internal/dev-notes/MESHCORE_UNNAMED_NODE_3820_PLAN.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: fix(meshcore): refresh known-but-nameless contacts (#3820)Tasks:
SummaryThis is a clean, well-reasoned fix. The root cause diagnosis is thorough, the change is surgical, and the test coverage is solid. Code Quality
The old logic: if (!wasKnown) {
void this.notifyNewNodeDiscovered(updated);
}
// ... schedule refresh only for new nodes (implicitly)The new logic correctly separates the "notify of new discovery" concern from the "refresh if name/type is missing" concern: if (!wasKnown) {
void this.notifyNewNodeDiscovered(updated);
}
// refresh fires for known-but-nameless contacts too
if (!updated.advName || updated.advType === undefined) {
this.schedulePathRefresh(publicKey);
}This is the right separation. The two concerns were conflated before, and the fix correctly decouples them. The comment block at lines ~1175–1192 is excellent — it documents the firmware behavior assumptions, the Potential Issues1. The condition 2. Re-triggering on every nameless advert (acceptable trade-off) Until 3. The The PR correctly calls out this assumption in a Test CoverageThe new test case at line 139 (
This is a precise regression test. The existing three tests remain intact (new node with only pubkey, complete advert skips refresh, already-known-and-named skips refresh), ensuring no regression in those paths. One minor gap: there's no test verifying that a known-but-nameless contact receiving multiple pubkey-only adverts within the debounce window still results in exactly one VerdictApprove. The fix is correct, minimal, and well-documented. Test coverage directly addresses the regression. The |
… OWNER (#3820) "Discover Repeaters" left repeaters as "Unknown" because NODE_DISCOVER_RESP carries no name and a zero-hop repeater may not advert for a long time (observed >30 min on live hardware), so the advert-refresh path (prior commit) could wait indefinitely. Firmware (ripplebiz/MeshCore simple_repeater `handleAnonOwnerReq`) answers an UNAUTHENTICATED ANON_REQ OWNER (CMD_SEND_ANON_REQ sub-type 0x02) with "node_name\nowner_info" — the same anon-request transport, and the same isRouteDirect() gating, as the region discovery shipped in #3743/#3765. So we can pull a discovered repeater's real name without admin login. - meshcoreNativeBackend: new `request_owner` bridge command — builds the [57][pubkey][0x02][0x00] frame, awaits the 0x8C BinaryResponse (serialized via runExclusiveRadioOp, mirroring request_regions), parses clock + first line of "node_name\nowner_info". - meshcoreManager: new `fetchOwnerName()` — installs a zero-hop direct out_path (firmware drops flooded OWNER reqs), issues request_owner, writes the name onto the contact and broadcasts it. discoverNodes() gains an opt-in `fetchNames` flag that fetches names for nameless repeaters/room-servers after the sweep. - meshcoreRoutes: the user-facing POST /discover opts in (fetchNames=true); the internal region-discovery sweep does not (it has its own per-repeater pass). Verified live: delete "Yeraze Repeater" -> Discover Repeaters -> name populates as "Yeraze Repeater" within the discovery window (~12 s), no admin login, no advert required. The advert-refresh (Option A) remains as a zero-airtime complement for repeaters that do advert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011JEaCGwY9Wz8jeV4e22GW4
|
Claude finished @Yeraze's task —— View job PR Review: fix(meshcore): refresh known-but-nameless contacts (#3820)Tasks:
SummaryA clean, well-scoped fix with two complementary strategies: a passive path (refresh on advert for known-but-nameless contacts) and an active path (ANON_REQ OWNER during discovery). The root cause analysis is thorough and accurate. The code quality is high overall. Core Fix (
|
Problem
A MeshCore repeater added via Discover Repeaters showed as "Unknown" in the node list and stayed that way — even after a zero-hop advert — until an admin login. Reopen of #3756. The reporter confirmed the official iOS/Windows app shows names from both Discover and adverts, so this is a MeshMonitor gap, not a protocol limitation.
Root cause (firmware-verified)
NODE_DISCOVER_RESPcarries no name (fixed buffer:type+snr+tag+pubkey), so discovery pre-creates a nameless device contact. The repeater's later zero-hop advert does carry the name and the firmware does store it — but because the contact already exists, the firmware emits a pubkey-only0x80advert (not the named0x8Anew-advert), so MeshMonitor'scontact_advertisedevent has no name. And a zero-hop repeater may not advert for a long time (observed >30 min on live hardware), so the name can be absent indefinitely. Admin login only "worked" because opening the panel triggers acontacts/refresh.The decisive find: a repeater answers an unauthenticated
ANON_REQ OWNER(CMD_SEND_ANON_REQsub-type0x02) withnode_name\nowner_info(firmwaresimple_repeater/MyMesh.cpp:handleAnonOwnerReq) — the same anon-request transport andisRouteDirect()gating as the region discovery shipped in #3743/#3765. So we can pull a discovered repeater's real name without admin credentials. Full analysis:docs/internal/dev-notes/MESHCORE_UNNAMED_NODE_3820_PLAN.md.Fix (two complementary layers)
request_ownerbridge command (mirrorsrequest_regions) +fetchOwnerName()in the manager: install a zero-hop direct out_path (firmware drops flooded OWNER reqs), issueANON_REQ OWNER, write the returned name onto the contact. The user-facingPOST /discoveropts in (fetchNames=true); the internal region-discovery sweep does not. So Discover Repeaters now returns names in seconds, no admin login, no advert required — matching the app.schedulePathRefresh(get_contactsre-read) out of the!wasKnowngate so a known-but-nameless contact also re-reads on the nameless0x80advert. Catches names from repeaters that do advert, with no round-trip. (Carries a comment: relies on discovery storinglastAdvert≈0so the advert isn't replay-dropped firmware-side.)Testing
meshcoreManager.ownerName.test.ts(out_path-before-request ordering, failure handling, non-companion guard) and the advert-refresh regression inmeshcoreManager.newNodeRefresh.test.ts; updatedmeshcoreRoutes.test.tsfor thefetchNamesopt-in. Full Vitest suite green (7698 passed, 0 failed).Owner-name fetched for 661b0674…: "Yeraze Repeater" (#3820).Fixes #3820
🤖 Generated with Claude Code