From 56265023dc73c5403fe9edb33851b22bb6499bbd Mon Sep 17 00:00:00 2001 From: ruv Date: Sat, 23 May 2026 17:48:08 -0400 Subject: [PATCH] feat(cog-ha-matter): P2 scaffold + ADR-116 P1 research-dossier fold-in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cron iter 1. Three things landed atomically because they cross-cite: P1 — research dossier complete Deep-researcher agent (a4dd35950ffd) shipped docs/research/ADR-116-ha-matter-cog-research.md: 8 sections, 30+ citations across Matter / HACS / cog arch / local-AI / federation / competitors / regulatory / v1 scope. Key findings folded into ADR-116 §3 and §4: - Matter device class: OccupancySensor (0x0107) + RFSensing feature on cluster 0x0406 (1.4 rev 5) - ESP32-C6 Thread Border Router: one Kconfig flag away (CONFIG_OPENTHREAD_BORDER_ROUTER=y) - HACS quality tier: target Gold (repairs + diagnostics + reconfiguration), start from hacs.integration_blueprint - CSA cert: ~$30-42k/yr — skip for v1, "Works with HA" positioning instead - Cog RAM/CPU: 128 MB / 15% on the Seed; 10 KB INT8 semantic-primitive classifier fits without PSRAM - SONA: <100 µs/query confirmed by ruvllm-esp32 v0.3.3 - FDA Jan 2026 wellness guidance covers HR / sleep / activity anomaly when marketed as "anomaly notification" not "diagnosis" - Competitor moat: Aqara FP300 / TOMMY / ESPectre all lack HR + BR + pose + semantic + witness simultaneously P2 — cog crate scaffold compiles v2/crates/cog-ha-matter/ created with cog-pose-estimation as precedent shape (ADR-101). Files: - Cargo.toml: depends on wifi-densepose-sensing-server with --features mqtt + wifi-densepose-hardware for the ADR-110 SyncPacket bridge. - src/lib.rs: COG_ID = "ha-matter", MDNS_SERVICE_TYPE "_ruview-ha._tcp", DEFAULT_CONTROL_PORT 9180. - src/manifest.rs: typed CogManifest (8 fields) mirroring cog-pose-estimation's manifest.template.json. Round-trip test locks the JSON wire shape; id-constant test guards against rename drift. - src/main.rs: clap CLI with --sensing-url / --mqtt-host / --mqtt-port / --privacy-mode / --print-manifest. The --print-manifest flag emits the build-time template with {{VERSION}} / {{ARCH}} placeholders for the signer. - v2/Cargo.toml: cog-ha-matter added as workspace member. Verification: cargo check -p cog-ha-matter --no-default-features → green cargo test -p cog-ha-matter --no-default-features --lib → 2/2 manifest tests pass ADR-116 §3 + §4 + §5 (phases) updated to mark P1+P2 ✅ done and seat the recommended v1 scope (privacy-mode audit-only → cog signing → SONA loop → HACS gold → Matter Bridge as v0.8) ranked by build cost × user impact per the dossier. P3 (next iter): wrap the existing ADR-115 MQTT publisher as the cog's main loop. The scaffold returns SUCCESS immediately today. Co-Authored-By: claude-flow --- docs/adr/ADR-116-cog-ha-matter-seed.md | 37 ++++++--- v2/Cargo.toml | 4 + v2/crates/cog-ha-matter/Cargo.toml | 39 ++++++++++ v2/crates/cog-ha-matter/src/lib.rs | 42 +++++++++++ v2/crates/cog-ha-matter/src/main.rs | 94 +++++++++++++++++++++++ v2/crates/cog-ha-matter/src/manifest.rs | 99 +++++++++++++++++++++++++ 6 files changed, 304 insertions(+), 11 deletions(-) create mode 100644 v2/crates/cog-ha-matter/Cargo.toml create mode 100644 v2/crates/cog-ha-matter/src/lib.rs create mode 100644 v2/crates/cog-ha-matter/src/main.rs create mode 100644 v2/crates/cog-ha-matter/src/manifest.rs diff --git a/docs/adr/ADR-116-cog-ha-matter-seed.md b/docs/adr/ADR-116-cog-ha-matter-seed.md index b19033f9..c8c02646 100644 --- a/docs/adr/ADR-116-cog-ha-matter-seed.md +++ b/docs/adr/ADR-116-cog-ha-matter-seed.md @@ -2,7 +2,7 @@ | Field | Value | |-------|-------| -| **Status** | Proposed (research in progress — `docs/research/ADR-116-ha-matter-cog-research.md`) | +| **Status** | Proposed — P1 research complete ([`docs/research/ADR-116-ha-matter-cog-research.md`](../research/ADR-116-ha-matter-cog-research.md)). P2 cog scaffold compiles (`v2/crates/cog-ha-matter`, 2/2 unit tests green). | | **Date** | 2026-05-23 | | **Deciders** | ruv | | **Codename** | **HA-COG** — HA + Matter, packaged for the Seed | @@ -63,22 +63,37 @@ Multiple Seeds in adjacent rooms coordinate via: The federation model is the natural extension of ADR-110's mesh substrate into the application layer. Specifically: ADR-110 gives us ≤100 µs cross-board sync; this ADR uses that to deduplicate cross-Seed events (one fall, one alert) and reconstruct multi-room transitions (one occupant, room A → hallway → room B). -## 3. Open questions (research dossier will answer) +## 3. Research dossier findings (P1 complete) -1. **Matter Bridge vs Matter Root** — can the Seed act as a commissioner, or is Bridge mode the only realistic 2026 option? -2. **Thread Border Router** — does the ESP32-S3 in the Seed (or a paired ESP32-C6 node) host a Thread Border Router cleanly, and does that buy us anything HA users care about? -3. **HACS integration value-add** — beyond MQTT auto-discovery, what does a custom HA integration unlock? (config flow / repairs / device-class custom entities / service catalog). -4. **CSA certification cost / timeline** — what's the minimum CSA-compliant subset and what does it cost to ship a CSA-certified Matter device today? -5. **Cog binary size + Seed RAM budget** — the Seed has 8 MB PSRAM + 320 KB SRAM. The cog must fit. -6. **ruvllm + RuVector latency for semantic primitives** — can the 10 inferred states run at 5 Hz on the Seed without external compute? -7. **HIPAA / FDA classification** — when does fall-detection cross into regulated medical-device territory, and does the `--privacy-mode` strip + Matter Health device class give us a defensible "wellness device" position? +Full dossier: [`docs/research/ADR-116-ha-matter-cog-research.md`](../research/ADR-116-ha-matter-cog-research.md). The eight research questions are now answered: + +1. **Matter Bridge vs Matter Root** — Matter 1.4 introduced `OccupancySensor (0x0107)` with `RFSensing` feature flag on cluster `0x0406` (revision 5 in Matter 1.4). That's the correct device class for WiFi-CSI sensing — no health/vitals cluster exists in Matter 1.4.2 and won't soon. **Seed acts as Bridge** with N dynamic OccupancySensor endpoints, **not Commissioner** (the C6 sensing nodes stay Accessories only — 320 KB SRAM no PSRAM rules out commissioning). +2. **Thread Border Router** — ESP32-C6 single-chip TBR confirmed working; `CONFIG_OPENTHREAD_BORDER_ROUTER=y` is the only config step. ADR-110's `c6_timesync.c` already initialises 802.15.4 — TBR is a Kconfig flag away. Real value: HA's Improv-style commissioning works without a separate Thread border router box. +3. **HACS value-add** — config flow (UI setup wizard), Repairs API (structured error cards), re-authentication, diagnostics download, typed service actions (`set_privacy_mode`, `calibrate_zone`), i18n translations. **Bronze is the minimum bar; Gold (repairs + diagnostics + reconfiguration) is the target.** Start from `hacs.integration_blueprint` template. +4. **CSA certification** — ~$30-42k first year ($22.5k membership + $10-19k ATL lab fees). **Skippable for v1** by publishing as "Works with HA" instead. CSA re-evaluate at v0.9+ after HACS adoption data lands. +5. **Cog RAM budget** — 128 MB RAM / 15 % CPU on the Seed appliance (Pi 5 + Hailo-10 variant has more headroom). 10 KB INT8 semantic-primitive classifier fits without PSRAM. Long-lived supervised process with capability scopes `network.mqtt + network.matter + api.ruview_vitals`. +6. **ruvllm + RuVector latency** — `ruvllm-esp32` v0.3.3 confirms SONA self-optimising adaptation under 100 µs per query. 8→10 INT8 classifier ~10 KB quantised. Per-home threshold tuning via HA thumbs-up/thumbs-down feedback as LoRA-style gradient steps — closes the top user complaint (false positives) without cloud round-trips. +7. **HIPAA / FDA** — FDA January 2026 General Wellness guidance explicitly classifies HR / sleep / activity-anomaly alerts as **wellness devices** (outside FDA jurisdiction) when marketed without diagnostic claims. Frame fall detection as **"activity anomaly notification"** not "fall diagnosis". `--privacy-mode` audit-only tier (no MQTT state messages, only SHA-256 digests on-Seed) creates a technical PHI barrier. `OccupancySensor (0x0107)` device class keeps the product in the same regulatory category as a smart motion sensor. +8. **Competitor moat** — Aqara FP300 (Nov 2025): 5 entities, no person count, no vitals, no fall detection. TOMMY: zones only, no vitals, closed-source, paywalled. ESPectre: motion only. **RuView's differentiation** — HR/BR + 17-keypoint pose + 10 semantic primitives + witness chain + SONA adaptation — has no competitor equivalent. + +## 4. Recommended v1 scope (from dossier §8) + +Ranked by build cost × user impact: + +| # | Feature | Cost | Impact | Phase | +|---|---|---|---|---| +| 1 | **`--privacy-mode` audit-only tier** (no MQTT state, SHA-256 digests on-Seed) | ~1 week | Closes care / GDPR deployments | P3 (this cog) | +| 2 | **Seed cog manifest + Ed25519 signing + store listing** | ~1-2 weeks | Enables one-click distribution | P2 + P8 (this cog) | +| 3 | **Local SONA fine-tuning loop** (HA feedback → LoRA gradient steps) | ~2-3 weeks | Reduces false positives, closes #1 user complaint | P5 (this cog) | +| 4 | **HACS gold-tier integration** (config flow + repairs + diagnostics) | ~4-6 weeks | Removes MQTT prerequisite for mainstream users | P9 (separate repo `hass-wifi-densepose`) | +| 5 | **Matter Bridge with OccupancySensor + dynamic endpoints** | ~6-8 weeks | Apple Home / Google Home / Alexa native | **v0.8** dedicated sprint (after HACS adoption data) | ## 4. Implementation phases | Phase | Scope | Status | |---|---|---| -| **P1** | Research dossier (`docs/research/ADR-116-ha-matter-cog-research.md`) | _in progress_ (deep-researcher agent) | -| **P2** | Cog manifest + binary scaffold (`cogs/ha-matter/`) | pending | +| **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 | pending | | **P4** | Seed-native enhancements (embedded broker, mDNS, witness) | pending | | **P5** | RuVector-backed threshold learning (SONA adaptation) | pending | diff --git a/v2/Cargo.toml b/v2/Cargo.toml index 9e0e908c..36c76c31 100644 --- a/v2/Cargo.toml +++ b/v2/Cargo.toml @@ -38,6 +38,10 @@ members = [ # PR #491 slot heuristic with a Candle network + Stoer-Wagner fusion. # Motivated by #499 ghost-skeleton reports. "crates/cog-person-count", + # ADR-116: Home Assistant + Matter Cognitum Seed cog. Wraps the + # ADR-115 MQTT publisher as a Seed-installable artifact with + # mDNS, embedded broker, RuVector thresholds, Ed25519 witness. + "crates/cog-ha-matter", # rvCSI — edge RF sensing runtime (ADR-095 platform, ADR-096 FFI/crate layout): # lives in its own repo (https://github.com/ruvnet/rvcsi), vendored here as # `vendor/rvcsi` and published to crates.io as `rvcsi-*` 0.3.x. Depend on the diff --git a/v2/crates/cog-ha-matter/Cargo.toml b/v2/crates/cog-ha-matter/Cargo.toml new file mode 100644 index 00000000..28c8eac0 --- /dev/null +++ b/v2/crates/cog-ha-matter/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "cog-ha-matter" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +description = "Cognitum Cog: Home Assistant + Matter integration for the Seed (ADR-116). Wraps ADR-115's HA-DISCO + HA-MIND publisher as a Seed-installable artifact with mDNS, embedded broker, RuVector-backed thresholds, and Ed25519 witness." +publish = false + +[[bin]] +name = "cog-ha-matter" +path = "src/main.rs" + +[lib] +name = "cog_ha_matter" +path = "src/lib.rs" + +[dependencies] +# CLI + logging — same shape as cog-pose-estimation (ADR-101). +clap = { version = "4", features = ["derive"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } + +# Async runtime for the publisher + mDNS responder + WebSocket pump. +tokio = { workspace = true, features = ["full"] } + +# ADR-115 publisher is the heart of this cog — we wrap it. +# default-features = false matches the sensing-server's pattern. +wifi-densepose-sensing-server = { version = "0.3.0", path = "../wifi-densepose-sensing-server", default-features = false, features = ["mqtt"] } + +# Hardware crate for SyncPacket + NodeState bridging (ADR-110 substrate). +wifi-densepose-hardware = { version = "0.3.0", path = "../wifi-densepose-hardware" } + +[dev-dependencies] +tempfile = "3.10" diff --git a/v2/crates/cog-ha-matter/src/lib.rs b/v2/crates/cog-ha-matter/src/lib.rs new file mode 100644 index 00000000..cfef9113 --- /dev/null +++ b/v2/crates/cog-ha-matter/src/lib.rs @@ -0,0 +1,42 @@ +//! ADR-116 — Home Assistant + Matter Cognitum Seed cog. +//! +//! This crate is the Seed-installable wrapper around ADR-115's +//! `wifi-densepose-sensing-server::mqtt` publisher. It adds the +//! Seed-native surfaces ADR-115's `--mqtt` flag can't easily reach: +//! +//! 1. **mDNS service advertisement** — `_ruview-ha._tcp` so HA discovers +//! the cog automatically (no manual broker host/port config). +//! 2. **Optional embedded MQTT broker** — for Seeds running without an +//! external mosquitto. Defaults to off; the cog can either embed +//! rumqttd or connect to a user-provided broker. +//! 3. **RuVector-backed semantic-primitive thresholds** — replaces +//! static `semantic-thresholds.yaml` with a SONA-adapted RuVector +//! inference. Per-home thresholds learned from the Seed's own +//! long-term observation stream. +//! 4. **Ed25519 witness chain** — every state transition signed so +//! regulated deployments (healthcare, education, shared housing) +//! have a tamper-evident audit log. +//! 5. **Multi-Seed federation** — peer discovery via mDNS + cross-Seed +//! event deduplication keyed on ADR-110's ≤100 µs mesh-aligned +//! timestamps. One fall in a shared room emits one alert, not N. +//! 6. **OTA firmware coordination** — the cog manages C6 firmware +//! rollouts for ESP32-C6 nodes in the local mesh. +//! +//! The cog binary entrypoint is in `bin/main.rs`. Library modules +//! below are intentionally small and testable per the /loop-worker +//! discipline rules (see `docs/ADR-110-BRANCH-STATE.md`). + +pub mod manifest; + +/// Cog identifier used in Seed's app-registry.json + the manifest. +pub const COG_ID: &str = "ha-matter"; + +/// mDNS service type advertised when the cog starts. +pub const MDNS_SERVICE_TYPE: &str = "_ruview-ha._tcp"; + +/// Default port for the cog's local HTTP control surface (`/health`, +/// `/api/v1/cog/status`). Distinct from the MQTT broker port. +pub const DEFAULT_CONTROL_PORT: u16 = 9180; + +/// Default port for the embedded MQTT broker, when enabled. +pub const DEFAULT_EMBEDDED_BROKER_PORT: u16 = 1883; diff --git a/v2/crates/cog-ha-matter/src/main.rs b/v2/crates/cog-ha-matter/src/main.rs new file mode 100644 index 00000000..b3fbba71 --- /dev/null +++ b/v2/crates/cog-ha-matter/src/main.rs @@ -0,0 +1,94 @@ +//! `cog-ha-matter` — Home Assistant + Matter Cognitum Seed cog (ADR-116). +//! +//! Binary entrypoint. The actual wiring lives in [`cog_ha_matter`] — +//! this main.rs is intentionally tiny so the cog runtime can call +//! into the library from tests and from the Seed's control plane +//! integration tests without re-launching the binary. + +use std::process::ExitCode; + +use clap::Parser; +use tracing::info; + +#[derive(Parser, Debug)] +#[command( + name = "cog-ha-matter", + version, + about = "Home Assistant + Matter Cognitum Seed cog", + long_about = "Wraps the ADR-115 HA-DISCO + HA-MIND publisher as a \ + Seed-installable artifact with mDNS, embedded broker, \ + RuVector-backed thresholds, and Ed25519 witness. See \ + docs/adr/ADR-116-cog-ha-matter-seed.md for the design." +)] +struct Args { + /// Where to find the local sensing-server (the cog speaks to it + /// to pull `VitalsSnapshot` for republication over MQTT/Matter). + #[arg(long, default_value = "http://127.0.0.1:3000")] + sensing_url: String, + + /// MQTT broker host. When omitted the cog can spin up an embedded + /// rumqttd on `DEFAULT_EMBEDDED_BROKER_PORT` (v1: external only). + #[arg(long, default_value = "127.0.0.1")] + mqtt_host: String, + + /// MQTT broker port. + #[arg(long, default_value_t = cog_ha_matter::DEFAULT_EMBEDDED_BROKER_PORT)] + mqtt_port: u16, + + /// Strip biometrics at the wire — only semantic primitives published. + /// Matches ADR-115 `--privacy-mode`. The right default for any + /// deployment with non-tenant occupants. + #[arg(long)] + privacy_mode: bool, + + /// Print the manifest the cog would self-report to the Seed's + /// control plane and exit. Useful for the build-time signer. + #[arg(long)] + print_manifest: bool, +} + +#[tokio::main] +async fn main() -> ExitCode { + tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| "cog_ha_matter=info,info".into()), + ) + .init(); + + let args = Args::parse(); + + info!( + sensing_url = %args.sensing_url, + mqtt = format!("{}:{}", args.mqtt_host, args.mqtt_port), + privacy = args.privacy_mode, + "cog-ha-matter starting (ADR-116 P2 scaffold)" + ); + + if args.print_manifest { + // Emit the manifest with build-time-template placeholders. The + // Makefile substitutes {{VERSION}} / {{ARCH}} before signing. + let m = cog_ha_matter::manifest::CogManifest { + id: cog_ha_matter::COG_ID.into(), + version: env!("CARGO_PKG_VERSION").into(), + binary_url: + "https://storage.googleapis.com/cognitum-apps/cogs/{{ARCH}}/cog-ha-matter-{{ARCH}}" + .into(), + binary_bytes: 0, + binary_sha256: String::new(), + binary_signature: String::new(), + installed_at: 0, + status: "installed".into(), + }; + println!( + "{}", + serde_json::to_string_pretty(&m).expect("manifest serialization is infallible") + ); + return ExitCode::SUCCESS; + } + + // P2 stops here — P3 will boot the ADR-115 MQTT publisher in a + // `tokio::spawn` and register the mDNS responder + control plane. + info!("scaffold-only — P3 wires the MQTT publisher next"); + ExitCode::SUCCESS +} diff --git a/v2/crates/cog-ha-matter/src/manifest.rs b/v2/crates/cog-ha-matter/src/manifest.rs new file mode 100644 index 00000000..c4597726 --- /dev/null +++ b/v2/crates/cog-ha-matter/src/manifest.rs @@ -0,0 +1,99 @@ +//! Cog manifest — same shape as `cog-pose-estimation/cog/manifest.template.json` +//! per ADR-101 / ADR-102 / ADR-116. Generated at build time by the cog's +//! Makefile, signed by the project's Ed25519 release key, uploaded to +//! `gs://cognitum-apps/cogs//cog-ha-matter-` for Seeds to fetch +//! via `app-registry.json`. +//! +//! The runtime ships the typed view here so the cog can self-report its +//! manifest to the Seed's control plane (`/api/v1/cog/status`). +//! +//! Kept in lib.rs's nearest sibling module so manifest format drift between +//! build-time template and runtime serializer fires a named test. + +use serde::{Deserialize, Serialize}; + +/// Wire-format mirror of `cog/manifest.template.json`. +/// +/// Every field is required at install time; `binary_signature` is the +/// Ed25519 sig over `binary_sha256` so the Seed can verify the cog +/// binary wasn't tampered with between GCS and the device. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CogManifest { + /// Stable cog identifier ("ha-matter"). Becomes the directory name + /// under `/var/lib/cognitum/apps//` on the Seed. + pub id: String, + /// SemVer of the cog binary. Bumped by the Makefile from + /// `cargo pkgid` at release time. + pub version: String, + /// Where the Seed fetches the binary from. Arch-specific URL with + /// the `{{ARCH}}` template slot filled in (e.g. `arm`, `x86_64`). + pub binary_url: String, + /// Bytes of the binary blob. Set at build time after `wc -c`. + pub binary_bytes: u64, + /// SHA-256 of the binary, hex-lowercase, no `0x` prefix. The Seed + /// verifies this before exec(). + pub binary_sha256: String, + /// Ed25519 signature over `binary_sha256`, base64-encoded. Optional + /// for unsigned dev builds; required for cogs listed in + /// `app-registry.json`. + pub binary_signature: String, + /// Unix epoch seconds at install time. The Seed stamps this when it + /// completes a successful install/upgrade. + pub installed_at: u64, + /// One of `"installed"`, `"upgrading"`, `"degraded"`, `"removed"`. + pub status: String, +} + +impl CogManifest { + pub fn id() -> &'static str { + super::COG_ID + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Lock the JSON wire shape against accidental field renames. Both + /// the Seed's control plane and the build-time signer parse this — + /// any drift fires a named test instead of silently breaking ops. + #[test] + fn manifest_round_trip_matches_template() { + let m = CogManifest { + id: "ha-matter".into(), + version: "0.1.0".into(), + binary_url: + "https://storage.googleapis.com/cognitum-apps/cogs/arm/cog-ha-matter-arm" + .into(), + binary_bytes: 4_200_000, + binary_sha256: + "a".repeat(64), + binary_signature: "Zm9v".into(), + installed_at: 1_779_512_400, + status: "installed".into(), + }; + let json = serde_json::to_value(&m).unwrap(); + // Eight required fields, no extras. + for key in [ + "id", + "version", + "binary_url", + "binary_bytes", + "binary_sha256", + "binary_signature", + "installed_at", + "status", + ] { + assert!(json.get(key).is_some(), "missing manifest field `{key}`"); + } + let m2: CogManifest = serde_json::from_value(json).unwrap(); + assert_eq!(m, m2); + } + + #[test] + fn manifest_id_constant_matches_cog_id() { + // The id helper must agree with the crate-level COG_ID constant + // (regression guard for a future rename). + assert_eq!(CogManifest::id(), super::super::COG_ID); + } +}