feat(adr-115): P2 — HA discovery emitter + privacy filter + config (27 tests)
Implements ADR-115 §3.1–§3.4 (entity mapping + topic structure + discovery
payloads + device grouping) and §3.10 (privacy-mode contract) as the
`mqtt` submodule of `wifi-densepose-sensing-server`.
Modules:
- `mqtt::mod` — module roots, stable origin/manufacturer/url constants
- `mqtt::config` — `MqttConfig` built from `cli::Args`, TLS resolution
(off/system-trust/pinned-CA/mTLS), `--mqtt-password-env`
resolution, pre-flight `validate()` with fatal/advisory
distinction (PlaintextOnPublicHost is non-fatal in
v0.7.0, hard-fail in v0.8.0 per §3.9 / §9.5).
- `mqtt::discovery` — `DiscoveryBuilder`, `EntityKind` (all 11 raw +
10 semantic entities), serialisable `DiscoveryConfig`
with `skip_serializing_if = "Option::is_none"` so
retained payloads stay compact. Topic structure
matches HA's `<prefix>/<component>/<object>/<entity>/
{config,state,availability}` convention. `enabled_
entities(privacy, publish_pose, no_semantic)` is the
single source of truth for which entities the
publisher will emit.
- `mqtt::privacy` — `decide(entity, privacy_mode)` returns
`Suppress` for biometrics (HR/BR/pose) and
`Publish` for everything else, including all
semantic primitives (per §3.12.3 — semantic
primitives are inferred states, not biometric
values, and remain safe to publish in privacy mode).
Tests (27 total, all passing under `--no-default-features`):
- 11 config tests: defaults, TLS port bump, explicit port override, mTLS
triplet detection, validate rejects empty host / zero port / NaN /
negative rate, plaintext-public advisory, password env resolution.
- 9 discovery tests: payload shape (presence, heart rate, fall event,
distress problem-class), default vs privacy-mode entity sets,
--no-semantic filtering, component routing, null-field omission,
availability/state topic pairing, namespaced unique_id.
- 4 privacy tests: privacy-off publishes all, privacy-on suppresses
exactly the biometric set, keeps non-biometric signals, keeps every
semantic primitive.
Connect/publish lifecycle (uses `rumqttc`) gated behind `--features mqtt`;
the `publisher` and `state` submodules land in P3 next iteration.
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>