mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
The migration-summary preflight always emitted a "resolved from service, not from device" ❌ row whenever the target was a hostname — even when SSH was available and could have answered authoritatively. Two problems compounded: the summary builder passed `nil` for the SSH client (skipping the device-side ping), and resolveIP's service-side fallback returned a bare fmt.Errorf the caller couldn't distinguish from a real failure. Changes: - ErrResolvedFromServiceOnly sentinel; service-side fallback wraps it with fmt.Errorf("%w: ...") so callers can errors.Is()-check. Apply-path callers that pass a real SSH client keep getting the same error shape they always did. - populatePlannedNetworkConfig now takes an SSHClient. GetMigrationSummary opens one when probe.SSHOK is true and passes it through, so the summary's resolve call uses the same device-side authority the apply paths use. Skipping the dial when SSH is known dead keeps a stale handshake-timeout from burning the preflight budget. - MigrationSummary gains ResolveIPSource ("device" / "service") and ResolveIPDurationMS so we can observe the SSH-ping cost in the wild. The historical comment claimed 2-5 s on firmware-27 devices — we now have data instead of a guess. - CLI renderer prints the new source + timing line, and only renders the ❌ ResolveIPError row for hard failures (both SSH ping AND service DNS failed). - Two regression tests cover the sentinel-tagging contract and the device-success-returns-nil-error path. Related to https://github.com/gesellix/Bose-SoundTouch/issues/282. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>