mirror of
https://github.com/ruvnet/RuView
synced 2026-06-30 13:43:18 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32b373eccf |
@@ -126,7 +126,10 @@
|
||||
"Bash(node .claude/*)",
|
||||
"mcp__claude-flow__:*"
|
||||
],
|
||||
"deny": []
|
||||
"deny": [
|
||||
"Read(./.env)",
|
||||
"Read(./.env.*)"
|
||||
]
|
||||
},
|
||||
"attribution": {
|
||||
"commit": "Co-Authored-By: claude-flow <ruv@ruv.net>",
|
||||
|
||||
@@ -26,8 +26,6 @@ on:
|
||||
- 'v2/crates/wifi-densepose-signal/**'
|
||||
- 'v2/crates/wifi-densepose-vitals/**'
|
||||
- 'v2/crates/wifi-densepose-wifiscan/**'
|
||||
- 'v2/crates/wifi-densepose-bfld/**'
|
||||
- 'v2/crates/cog-ha-matter/**'
|
||||
- 'v2/Cargo.toml'
|
||||
- 'v2/Cargo.lock'
|
||||
- 'ui/**'
|
||||
@@ -61,16 +59,11 @@ jobs:
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
# Bypassing docker/login-action@v3: the action kept emitting
|
||||
# "malformed HTTP Authorization header" against a known-good
|
||||
# dckr_pat_* token (verified by direct curl against the Hub API).
|
||||
# `docker login --password-stdin` is the documented credential
|
||||
# path and avoids whatever encoding step the action injects.
|
||||
env:
|
||||
DH_USER: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DH_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: |
|
||||
printf '%s' "$DH_TOKEN" | docker login docker.io -u "$DH_USER" --password-stdin
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Log in to ghcr.io
|
||||
uses: docker/login-action@v3
|
||||
|
||||
+5
-14
@@ -3,7 +3,7 @@
|
||||
# Multi-stage build for minimal final image
|
||||
|
||||
# Stage 1: Build
|
||||
FROM rust:1.89-bookworm AS builder
|
||||
FROM rust:1.85-bookworm AS builder
|
||||
|
||||
WORKDIR /build
|
||||
|
||||
@@ -14,14 +14,9 @@ COPY v2/crates/ ./crates/
|
||||
# Copy vendored RuVector crates
|
||||
COPY vendor/ruvector/ /build/vendor/ruvector/
|
||||
|
||||
# Build release binaries:
|
||||
# - sensing-server with `mqtt` feature so the HA-DISCO MQTT publisher
|
||||
# (ADR-115) is wired in (auto-discovery topics flow to Home Assistant)
|
||||
# - cog-ha-matter, the ADR-116 Cognitum cog that wraps HA-DISCO +
|
||||
# HA-MIND + mDNS + embedded broker for Home Assistant / Matter
|
||||
RUN cargo build --release -p wifi-densepose-sensing-server --features mqtt 2>&1 \
|
||||
&& cargo build --release -p cog-ha-matter 2>&1 \
|
||||
&& strip target/release/sensing-server target/release/cog-ha-matter
|
||||
# Build release binary
|
||||
RUN cargo build --release -p wifi-densepose-sensing-server 2>&1 \
|
||||
&& strip target/release/sensing-server
|
||||
|
||||
# Stage 2: Runtime
|
||||
FROM debian:bookworm-slim
|
||||
@@ -32,9 +27,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binaries
|
||||
# Copy binary
|
||||
COPY --from=builder /build/target/release/sensing-server /app/sensing-server
|
||||
COPY --from=builder /build/target/release/cog-ha-matter /app/cog-ha-matter
|
||||
|
||||
# Copy UI assets
|
||||
COPY ui/ /app/ui/
|
||||
@@ -51,7 +45,6 @@ RUN set -e; \
|
||||
test -d "$d" || { echo "FATAL: missing UI directory $d"; exit 1; }; \
|
||||
done; \
|
||||
test -x /app/sensing-server || { echo "FATAL: /app/sensing-server is not executable"; exit 1; }; \
|
||||
test -x /app/cog-ha-matter || { echo "FATAL: /app/cog-ha-matter is not executable"; exit 1; }; \
|
||||
echo "image assets OK"
|
||||
|
||||
# Optional bearer-token auth on /api/v1/*: leave unset for LAN-mode (default),
|
||||
@@ -65,8 +58,6 @@ EXPOSE 3000
|
||||
EXPOSE 3001
|
||||
# ESP32 UDP
|
||||
EXPOSE 5005/udp
|
||||
# MQTT broker (cog-ha-matter embedded broker — Home Assistant + Matter)
|
||||
EXPOSE 1883
|
||||
|
||||
ENV RUST_LOG=info
|
||||
|
||||
|
||||
@@ -15,21 +15,6 @@
|
||||
# MODELS_DIR — directory to scan for .rvf model files (default: data/models)
|
||||
set -e
|
||||
|
||||
# Route to cog-ha-matter (ADR-116) when invoked as:
|
||||
# docker run <image> cog-ha-matter [--flags]
|
||||
# or via the short alias `ha-matter`. Strips the keyword and execs the
|
||||
# Home Assistant + Matter cog binary, defaulting --sensing-url to the
|
||||
# co-located sensing-server endpoint so docker-compose deployments work
|
||||
# out of the box.
|
||||
case "${1:-}" in
|
||||
cog-ha-matter|ha-matter)
|
||||
shift
|
||||
exec /app/cog-ha-matter \
|
||||
--sensing-url "${SENSING_URL:-http://127.0.0.1:3000}" \
|
||||
"$@"
|
||||
;;
|
||||
esac
|
||||
|
||||
# If the first argument looks like a flag (starts with -), prepend the
|
||||
# server binary so users can just pass flags:
|
||||
# docker run <image> --source esp32 --tick-ms 500
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
# ADR-125: RuView ↔ Apple Home native HAP bridge — direct HomeKit accessory advertisement from the Seed
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Status** | Proposed |
|
||||
| **Date** | 2026-05-25 |
|
||||
| **Deciders** | ruv |
|
||||
| **Codename** | **APPLE-FABRIC** — RuView speaks HomeKit directly so Apple HomePod / Apple TV act as the discovery + automation surface with zero Home-Assistant middle layer |
|
||||
| **Relates to** | [ADR-115](ADR-115-home-assistant-integration.md) (HA-DISCO MQTT publisher), [ADR-116](ADR-116-cog-ha-matter-seed.md) (cog-ha-matter §P7 left HAP/Matter as a feature-flag stub), [ADR-118](ADR-118-bfld-beamforming-feedback-layer-for-detection.md) (BFLD presence + identity-risk events), [ADR-122](ADR-122-bfld-ruview-ha-matter-exposure.md) (BFLD HA/Matter exposure) |
|
||||
| **Tracking issue** | TBD |
|
||||
|
||||
---
|
||||
|
||||
## 1. Context
|
||||
|
||||
### 1.1 The misunderstanding worth correcting once
|
||||
|
||||
A naive integration tries to **push** data to a HomePod — open a socket, send a JSON-RPC, call an MQTT topic on `homepod.local`. Apple intentionally does not expose that surface. The HomePod is not an endpoint; it is the **Home Hub + Matter Controller + HomeKit Controller + Siri endpoint** for the Apple Home ecosystem on the LAN. It **discovers** accessories that advertise themselves on the local network via Bonjour/mDNS using the HomeKit Accessory Protocol (HAP) or Matter.
|
||||
|
||||
The correct direction of flow is therefore:
|
||||
|
||||
```text
|
||||
RuView / Seed
|
||||
↓ (advertise HAP / Matter accessory on LAN)
|
||||
HomeKit / Matter accessory
|
||||
↓ (mDNS discovery)
|
||||
HomePod
|
||||
↓ (forwards to Apple Home automation graph)
|
||||
Apple Home ecosystem (iPhone, Watch, Mac, Siri, automations)
|
||||
```
|
||||
|
||||
### 1.2 What we ship today and where it stops
|
||||
|
||||
ADR-115 ships an **MQTT auto-discovery publisher** that talks to Home Assistant. ADR-116's `cog-ha-matter` Cognitum cog wraps that publisher into a Seed-installable artifact with mDNS, an embedded rumqttd broker, RuVector-backed thresholds, and an Ed25519 witness chain. ADR-122 explicitly extends the same publisher with the BFLD presence / identity-risk / Soul-Match topics so a Home Assistant install sees them as auto-discovered entities. The current path to HomePod therefore runs:
|
||||
|
||||
```text
|
||||
RuView sensing-server ──► cog-ha-matter (MQTT HA-DISCO + HA-MIND)
|
||||
↓
|
||||
Home Assistant broker
|
||||
↓
|
||||
Home Assistant HomeKit Bridge add-on
|
||||
↓
|
||||
HomePod
|
||||
```
|
||||
|
||||
This works and the auto-discovery is real, but it introduces a hard dependency: an operator must run Home Assistant, install its HomeKit Bridge integration, and pair the bridge in the Apple Home app. The Seed alone does not appear in Apple Home.
|
||||
|
||||
ADR-116 §P7 anticipated this — the `cog-ha-matter` `Cargo.toml` already carries a `matter = []` feature stub with the comment "matter-rs is added in P7; intentionally absent in P1 to keep the dep surface small until the SDK choice is validated." This ADR closes that box.
|
||||
|
||||
### 1.3 Why now
|
||||
|
||||
Three forces line up in 2026-05:
|
||||
|
||||
1. **The BFLD privacy gate (ADR-118 / 120 / 121) is shipped.** Class-2 and class-3 frames are the only ones eligible to cross the Matter boundary (ADR-122 §2.4). Without that gate we could not safely expose RuView signals to a consumer ecosystem. With it, every Anonymous / Restricted event is safe to advertise as a HomeKit sensor.
|
||||
2. **`@ruvnet/rvagent` (ADR-124) is on npm.** The MCP surface that lets agents query RuView is live. A first-class Apple-Home presence widens RuView's reach from "agents that speak MCP" to "anyone with an iPhone and a HomePod" — the consumer wedge.
|
||||
3. **The Cognitum Seed Docker image now bundles `cog-ha-matter`** (this branch's `Dockerfile.rust` change, see #794) — the runtime where a HAP advertiser would live is finally a single-image deployment.
|
||||
|
||||
### 1.4 Strategic framing
|
||||
|
||||
The combination is asymmetric:
|
||||
|
||||
| Layer | RuView contributes | Apple Home contributes |
|
||||
|-------|---------------------|------------------------|
|
||||
| Sensing | Passive RF presence, breathing, heart rate, fall risk, BFLD identity-risk, through-wall occupancy, longitudinal wellness | (none — Apple has no native RF sensing surface) |
|
||||
| Adoption | (limited — researcher-grade hardware today) | iPhone, Watch, Mac, HomePod, Apple TV installed base; consumer trust; voice; on-device intelligence |
|
||||
| UX | (utility CLI + a Web UI) | Home app, Siri, automation engine, notifications, accessibility |
|
||||
| Trust | Ed25519 witness chain, privacy class gate, local-first | Apple HomeKit local pairing, end-to-end encrypted, no cloud requirement |
|
||||
|
||||
RuView supplies the **invisible cognition layer** Apple cannot provide on its own; Apple supplies the **distribution and UX** that an open sensing stack cannot bootstrap. Direct HAP integration removes the only structural barrier between those two layers — Home Assistant as a mandatory intermediary.
|
||||
|
||||
---
|
||||
|
||||
## 2. Decision
|
||||
|
||||
Ship a **native HomeKit / Matter accessory** in the Seed runtime so a freshly-imaged Cognitum Seed appears in the Apple Home app under `Add Accessory → More Options` with **zero Home-Assistant dependency**.
|
||||
|
||||
Concretely:
|
||||
|
||||
1. Add a `hap-accessory` workspace component that advertises a set of HomeKit characteristics over mDNS using HAP-1.1 (HomeKit Accessory Protocol).
|
||||
2. The component subscribes to `wifi-densepose-sensing-server`'s WebSocket / BFLD `MqttEvent` stream and maps each privacy-class-2/3 event onto a HomeKit characteristic update.
|
||||
3. The same Docker image that ships `sensing-server` and `cog-ha-matter` ships the new advertiser as a third entrypoint:
|
||||
|
||||
```bash
|
||||
docker run --network host ruvnet/wifi-densepose:latest hap-accessory --privacy-mode
|
||||
```
|
||||
|
||||
`--network host` (or a macvlan bridge) is required because HAP pairing depends on the accessory and the controller seeing each other's mDNS broadcasts on the same L2 segment — same constraint Home Assistant's HomeKit Bridge has.
|
||||
|
||||
### 2.1 Two implementation tracks (decided here together; ship 2.1.a first)
|
||||
|
||||
#### 2.1.a — **HAP-python sidecar** (fastest to ship, lands first)
|
||||
|
||||
Add a tiny Python entrypoint `bridges/hap-python/ruview_hap.py` using the well-maintained [`HAP-python`](https://github.com/ikalchev/HAP-python) library. The Dockerfile gets a thin Python runtime stage; the entrypoint script polls `sensing-server` over HTTP and pushes characteristic updates into the HAP loop.
|
||||
|
||||
```python
|
||||
# bridges/hap-python/ruview_hap.py (≈80 LOC)
|
||||
from pyhap.accessory import Accessory
|
||||
from pyhap.accessory_driver import AccessoryDriver
|
||||
from pyhap.const import CATEGORY_SENSOR
|
||||
import urllib.request, json, threading, time
|
||||
|
||||
SENSING_URL = "http://127.0.0.1:3000/api/v1"
|
||||
|
||||
class RuViewSensor(Accessory):
|
||||
category = CATEGORY_SENSOR
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
s_motion = self.add_preload_service('MotionSensor')
|
||||
self.c_motion = s_motion.configure_char('MotionDetected')
|
||||
s_occ = self.add_preload_service('OccupancySensor')
|
||||
self.c_occ = s_occ.configure_char('OccupancyDetected')
|
||||
s_temp = self.add_preload_service('TemperatureSensor')
|
||||
self.c_temp = s_temp.configure_char('CurrentTemperature')
|
||||
threading.Thread(target=self._poll, daemon=True).start()
|
||||
|
||||
def _poll(self):
|
||||
while True:
|
||||
try:
|
||||
v = json.loads(urllib.request.urlopen(f"{SENSING_URL}/vitals").read())
|
||||
self.c_motion.set_value(bool(v.get("motion_present")))
|
||||
self.c_occ.set_value(int(bool(v.get("occupancy"))))
|
||||
if "ambient_temp_c" in v:
|
||||
self.c_temp.set_value(v["ambient_temp_c"])
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(1.0)
|
||||
|
||||
driver = AccessoryDriver(port=51826)
|
||||
driver.add_accessory(accessory=RuViewSensor(driver, 'RuView Sense'))
|
||||
driver.start()
|
||||
```
|
||||
|
||||
Pairing flow on the operator's iPhone:
|
||||
|
||||
1. Open Apple Home → `Add Accessory` → `More Options`
|
||||
2. Tap `RuView Sense` (appears via mDNS automatically)
|
||||
3. Enter the setup code shown in `docker logs` (or pinned in env)
|
||||
4. Done — Siri can say "Hey Siri, is anyone in the living room?"
|
||||
|
||||
Replace the `motion_present` / `occupancy` mappings progressively as RuView capabilities mature: BFLD class-2 `presence` event → `OccupancyDetected`; BFLD class-3 `identity_risk_score > threshold` → `SecuritySystemCurrentState`; `breathing_present` → `OccupancyDetected` (sleep room); `fall_risk` → a programmable switch that fires an Apple Home automation.
|
||||
|
||||
Acceptance criteria for 2.1.a:
|
||||
|
||||
- A1: `docker run ... hap-accessory --privacy-mode` advertises an `_hap._tcp` service that the HomePod sees within 30s (`dns-sd -B _hap._tcp local.` on a peer Mac shows `RuView Sense`).
|
||||
- A2: Pairing from Apple Home succeeds and the entity appears in the Home app under the configured room.
|
||||
- A3: `MotionDetected` flips within 2 s of an actual RF presence detection from a calibrated ESP32 source (`CSI_SOURCE=esp32`).
|
||||
- A4: Restarting the container preserves the pairing (HAP state persisted under `/var/lib/ruview-hap/`).
|
||||
- A5: Privacy: the entrypoint refuses to launch without `--privacy-mode` when `RUVIEW_BFLD_PRIVACY_CLASS` is unset, matching the structural invariant I1 (Raw BFI never exits the node — ADR-118 §2.2).
|
||||
|
||||
#### 2.1.b — **Rust-native HAP** (single binary, closes ADR-116 P7)
|
||||
|
||||
Wire one of the maintained Rust HAP crates into `cog-ha-matter` so the Python sidecar can be removed. Candidate crates:
|
||||
|
||||
- [`hap`](https://crates.io/crates/hap) (Sebastian Schmidt) — last published 0.1.0-pre.16, MIT, active in 2024, supports HAP-1.1, has examples for `MotionSensor`, `LightBulb`, `OccupancySensor`. **First choice.**
|
||||
- [`accessory-server`](https://crates.io/crates/accessory-server) — narrower scope, fewer services
|
||||
- A future `matter-rs` crate from project-chip — once stable (CHIP SDK Rust bindings are still emerging in 2026-05)
|
||||
|
||||
The `matter = []` feature stub in `cog-ha-matter/Cargo.toml` (added in ADR-116 P1) becomes:
|
||||
|
||||
```toml
|
||||
[features]
|
||||
default = []
|
||||
mqtt = ["dep:rumqttc"]
|
||||
matter = ["dep:hap"] # ADR-125 §2.1.b
|
||||
```
|
||||
|
||||
with a runtime subcommand `cog-ha-matter --mode hap` that mirrors the Python advertiser's accessory set. Single binary, no Python interpreter in the image, matches the all-Rust ethos of the Cognitum Seed (ADR-116 §1.4).
|
||||
|
||||
### 2.2 What we DO NOT do in 2.1.a or 2.1.b
|
||||
|
||||
- **No Matter (CHIP) controller code.** Matter is the long-term play but its SDK in Rust is not yet stable and the certificate provisioning is heavy. HAP-1.1 over Bonjour gives 95% of the UX for 10% of the complexity, today.
|
||||
- **No direct connection to the HomePod.** As the framing in §1.1 makes explicit, RuView never opens a socket to the HomePod. It advertises; the HomePod discovers.
|
||||
- **No iCloud account binding.** HAP pairing is local-network-only by design — RuView gets adoption without ever touching Apple ID, which is a privacy story we keep cleanly.
|
||||
- **No Class-0 (`Raw`) BFI exposure.** Structural invariant I1 (ADR-118 §2.2) holds. Only privacy-class-2 (Anonymous) and class-3 (Restricted) frames may be mapped onto HomeKit characteristics. The advertiser refuses to start in any other mode.
|
||||
|
||||
### 2.3 Sequencing
|
||||
|
||||
1. **P1** (this ADR-125 + 1 PR) — HAP-python sidecar (§2.1.a) lands as a separate entrypoint in the same Docker image. AC A1–A5 are gates.
|
||||
2. **P2** (follow-up PR after operator feedback from 5+ Apple Home pairings) — Rust-native HAP (§2.1.b). Replaces P1; P1's `bridges/hap-python/` becomes an archived reference implementation.
|
||||
3. **P3** (when matter-rs stabilizes) — Matter Controller path (still RuView-as-accessory, but using the Matter clusters rather than HAP-1.1 services). The Cognitum Cog gains a Matter QR code; pairing flow widens to "any Matter-capable controller, not just Apple."
|
||||
|
||||
---
|
||||
|
||||
## 3. Consequences
|
||||
|
||||
### 3.1 Wins
|
||||
|
||||
- **Direct discoverability on Apple Home.** A Seed in the kitchen appears as `RuView Sense` in the Home app within seconds of `docker run`. No HA, no MQTT broker, no Home-Assistant HomeKit Bridge add-on.
|
||||
- **Siri natively answers RuView questions.** "Hey Siri, is anyone in the kitchen?" — the question reaches the HomeKit characteristic without any custom skill or HA template sensor.
|
||||
- **Apple-Home automations gain ambient triggers** RuView already produces (presence, breathing, fall, identity-risk) for free — they become first-class automation triggers in the Home app's UI.
|
||||
- **Strategically corrects RuView's distribution problem.** The Apple Home installed base is the largest consumer surface for HomeKit-grade accessories. RuView's sensing IP becomes addressable to that base without an SDK port.
|
||||
- **Closes ADR-116 §P7** — the long-flagged matter / HAP gap is now scheduled, not deferred indefinitely.
|
||||
|
||||
### 3.2 Costs
|
||||
|
||||
- **Python runtime in the Docker image (only for 2.1.a, until 2.1.b lands).** Adds ~30 MB to the runtime layer. Mitigation: P2 removes it; P1 isolates the Python dep in a side-stage so the sensing-server / cog-ha-matter layers stay clean.
|
||||
- **Network-mode constraint.** HAP pairing needs the controller and accessory on the same L2 segment (mDNS broadcasts). Operators who run RuView in a container behind a NAT/bridge need `--network host` or a macvlan — same constraint HA's HomeKit Bridge has, but worth documenting.
|
||||
- **Pairing state persistence.** HAP-python stores pairing data in a local file; that state must survive container restarts. Volume-mount `/var/lib/ruview-hap/` to a persistent location.
|
||||
|
||||
### 3.3 Risks
|
||||
|
||||
- **HAP-python maintenance.** The library is community-maintained; if it goes stale, P2 (Rust-native) absorbs the risk. 2.1.a is explicitly a stepping stone, not a long-term commitment.
|
||||
- **Apple's evolving requirements.** HomeKit Accessory Certification is required to put a HAP logo on hardware, not to ship a software accessory that pairs locally. RuView's container deployment is squarely in the "uncertified developer accessory" lane, which Apple explicitly permits for local pairing. Worth restating in the operator README.
|
||||
- **Privacy-class enforcement at the bridge boundary.** A bug that lets a class-0 BFI frame's data influence a HAP characteristic update would violate I1. Mitigation: the bridge consumes only the BFLD `MqttEvent` stream (which is already gated by `PrivacyGate` per ADR-120), never raw BFI; tests assert this in the same style as ADR-122 §4.3.
|
||||
|
||||
### 3.4 Reversibility
|
||||
|
||||
The advertiser is a separate entrypoint — pulling it out is `docker run` without the `hap-accessory` first-arg, identical to today's behavior. Zero impact on `sensing-server` and `cog-ha-matter` operations.
|
||||
|
||||
---
|
||||
|
||||
## 4. Acceptance test (P1 / §2.1.a)
|
||||
|
||||
```bash
|
||||
# 1. Start a sensing server (simulated source so the test runs anywhere)
|
||||
docker run -d --name rs -p 3000:3000 -e CSI_SOURCE=simulated \
|
||||
ruvnet/wifi-densepose:latest
|
||||
|
||||
# 2. Launch the HAP advertiser sidecar in privacy mode
|
||||
docker run -d --name hap --network host \
|
||||
-v /var/lib/ruview-hap:/var/lib/ruview-hap \
|
||||
-e RUVIEW_BFLD_PRIVACY_CLASS=2 \
|
||||
ruvnet/wifi-densepose:latest hap-accessory --privacy-mode
|
||||
|
||||
# 3. From a Mac on the same LAN: should see RuView Sense as HAP
|
||||
dns-sd -B _hap._tcp local. # expect: "RuView Sense" within 30 s
|
||||
|
||||
# 4. From iPhone Home app: Add Accessory → More Options → RuView Sense
|
||||
# Enter setup code from `docker logs hap`
|
||||
# Expect: pairing completes, entity appears in selected Room
|
||||
|
||||
# 5. Cycle the container; re-open Home app: entity is still paired
|
||||
docker restart hap
|
||||
# Expect: no re-pairing prompt; characteristic updates resume
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Open questions
|
||||
|
||||
- Should `hap-accessory` advertise as **one bridge** with N child accessories (preferred — single pairing for all RuView sensors on the Seed), or **N independent accessories** (simpler code, worse Home-app UX)?
|
||||
- Setup code: derived deterministically from the Seed's Ed25519 witness key (so reinstalls re-use the same code), or random per launch (better security, worse UX)?
|
||||
- Map of BFLD events ↔ HomeKit characteristics: presence → `OccupancyDetected` is obvious; how do we surface `identity_risk_score` (continuous 0..1) — as a `SecuritySystemCurrentState`, a custom characteristic, or a `MotionSensor` proxy gated by a threshold?
|
||||
- Does the same advertiser run on a Cognitum Seed (ESP32-S3-class hardware) or only on the Seed's host appliance? If the ESP32 advertises directly, that's a future ADR — for now the bridge lives on the host.
|
||||
|
||||
---
|
||||
|
||||
## 6. References
|
||||
|
||||
- ADR-115 — Home-Assistant integration (HA-DISCO MQTT publisher)
|
||||
- ADR-116 — `cog-ha-matter` Seed cog (this is where the `matter` feature stub lives)
|
||||
- ADR-118 — BFLD beamforming-feedback layer (privacy gate + class invariants)
|
||||
- ADR-122 — BFLD RuView HA/Matter exposure (current MQTT-based bridge that this ADR's HAP-native path complements)
|
||||
- HomeKit Accessory Protocol Specification (Non-Commercial Version), Apple — https://developer.apple.com/apple-home/
|
||||
- HAP-python — https://github.com/ikalchev/HAP-python
|
||||
- `hap` (Rust) — https://crates.io/crates/hap
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ruview",
|
||||
"description": "End-to-end RuView (WiFi-DensePose) toolkit for Claude Code: onboarding, ESP32 hardware setup, configuration, sensing applications, model training, advanced multistatic sensing, witness verification, BFLD privacy layer, and rvAgent + RVF agentic flows — from practical to advanced.",
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "ruvnet",
|
||||
"url": "https://github.com/ruvnet/RuView"
|
||||
@@ -19,14 +19,5 @@
|
||||
"edge-ai",
|
||||
"model-training",
|
||||
"onboarding"
|
||||
],
|
||||
"mcpServers": {
|
||||
"rvagent": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@ruvnet/rvagent"],
|
||||
"env": {
|
||||
"RVAGENT_SENSING_URL": "http://localhost:3000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,24 +2,6 @@
|
||||
|
||||
You are helping the operator explore or prototype the integration of `vendor/ruvector/crates/rvAgent/` (a production Rust AI-agent framework) with RuView's existing sensing pipeline (`v2/crates/wifi-densepose-*`) and the RVF cognitive container format (`v2/crates/wifi-densepose-sensing-server/src/rvf_container.rs`).
|
||||
|
||||
## Live MCP server: `@ruvnet/rvagent` v0.1.0
|
||||
|
||||
The TypeScript MCP server (`tools/ruview-mcp/`, published as `@ruvnet/rvagent`) is live on npm and exposes `bfld_last_scan`, `bfld_subscribe`, `presence_now`, `vitals_get_breathing`, `vitals_get_heart_rate`, `vitals_get_all`, `vitals_fetch`. Add to a Codex MCP config:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"rvagent": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@ruvnet/rvagent"],
|
||||
"env": { "RVAGENT_SENSING_URL": "http://localhost:3000" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the operator-facing tool surface; the Rust crate below remains the substrate for deeper RVF-aware agentic flows.
|
||||
|
||||
## Trigger phrasing
|
||||
|
||||
- "wire rvAgent into RuView"
|
||||
|
||||
@@ -7,24 +7,6 @@ description: Explore and prototype rvAgent + RVF integration for RuView agentic
|
||||
|
||||
Surface area for wiring `vendor/ruvector/crates/rvAgent/` into RuView so the existing sensing pipeline becomes the substrate an agentic flow can read, reason about, and respond to.
|
||||
|
||||
## Quickstart — published MCP server (`@ruvnet/rvagent` v0.1.0)
|
||||
|
||||
Installing this plugin registers `@ruvnet/rvagent` as an MCP server. On activation, Claude Code spawns `npx -y @ruvnet/rvagent` and exposes its tools directly:
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| `bfld_last_scan` | Most recent BFLD event from the sensing server |
|
||||
| `bfld_subscribe` | Stream BFLD events for a window |
|
||||
| `presence_now` | Current room-level presence state |
|
||||
| `vitals_get_breathing` | Latest breathing-rate sample |
|
||||
| `vitals_get_heart_rate` | Latest heart-rate sample |
|
||||
| `vitals_get_all` | Composite vitals snapshot |
|
||||
| `vitals_fetch` | Historical vitals window |
|
||||
|
||||
Override the sensing-server URL via the `RVAGENT_SENSING_URL` env var (default `http://localhost:3000`). Source lives at `tools/ruview-mcp/`; ADR-124 captures the design.
|
||||
|
||||
Smoke-check the wiring: `npm view @ruvnet/rvagent version` should return `0.1.0` (or newer).
|
||||
|
||||
## When to use this skill
|
||||
|
||||
- "I want an agent that reacts to BFLD presence in the kitchen and pages the carer."
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# rotate-npm-token.sh — push NPM_TOKEN from .env into GCP Secret Manager
|
||||
# and (optionally) publish @ruvnet/rvagent.
|
||||
#
|
||||
# Usage:
|
||||
# bash scripts/rotate-npm-token.sh # rotate only
|
||||
# bash scripts/rotate-npm-token.sh --publish # rotate + npm publish
|
||||
#
|
||||
# Env overrides:
|
||||
# GCP_PROJECT (default: cognitum-20260110)
|
||||
# NPM_TOKEN_SECRET (default: NPM_TOKEN)
|
||||
# ENV_FILE (default: <repo-root>/.env)
|
||||
# PUBLISH_PACKAGE_DIR (default: <repo-root>/tools/ruview-mcp)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ENV_FILE="${ENV_FILE:-$REPO_ROOT/.env}"
|
||||
PROJECT="${GCP_PROJECT:-cognitum-20260110}"
|
||||
SECRET="${NPM_TOKEN_SECRET:-NPM_TOKEN}"
|
||||
PKG_DIR="${PUBLISH_PACKAGE_DIR:-$REPO_ROOT/tools/ruview-mcp}"
|
||||
|
||||
[ -f "$ENV_FILE" ] || { echo "ERROR: .env not found at $ENV_FILE" >&2; exit 1; }
|
||||
|
||||
TOKEN="$(awk -F= '
|
||||
/^[[:space:]]*NPM_TOKEN[[:space:]]*=/ {
|
||||
sub(/^[^=]*=[[:space:]]*/, "", $0)
|
||||
sub(/^["'\'']/, "", $0)
|
||||
sub(/["'\''][[:space:]]*$/, "", $0)
|
||||
sub(/[[:space:]]+$/, "", $0)
|
||||
print
|
||||
exit
|
||||
}
|
||||
' "$ENV_FILE")"
|
||||
|
||||
if [ -z "${TOKEN:-}" ]; then
|
||||
echo "ERROR: NPM_TOKEN not found in $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LEN=${#TOKEN}
|
||||
echo "Found NPM_TOKEN in .env (length=$LEN)"
|
||||
|
||||
echo "Pushing new version to gcloud secret '$SECRET' in project '$PROJECT'..."
|
||||
if ! gcloud secrets describe "$SECRET" --project="$PROJECT" >/dev/null 2>&1; then
|
||||
echo "Secret '$SECRET' not found; creating..."
|
||||
printf '%s' "$TOKEN" | gcloud secrets create "$SECRET" \
|
||||
--project="$PROJECT" --replication-policy=automatic --data-file=-
|
||||
else
|
||||
printf '%s' "$TOKEN" | gcloud secrets versions add "$SECRET" \
|
||||
--project="$PROJECT" --data-file=-
|
||||
fi
|
||||
|
||||
echo "Verifying secret round-trips..."
|
||||
RETRIEVED="$(gcloud secrets versions access latest --secret="$SECRET" --project="$PROJECT")"
|
||||
if [ "$RETRIEVED" != "$TOKEN" ]; then
|
||||
echo "ERROR: retrieved token does not match the value written to .env" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "OK — secret '$SECRET' updated and verified (length=${#RETRIEVED})."
|
||||
|
||||
if [ "${1:-}" = "--publish" ]; then
|
||||
[ -d "$PKG_DIR" ] || { echo "ERROR: package dir not found at $PKG_DIR" >&2; exit 1; }
|
||||
echo "Publishing @ruvnet/rvagent from $PKG_DIR..."
|
||||
(
|
||||
cd "$PKG_DIR"
|
||||
if [ -f package.json ] && grep -q '"build"' package.json; then
|
||||
npm run build
|
||||
fi
|
||||
NODE_AUTH_TOKEN="$RETRIEVED" npm publish --access public
|
||||
)
|
||||
fi
|
||||
|
||||
echo "Done."
|
||||
@@ -6,6 +6,7 @@ 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"
|
||||
@@ -29,7 +30,7 @@ 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.1", path = "../wifi-densepose-sensing-server", default-features = false, features = ["mqtt"] }
|
||||
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" }
|
||||
|
||||
@@ -6,6 +6,7 @@ authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Cognitum Cog: learned multi-person counter from WiFi CSI (ADR-103). Replaces the PR #491 slot heuristic with a Candle-based count head + Stoer-Wagner multi-node fusion."
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "cog-person-count"
|
||||
|
||||
@@ -6,6 +6,7 @@ authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Cognitum Cog: 17-keypoint pose estimation from WiFi CSI. See ADR-100 (packaging) + ADR-101 (this Cog)."
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "cog-pose-estimation"
|
||||
@@ -35,7 +36,7 @@ candle-nn = { version = "0.9", default-features = false }
|
||||
safetensors = "0.4"
|
||||
# wifi-densepose-train re-exports the model types we need; depend by path
|
||||
# inside the workspace.
|
||||
wifi-densepose-train = { version = "0.3.1", path = "../wifi-densepose-train", default-features = false }
|
||||
wifi-densepose-train = { path = "../wifi-densepose-train", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "wifi-densepose-sensing-server"
|
||||
version = "0.3.1"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Lightweight Axum server for WiFi sensing UI with RuVector signal processing"
|
||||
license.workspace = true
|
||||
@@ -48,7 +48,7 @@ wifi-densepose-wifiscan = { version = "0.3.0", path = "../wifi-densepose-wifisca
|
||||
# default-features = false drops the optional ndarray-linalg/BLAS chain so that
|
||||
# `--no-default-features` at the workspace root can produce a Windows-friendly
|
||||
# build without vcpkg/openblas (issue #366, #415).
|
||||
wifi-densepose-signal = { version = "0.3.1", path = "../wifi-densepose-signal", default-features = false }
|
||||
wifi-densepose-signal = { version = "0.3.0", path = "../wifi-densepose-signal", default-features = false }
|
||||
|
||||
# Hardware crate — SyncPacket decoder for ADR-110 §A0.12 mesh-aligned timestamps.
|
||||
wifi-densepose-hardware = { version = "0.3.0", path = "../wifi-densepose-hardware" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "wifi-densepose-signal"
|
||||
version = "0.3.1"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "WiFi CSI signal processing for DensePose estimation"
|
||||
license.workspace = true
|
||||
|
||||
Vendored
+1
-1
Submodule vendor/midstream updated: 8f70d2bb9d...92250c20d8
Vendored
+1
-1
Submodule vendor/ruvector updated: 53f0419782...cf074121e5
Vendored
+1
-1
Submodule vendor/rvcsi updated: 72891d740f...77c8b6e051
Vendored
+1
-1
Submodule vendor/sublinear-time-solver updated: c25dddf163...47804fc5ca
Reference in New Issue
Block a user