cog-ha-matter (ADR-116 P4): MdnsService -> mdns-sd ServiceInfo bridge
Pure conversion from our wire-format `MdnsService` to the
`mdns_sd::ServiceInfo` shape the responder daemon consumes. No
socket binding, no daemon registration yet — that lands next iter
as a `runtime::spawn_mdns_responder(info)` JoinHandle returning
helper, same shape as `runtime::spawn_publisher`.
* `MdnsService::to_service_info(hostname, ipv4) ->
Result<ServiceInfo, mdns_sd::Error>`
* `mdns-sd = "0.11"` added — aligned with the workspace pin from
wifi-densepose-desktop so the lockfile doesn't fork dalek-like
surfaces.
3 new tests:
* to_service_info_carries_service_type_and_port — locks that
`_ruview-ha._tcp` (with or without mdns-sd's trailing-dot
normalisation) and the control port round-trip through the
conversion
* to_service_info_propagates_txt_records — every locked TXT
key from iter 4 (cog_id, mqtt_port, privacy, proto, node_id,
cog_version) reachable via `get_property_val_str` on the
converted ServiceInfo
* to_service_info_does_not_silently_drop_caller_hostname —
locks the caller-side responsibility for the .local. suffix.
mdns-sd 0.11 accepts bare hostnames (verified empirically by
initial test expecting it to reject — it didn't), so the
wrapper layer must do the trailing-dot dance. Documenting
that via a named test catches future bumps where the lib
starts mutating the value.
63/63 cog tests green (60 → 63).
ADR-116 P4 now ⁶⁄₇: ✅ mDNS record-builder, ✅ chain, ✅ JSONL, ✅
file persistence, ✅ Ed25519 signing, ✅ ServiceInfo conversion;
⏳ daemon register + embedded broker.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
bb154d4e78
commit
34eced880f
|
|
@ -95,7 +95,7 @@ Ranked by build cost × user impact:
|
|||
| **P1** | Research dossier ([`docs/research/ADR-116-ha-matter-cog-research.md`](../research/ADR-116-ha-matter-cog-research.md)) | ✅ **done** — 8 sections, 30+ citations, v1 scope ranked |
|
||||
| **P2** | Cog crate scaffold (`v2/crates/cog-ha-matter/`) — Cargo.toml + `src/{lib,main,manifest}.rs`, workspace member, CLI args, `--print-manifest` flag, 2 manifest unit tests | ✅ **done** — `cargo check` + `cargo test` green |
|
||||
| **P3** | Wrap existing ADR-115 MQTT publisher as cog entry point | ✅ **wiring done** — `main.rs` boots ADR-115's `publisher::spawn` via `runtime::spawn_publisher` thin wrapper, holds a long-lived `broadcast::Sender<VitalsSnapshot>`, awaits Ctrl-C. Live-handle test green without a broker. Next (P3.5): subscribe to sensing-server `/v1/snapshot` WS and republish into the channel. |
|
||||
| **P4** | Seed-native enhancements (embedded broker, mDNS, witness) | in progress — (a) mDNS record-builder ✅. (b) Witness hash-chain ✅. (c) JSONL line serializer ✅. (d) File persistence + chain-level verify ✅. **(e) Ed25519 signing layer ✅** — `witness_signing::{sign_event, verify_signature, signature_to_hex, signature_from_hex}` signs the same canonical bytes the hash chain commits to, so a single attestation covers `kind + payload + ts + seq + prev_hash`. Tests cover wrong-key, tampered-event, wrong-prev_hash, hex round-trip, determinism. (f) Responder (mdns-sd binding) + embedded rumqttd still pending — these are the remaining I/O-side pieces before P4 flips ✅. |
|
||||
| **P4** | Seed-native enhancements (embedded broker, mDNS, witness) | in progress — (a) mDNS record-builder ✅. (b) Witness hash-chain ✅. (c) JSONL line serializer ✅. (d) File persistence + chain-level verify ✅. (e) Ed25519 signing layer ✅. **(f) mDNS ServiceInfo conversion ✅** — `MdnsService::to_service_info(hostname, ipv4)` produces the `mdns_sd::ServiceInfo` the responder daemon consumes; 3 tests verify service-type, port, TXT propagation. `mdns-sd = 0.11` aligned with the workspace's existing pin from `wifi-densepose-desktop`. (g) `ServiceDaemon::register` spawn + embedded rumqttd still pending — the remaining live-I/O pieces before P4 flips ✅. |
|
||||
| **P5** | RuVector-backed threshold learning (SONA adaptation) | pending |
|
||||
| **P6** | Multi-Seed federation (cross-Seed dedup + witness) | pending |
|
||||
| **P7** | Matter Bridge mode (depends on matter-rs / esp-matter readiness) | pending |
|
||||
|
|
|
|||
|
|
@ -935,6 +935,7 @@ version = "0.3.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"ed25519-dalek",
|
||||
"mdns-sd",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
|
|||
|
|
@ -41,5 +41,10 @@ wifi-densepose-hardware = { version = "0.3.0", path = "../wifi-densepose-hardwar
|
|||
sha2 = { workspace = true }
|
||||
ed25519-dalek = "2.1"
|
||||
|
||||
# mDNS responder (ADR-116 P4 §2.2): pure-Rust zero-conf daemon.
|
||||
# Same version pinned in wifi-densepose-desktop to keep the
|
||||
# workspace lockfile narrow.
|
||||
mdns-sd = "0.11"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@
|
|||
//! are broadcast in cleartext and harvested by passive scanners, so
|
||||
//! treating them as PII-clean is part of the privacy posture.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mdns_sd::ServiceInfo;
|
||||
|
||||
use crate::COG_ID;
|
||||
|
||||
/// Default mDNS instance name template. `{node_id}` is substituted
|
||||
|
|
@ -74,6 +78,33 @@ impl MdnsService {
|
|||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v.as_str())
|
||||
}
|
||||
|
||||
/// Convert into the `mdns_sd::ServiceInfo` the responder daemon
|
||||
/// consumes. Pure transform — no socket binding, no daemon
|
||||
/// registration. The caller wires the resulting `ServiceInfo`
|
||||
/// into `ServiceDaemon::register` (next iter).
|
||||
///
|
||||
/// `hostname` should end in `.local.` per RFC 6762 — e.g.
|
||||
/// `"cognitum-seed-1.local."`. `ipv4` is the LAN-routable
|
||||
/// address HA's discovery will reach back on.
|
||||
pub fn to_service_info(
|
||||
&self,
|
||||
hostname: &str,
|
||||
ipv4: &str,
|
||||
) -> Result<ServiceInfo, mdns_sd::Error> {
|
||||
let mut props: HashMap<String, String> = HashMap::with_capacity(self.txt_records.len());
|
||||
for (k, v) in &self.txt_records {
|
||||
props.insert(k.clone(), v.clone());
|
||||
}
|
||||
ServiceInfo::new(
|
||||
&self.service_type,
|
||||
&self.instance_name,
|
||||
hostname,
|
||||
ipv4,
|
||||
self.control_port,
|
||||
Some(props),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the cog's mDNS advertisement record from the cog's typed
|
||||
|
|
@ -203,6 +234,51 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_service_info_carries_service_type_and_port() {
|
||||
let svc = build_mdns_service(&id(), 9180, 1883, false);
|
||||
let info = svc
|
||||
.to_service_info("cognitum-seed-1.local.", "192.168.1.50")
|
||||
.expect("valid service info");
|
||||
// mdns-sd may rewrite the type with a trailing dot; allow
|
||||
// both forms.
|
||||
let ty = info.get_type();
|
||||
assert!(
|
||||
ty == "_ruview-ha._tcp" || ty == "_ruview-ha._tcp.",
|
||||
"unexpected service type: {ty}"
|
||||
);
|
||||
assert_eq!(info.get_port(), 9180);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_service_info_propagates_txt_records() {
|
||||
let svc = build_mdns_service(&id(), 9180, 1883, true);
|
||||
let info = svc
|
||||
.to_service_info("cognitum-seed-1.local.", "192.168.1.50")
|
||||
.expect("valid service info");
|
||||
// Every locked TXT key must reach the wire-format payload.
|
||||
assert_eq!(info.get_property_val_str("cog_id"), Some(crate::COG_ID));
|
||||
assert_eq!(info.get_property_val_str("mqtt_port"), Some("1883"));
|
||||
assert_eq!(info.get_property_val_str("privacy"), Some("1"));
|
||||
assert_eq!(info.get_property_val_str("proto"), Some("ruview-ha/1"));
|
||||
assert!(info.get_property_val_str("node_id").is_some());
|
||||
assert!(info.get_property_val_str("cog_version").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_service_info_does_not_silently_drop_caller_hostname() {
|
||||
// mdns-sd 0.11 accepts bare hostnames (no `.local.`); the
|
||||
// responsibility for the trailing dot lives in our wrapper.
|
||||
// Lock that the caller's hostname survives the conversion
|
||||
// verbatim — a future bump that starts mutating the value
|
||||
// surfaces a named test instead of a silent change.
|
||||
let svc = build_mdns_service(&id(), 9180, 1883, false);
|
||||
let info = svc
|
||||
.to_service_info("cognitum-seed-1.local.", "192.168.1.50")
|
||||
.unwrap();
|
||||
assert!(info.get_hostname().contains("cognitum-seed-1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn txt_keys_match_locked_surface() {
|
||||
// The HA-side YAML auto-discovery binds on these exact keys.
|
||||
|
|
|
|||
Loading…
Reference in New Issue