personal memory agent

feat(link): advertise Tailscale-overlay (CGNAT) interfaces as pairing candidates master

The LAN-interface watcher dropped every overlay-class interface by name before classification ever ran, so a phone and journal sharing a Tailscale tailnet but no physical LAN couldn't pair despite being directly reachable. Make interface classification a joint name+address decision: overlay-plausible names (utun*/tun*/tailscale*) carrying CGNAT (100.64.0.0/10) scope "vpn", ULA (fc00::/7) still scope "ula"; tap* becomes hard-excluded regardless of address; CGNAT on an ordinary NIC stays excluded (host ISP-CGNAT WAN, not peer-reachable). The already-declared "vpn" scope now flows through to /api/status vpn_candidates and the pair-link candidate list. Fix a candidate-ordering bug so a vpn-scoped IP is never promoted ahead of a lan/ula one: _list_pair_link_candidates() retains scope, promotes the default route only within its own scope group, and orders vpn candidates last. This unblocks pairing when the only reachable IPv4 is the Tailscale overlay address. No wire-format change, no contract change, no iOS change. Co-Authored-By: Codex <codex@openai.com>


+195 -54
5 changed files