Closes the mDNS half of P4. `runtime::start_mdns_responder` binds
multicast via `mdns_sd::ServiceDaemon::new`, builds the
ServiceInfo from `MdnsService::to_service_info` (iter 9), and
registers — returning a typed handle that owns both daemon and
fullname.
Handle shape:
pub struct MdnsResponderHandle {
daemon: ServiceDaemon,
fullname: String,
}
impl MdnsResponderHandle {
pub fn fullname(&self) -> &str;
pub fn shutdown(self) -> Result<(), mdns_sd::Error>;
}
impl Drop for MdnsResponderHandle { /* best-effort */ }
Why explicit `shutdown` + best-effort `Drop`: a clean shutdown
sends a goodbye packet so HA's discovery integration sees the
service leave (good UX — no stale device card). `Drop` is the
fallback for panics / process termination but swallows errors
since panicking-in-Drop would mask the real failure.
1 new live-I/O test:
* mdns_responder_fullname_concatenates_instance_and_service_type
— actually binds multicast on the loopback adapter, registers,
asserts the fullname contains `_ruview-ha._tcp`, then
shutdown()s. Confirmed working on Windows; CI environments
where multicast bind is filtered will hit the gracefully-
skipping early return rather than failing the suite.
64/64 cog tests green (63 → 64).
ADR-116 P4: mDNS half ✅ (record-builder + ServiceInfo + live
responder), witness half ✅ (chain + JSONL + file + Ed25519).
Last piece is the embedded rumqttd broker so external mosquitto
becomes optional.
Co-Authored-By: claude-flow <ruv@ruv.net>