The inventory matched identity only when a row was created and never looked
again, so two rows describing one host — minted before either carried the
address that would have matched them — stayed separate forever. Documents,
racks and canvases then multiplied them.
Two repairs:
* `POST /scan/pending/merge` and a Merge action in the inventory's select
mode: the user picks the survivor. The only way to collapse rows sharing
no address at all, since a name is not an identity.
* `reconcile_duplicates`, run at the end of the Proxmox import and of a
scan: collapses what a shared MAC proves. Never a shared IP alone, and
never across two distinct IEEEs.
Merging keeps everything. The survivor holds every fact it had and fills its
gaps from the others; addresses, services, properties and sources union.
Canvas nodes, rack mounts, documents and mesh links are re-pointed before the
extras go, and each node's `display_view` gains the facts it never saw as
visible — a whitelist left alone would have rendered them hidden and made the
merge look like a no-op on the canvas.
Two bugs fell out on the way:
* `dedupe_nodes_by_device` deleted duplicate nodes while `rack_devices.node_id`
and `documents.node_id` still named them. SQLite runs with foreign keys off,
so the declared `ON DELETE SET NULL` never fired.
* The scanner's duplicate collapse deleted inventory rows outright, losing
their facts and orphaning whatever drew them. It merges now; its guard for
a second approved row is unchanged.
ha-relevant: maybe