Files
ukmesh/docs/node-identity.md
T

1.9 KiB
Raw Blame History

Canonical node identity

Why one physical device can appear as multiple nodes, and how the stack merges them at the data layer (not in the UI).

The problem

nodes is keyed by the exact 64-hex node_id. Two failure modes create ghost rows / "repeater not showing data":

  1. A device that re-keys (or advertises a one-nibble-corrupted key) becomes a separate row — same name, near-identical keys (GNOME-MSG-RPT ×3, 2E0MTU RPT Hilperton ×2).
  2. MQTT status rows carry the OBSERVER key while advert/position rows carry the node's own key — one device's status and position live under DIFFERENT rows.

Every flow (history, adverts, owner dashboard, links, aggregates) queries exact IDs, so data appears split/stale and the owner dashboard lists repeaters multiple times.

The fix

  • backend/src/db/nodeIdentity.ts + migrations 036/037: evidence-based canonical identity merging at the repository layer. Canonical views return one row per device with identity_source_ids (the merged member keys).
  • Perf: canonical views are wrapped in a materialized CTE (migration 037); /api/node-status/latest resolves canonical members first, then reads per member via the (node_id, time) index. A naive canonical projection over the 5.6M-row telemetry view was >8s; the fixed path is ~1.5s.
  • Owner dashboard: the owner API exposes canonicalId + members for authorized keys; the UI collapses same-name identities into one entry with the member key list.

False-merge guard (hard-won)

  • Active-to-active packet-pairing alone is NOT identity proof — colocated families named Dunston-1/2/3, NorthMesh RPT-1/2, Reach Yagi E/NWW must stay separate.
  • Differing ordinal/directional suffixes = hard ambiguity, never merge.
  • Metadata-only observer rows DO qualify for merging.

Diagnose

SELECT name, public_key, last_seen, advert_count
FROM nodes WHERE name ILIKE '<name>';