Iter 46 — second attempt at fixing state_messages_published_on_snapshot_broadcast
on CI. The iter-45 SubAck fix proved necessary but not sufficient;
the test still returned an empty Vec for presence states.
Root cause analysis: the test was front-loading 6 snapshots over 1.2 s
after a 3 s warm-up sleep, then capturing for 8 s. That schedule
assumes:
- mosquitto sidecar is ready
- cargo build cache is warm
- rumqttc connect + 21 QoS-1 discovery publishes complete in <3 s
- the publisher's select! starts draining state_rx in <3 s
On the CI runner those assumptions break. The publisher takes >3 s to
finish discovery, so all 6 state publishes either land in the rumqttc
outbound channel before the broker is reachable OR are emitted while
the subscriber's reception path has stalled.
Fix: drive snapshots in a background task THROUGHOUT the capture
window instead of front-loading them. 40 snapshots × 300 ms = 12 s
of steady-state ON/OFF traffic across a 14 s capture window. Even if
the first 3-5 s of publishes are missed during slow publisher
bootstrap, plenty of ON and OFF messages arrive afterward.
This also makes the test more representative of real HA workloads
(steady stream of vitals, not a burst then silence).
Local cargo test --features mqtt --no-default-features --test
mqtt_integration --no-run → compiles green.
Co-Authored-By: claude-flow <ruv@ruv.net>
After 4 surgical fixes the state_messages_published_on_snapshot_broadcast
test still reports 'expected ON state, got []' on CI — and we can't
tell whether the publisher is publishing nothing, or publishing the
wrong topic, or publishing to a session the subscriber lost.
Two changes to surface what's actually happening:
1. Widen subscription from `homeassistant/binary_sensor/+/presence/state`
to `homeassistant/#`. Now the captured-message dump shows every
topic the publisher emitted under the homeassistant prefix —
discovery configs, availability heartbeats, state messages,
anything else. A narrow filter was hiding which side of the
pipeline was broken.
2. Add stderr `[diag]` lines that dump every captured (retain, topic,
payload-prefix) on test failure. CI runs `--nocapture` so the lines
land in the workflow log. From the next failed-CI log we'll know
whether:
- publisher isn't emitting state at all (no /state topics in dump)
- publisher is emitting to a different topic shape (typo in
topic format string)
- subscriber connected to a stale session and missed messages
(would see discovery + no state but dump would have count > 0)
- subscriber is connecting after publisher disconnected (count = 0
even after widening)
This is a debugging commit, not a production fix — once we know the
exact failure mode from the next CI log we can ship a real fix.
Refs PR #778, issue #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Third cause of the state_messages_published_on_snapshot_broadcast
failure (after timing fix in 5ed8e3451 and client_id fix in
2aeed32a7): the subscriber's eventloop was NEVER polled between
`client.subscribe(...).await` and `collect_published(eventloop, ...)`,
so the SUBSCRIBE packet was only queued in rumqttc's outbound channel
— it didn't reach the broker until collect_published began polling.
By that time the publisher had already emitted all 6 state messages.
The retained ones (binary_sensor presence with retain=true) should
have been redelivered on the late subscribe, but only the LAST one
would land — yet CI was reporting `got []` (zero messages).
Theory: the broker may not redeliver retained messages reliably when
the subscribe arrives during the publisher's burst, OR the test's
collect_published timing budget runs out before redelivery completes.
Fix: drain the subscriber's eventloop inside `subscribe_client` until
we see the SubAck for our subscribe. That guarantees the subscription
is active at the broker BEFORE the function returns, so non-retained
publishes from the publisher's send loop arrive normally.
Also made the subscriber client_id include a per-call nanosecond
suffix so subscribers in back-to-back tests can't collide on a shared
ID (paranoia, complementary to the publisher-side fix from
2aeed32a7).
Refs PR #778, issue #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
The mqtt-integration test suite still failed `state_messages_published_
on_snapshot_broadcast` after the timing fix (5ed8e3451) — but with a
new symptom: 'expected ON state, got []'. The subscriber captured ZERO
messages on the presence state topic.
Root cause: all three integration tests built `client_id` as
`ruview-int-test-<pid>` — the same string for every test in the
sequential cargo-test run. MQTT brokers default to "session takeover":
when a new connect arrives with the same client_id as an existing
session, mosquitto disconnects the old one immediately.
Sequence on CI (`--test-threads=1`):
1. discovery_topics_appear_on_broker connects (ruview-int-test-1234)
2. test passes; publisher task continues running in background
3. privacy_mode_suppresses_biometric_discovery connects (same id)
→ mosquitto kicks test 1's publisher mid-rumqttc-disconnect-handshake
4. state_messages_published_on_snapshot_broadcast connects (same id)
→ mosquitto kicks test 2's publisher
→ test 3's publisher in turn races with the broker's cleanup
and its first publishes may land in a half-cleaned session
→ state messages dropped silently
Fix: include a per-test label in the client_id
(`ruview-int-test-<pid>-<label>` — labels: "discovery", "privacy",
"state"). Each test gets its own MQTT session; no cross-test takeover.
Refs PR #778, issue #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Brings the new main (with ADR-110 merged, commit 00a234eda) into the
ADR-115 branch so PR #778 can be merge-able.
Conflicts:
CHANGELOG.md
Both branches added entries to the [Unreleased] → Added section.
Resolution: keep BOTH — the ADR-115 HA+Matter entry first
(front-facing, this branch's contribution), then the ADR-110
waves 1-5 entries from main (already merged, historical record).
No content lost.
docs/adr/ADR-115-home-assistant-integration.md
Add/add conflict — main got the file in its earlier shape
(Status: Proposed, Tracking issue: TBD) via the iter-17-19
cross-branch checkout incident on the adr-110 branch that ended
up merged via PR #764. This branch's version has the current
Accepted status and the real PR #778 link.
Resolution: take this branch's authoritative ADR-115 content.
The 3 .rs files I had flagged in PR #778 comment 4526344883
(lib.rs, esp32_parser.rs, tracker_bridge.rs) AUTO-MERGED cleanly —
this branch's local state already had the equivalent shape.
Verification: cargo check -p wifi-densepose-sensing-server
--no-default-features → green (5 warnings, 0 errors).
Co-Authored-By: claude-flow <ruv@ruv.net>
The module-level doc comment in matter/bridge.rs had a 4-space-indented
ASCII tree diagram. Rustdoc parses any 4-space-indented block in a doc
comment as a Rust code block (markdown indented-code-block syntax) and
runs it as a doctest. The tree text isn't valid Rust → doctest fails.
This broke the Rust Workspace Tests workflow on PR #778:
test crates/.../src/matter/bridge.rs - matter::bridge (line 6) ... FAILED
test result: FAILED. 0 passed; 1 failed
error: doctest failed, to rerun pass `-p ... --doc`
Wrapping the tree in a `text` fenced block tells rustdoc to render but
not compile it.
Verified locally:
cargo test -p wifi-densepose-sensing-server --no-default-features --doc
test result: ok. 0 passed; 0 failed; 1 ignored
Refs PR #778, issue #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
state_messages_published_on_snapshot_broadcast was failing under CI
(0/3 → 2/3 passes; only this one red). Root cause: the test waited
only 700ms after spawn(publisher) before sending the first
VitalsSnapshot through the broadcast channel, and used a 3s capture
window after a 200ms inter-snapshot delay.
What's actually happening on the wire during those 700ms:
1. rumqttc::AsyncClient::new() returns immediately (connection is
lazy — happens on first publish)
2. publisher::run() awaits publish_all_discovery() which issues 21+
QoS-1 publishes on the discovery prefix. Each is an ack-waited
round-trip — median ~800ms total on local loopback, easily
>2s on a fresh GH Actions runner with cold rustls.
3. After discovery, the run loop reaches its tokio::select! and
starts draining state_rx.
The test was sending broadcasts WHILE the publisher was still in
discovery, so the broadcast::Receiver buffer (capacity 32) was
draining without the publisher ever processing them — the publisher's
select! only polls state_rx between rumqttc events.
Fix:
- Wait 3s after spawn() (well past observed ramp-up, doubled for
CI variance)
- Send 6 snapshots in a loop with 200ms gaps (one dropped won't
tank the test)
- Capture window 8s instead of 3s (room for rate-limited publishes
to land)
Local impact: test now reliably passes against `mosquitto -c
allow_anonymous=true` on loopback in ~12s wall time. CI matrix should
pick the same green outcome.
Other two integration tests (discovery + privacy_mode) already passed
on every prior run — they only assert on discovery topics, which the
publisher emits before any state.
Refs PR #778, issue #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
The witness-bundle generator's `ADR_FILES` array was snapshotted at
P10 (commit a4f56d2f1). Since then we've shipped:
- 4 Matter source files (P7+P8a: mod.rs, clusters.rs, bridge.rs,
commissioning.rs)
- 4 ops/docs files (v0.7.0 release notes, benchmarks.md,
validate-esp32-mqtt.sh, validate-ha-blueprints.py)
- 9 blueprint files (README + 8 YAMLs under examples/ha-blueprints/)
- 4 Lovelace files (README + 3 dashboard YAMLs)
- Also fixed a typo where the manifest pointed at
`src/Cargo.toml` instead of the crate-level `Cargo.toml`.
After this commit the witness bundle's `manifest/source-hashes.txt`
covers **55 files** (was 28) — full source surface that ships with
v0.7.0. Bundle still self-verifies end-to-end via `bash VERIFY.sh`.
Local regen + verify on HEAD cb3ea9fbd:
bash scripts/witness-adr-115.sh
cd dist/witness-bundle-ADR115-*/ && bash VERIFY.sh
→ ADR-115 witness bundle: VERIFIED ✓
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
4 new proptest cases in matter::commissioning::tests. Each fuzzes
~256 random (passcode, discriminator) pairs per cargo-test run →
~1,024 additional commissioning-code trials per CI cycle.
## Invariants enforced under random sampling
- `manual_code_shape_invariants` — for ANY valid (passcode, disc) in
range and not in the §5.1.6.1 disallowed set, from_input MUST
produce: exactly 11 ASCII digits, Verhoeff self-consistent body+
check, 4-3-4 display form with dashes at positions 4 and 8.
- `disallowed_passcodes_always_rejected` — every passcode in the
§5.1.6.1 list MUST be rejected regardless of discriminator.
- `oversized_inputs_always_rejected` — passcode ≥ 2^27 OR
discriminator ≥ 2^12 MUST be rejected, regardless of the other
axis's value.
- `manual_code_deterministic_under_random_input` — same input always
produces same code (uses prop_assume to skip the spec-disallowed
passcodes since they'd Err out before getting to the code-equality
check).
The DISALLOWED_PASSCODES const is hoisted from the example-based
test for reuse across proptest cases.
Lib test count: 420 → 424. Effective ADR-115 fuzz coverage rises to
~3,584 fuzzed trials per CI run (1,280 wire-boundary + 1,280
semantic-bus + 1,024 commissioning).
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
5 new proptest cases in semantic:🚌:tests. Each runs ~256
iterations per cargo-test invocation → ~1,280 additional fuzzed
snapshot trials per CI run, throwing every variety of RawSnapshot
the bus can plausibly see at the 10-primitive FSM dispatch.
The `arb_snapshot()` Strategy generates RawSnapshots with:
- since_start ∈ 0..86400 s (covers warmup + 24h primitives)
- timestamp_ms full positive range
- motion deliberately ∈ -0.5..2.0 (out-of-range to test clamping)
- motion_energy ∈ -1000..10000
- breathing_rate_bpm ∈ Option<0..200>
- heart_rate_bpm ∈ Option<0..250>
- n_persons ∈ 0..10
- rssi_dbm ∈ Option<-120..0>
- vital_confidence ∈ 0..1
- local_seconds_since_midnight ∈ 0..86400 (covers bed_exit window
wrap-around test)
- active_zones ∈ random vec of [a-z]{3,8} strings
Strategy is split into two nested tuples because proptest only impls
Strategy for tuples up to length 12 (we have 13 fields).
Invariants enforced:
- `bus_tick_never_panics_on_arbitrary_snapshot` — every primitive
handles every plausible input without panic. Pathological cases
include motion=1.7, HR=Some(0.0), empty zones, NULs nowhere
(RawSnapshot doesn't carry those), and odd timestamp combinations.
- `bus_events_carry_node_id_and_ts` — no event ever emitted with
empty node_id; timestamp_ms exactly matches the input snapshot's.
- `boolean_states_always_have_reason_tags` — when `changed=true`,
the `reason.tags` MUST be non-empty. The explainability contract
is enforced at the bus boundary, not just where convenient.
- `per_tick_event_count_bounded_by_primitive_count` — bus emits ≤
10 events per tick (one per primitive). Catches double-emission
bugs where a future primitive accidentally fires twice.
- `replay_same_snapshot_is_deterministic_per_fresh_bus` — replaying
the same snapshot to N fresh buses produces the same event-kind
list every time. Catches uninitialised internal state.
Lib test count: 415 → 420 (each proptest function = 1 test slot but
fuzzes ~256 cases internally). Effective coverage rises to ~1,955
assertions per CI lib run.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
## Status flip — ADR-115 §Status
Per maintainer ACK (#776 issue body + 13 ACK'd open questions) and the
shipped implementation in PR #778 (410 lib tests, witness bundle
VERIFIED), the MQTT track is now Accepted. The Matter SDK wiring P8b
remains Proposed pending the §9.10 deferral to v0.7.1.
ADR header table updated:
- Status: "**Accepted** (MQTT track P1-P7 + P8a + P9 + P10 shipped
2026-05-23 in PR #778, 410 lib tests, witness bundle VERIFIED) /
**Proposed** (Matter SDK wiring P8b deferred to v0.7.1 per §9.10)"
- Codename: HA-DISCO (MQTT) + HA-FABRIC (Matter) + **HA-MIND** (semantic
primitives) — the third codename always belonged in the masthead.
- Tracking issue: now points at #776 + PR #778
`docs/adr/README.md` ADR index gets an ADR-115 row in the
"Platform and UI" section with the same Accepted/Proposed split.
## Property-based fuzzing — mqtt::security
Added 5 proptest cases (each runs ~256 iterations per cargo-test
invocation, so ~1280 additional assertions per CI run):
- topic_segment_rejects_anything_with_wildcards_or_separators —
random Unicode prefix/suffix + an injected '+', '#', NUL, or '/'
MUST be rejected
- topic_segment_accepts_safe_alphabet — any string built solely from
the safe alphabet MUST be accepted
- topic_segment_always_rejects_empty — invariant across seeds
- payload_size_check_is_monotonic — every size ≤ MAX is OK, every
size > MAX errors with the exact size
- path_safety_rejects_nul_or_newline_anywhere — NUL/newline at any
offset in the path MUST be rejected
`proptest` 1.5 added as dev-dep with default features off (no
proptest-derive needed). ~3 transitive crates added, dev-only.
Total lib tests: 410 → 415 passed, 0 failed, 1 properly ignored.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Drop-in Lovelace dashboard YAMLs covering the three common ADR-115
deployment shapes. Paste into HA's raw config editor, rename the
`binary_sensor.ruview_<room>_*` entity IDs to match what HA
auto-discovered, done.
| File | Use case |
|-------------------------------------|-----------------------------------|
| 01-single-room-overview.yaml | One node, full 21-entity surface |
| 02-multi-node-grid.yaml | 3+ nodes (whole-house) |
| 03-healthcare-aal-view.yaml | Care-giver dashboard, --privacy-mode-safe |
## Single-room overview
- Three top tiles: presence / sleeping / room active
- Glance card with HR / BR / motion / persons / RSSI
- Gauge for fall_risk_elevated with green<40<yellow<70<red
- Safety entities card (distress / no_movement / inactivity anomaly)
- 6h history graph of HR + BR
- 24h logbook of fall / bed_exit / multi_room events
## Multi-node grid
- Top markdown header
- 2-column grid of per-room presence tiles with navigation actions
drilling into per-room dashboards
- Glance card showing per-room person counts
- 24h logbook of semantic events across the house
## Healthcare / AAL
- **Privacy-mode-compatible** — binds only to semantic primitives, no
raw HR/BR/pose on the dashboard surface. Carer-app-friendly.
- Six tiles: sleeping / room-active / bathroom (top row) +
distress / inactivity-anomaly / no-movement (bottom row)
- Gauge for fall_risk_elevated
- 24h logbook of safety-relevant events
- Last-presence-change timestamp card
## README + privacy-mode coverage
`examples/lovelace/README.md` documents how to rename auto-discovered
entity IDs (either via HA's entity-rename UI or via the NVS-only
node_friendly_name field per ADR §9.6) and explains why dashboard 3
remains useful under --privacy-mode (inferred states still publish,
biometric values don't).
All three files validate as well-formed YAML with `title:` + `cards:`
under PyYAML.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Per ADR-115 §9.4 (maintainer ACK on #776), v0.7.0 ships **3 starter
blueprints**. This commit goes further: all **8** of the catalog
proposed in §3.12.2 land as standalone YAML files under
`examples/ha-blueprints/`, ready to import into HA.
## Blueprints
1. Notify on possible distress → possible_distress
2. Dim hallway when sleeping → someone_sleeping
3. Wake routine on bed exit → bed_exit (time-window-gated)
4. Alert on elderly inactivity → elderly_inactivity_anomaly
(with optional escalation chain)
5. Meeting lights + presence mode → meeting_in_progress
(activates a HA scene)
6. Bathroom fan while occupied → bathroom_occupied
(privacy-mode-safe; zone-derived)
7. Escalate on fall-risk crossing → fall_risk_elevated
(numeric_state trigger)
8. Auto-arm security when not active → group(room_active) + no_movement
(composed; multi-room sense)
Each blueprint:
- Uses HA's blueprint schema (https://www.home-assistant.io/docs/blueprint/schema/)
- Declares typed `selector:` for every input (entity-domain-constrained
where applicable)
- Carries a `source_url` for HACS-style re-import
- Includes `mode: single` + `max_exceeded: silent` where appropriate
so transient retriggers don't spam
- Includes a `cooldown_minutes` / `confirm_minutes` / `ack_timeout_min`
parameter where time-debouncing matters
## Validator (`scripts/validate-ha-blueprints.py`)
Pure-Python validator that:
- Registers no-op constructors for HA's `!input` and `!secret` YAML tags
(PyYAML doesn't know them)
- Asserts every file has a top-level `blueprint:` mapping with
`name`/`description`/`domain`
- Asserts `domain` is `automation` or `script`
- Asserts at least one declared `input`
- Asserts at least one of `trigger`/`action`/`sequence` is present
Exits 0 only when all 8 validate. Local run:
python scripts/validate-ha-blueprints.py
All 8 HA Blueprints validate OK
## CI integration
`.github/workflows/mqtt-integration.yml` gains a new
`Validate HA Blueprints` step that runs the Python validator before
the cargo test phases — fails the workflow on any malformed blueprint
in a PR.
## Privacy-mode coverage table
5 of 8 blueprints are unconditionally privacy-mode-safe (no biometric
dependency in the state derivation). The other 3 depend on inferred
states that themselves derive from biometrics — the inferred state
still publishes under `--privacy-mode` (per ADR §3.12.3) but the
operator should audit the use case in regulated contexts. Full table
in `examples/ha-blueprints/README.md`.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds the ambient-intelligence-platform positioning to the top of the
README, right under the hero tagline. Four standard shields.io badges
the smart-home community immediately recognises, plus a one-paragraph
explainer covering the 21 entities + 3 starter blueprints.
Pairs the technical implementation (PR #778) with the marketing surface
that turns README visitors into HA users. Matches the project
positioning saved to memory [[project-ruview-positioning]].
Refs #776, PR #778.
## scripts/validate-esp32-mqtt.sh — proof-of-life against real hardware
Standalone bash harness that asserts the full pipeline works
end-to-end with an attached ESP32-S3:
ESP32-S3 CSI source → sensing-server → MQTT broker → captured
topics → coverage matrix → report → exit 0 / non-zero
Five phases:
1. Pre-flight (mosquitto-clients on PATH, cargo on PATH; starts an
inline mosquitto if no broker reachable)
2. Start sensing-server with --source esp32 --mqtt (uses the
example binary that landed in P6)
3. Capture mosquitto_sub traffic for --duration seconds
4. Assert coverage matrix: 16 expected HA discovery topics (raw +
semantic primitives) MUST appear; ≥1 state message MUST land
5. Write a Markdown report under --report path
Exit codes:
0 — all assertions passed
2 — bad CLI args
3 — missing prereq (mosquitto_pub, cargo)
4 — no broker reachable AND no mosquitto binary to start one
5 — sensing-server died on startup (log tail in report)
6 — coverage assertions failed (details in report)
The script is **runnable without hardware** (will time out cleanly
with state-message-count=0); attach a real ESP32 to get a full PASS.
Default port: 127.0.0.1:11883 + 60 s capture window.
Usage:
bash scripts/validate-esp32-mqtt.sh \
--duration 60 \
--broker 127.0.0.1:11883 \
--source esp32 \
--report dist/validation-esp32-<sha>.txt
## scripts/witness-adr-115.sh — integration
Two changes:
1. Always copy `docs/integrations/benchmarks.md` into the bundle's
`bench-results/` dir so the bench numbers travel with the bundle
even when `RUVIEW_RUN_BENCH=0` (the captured numbers from
`ca10df7b0` are still load-bearing).
2. New `RUVIEW_RUN_ESP32=1` opt-in path that runs the validation
harness above and bakes the report into the bundle as
`esp32-validation.md`. Without the env var, a placeholder note
explains how to opt in.
Both scripts pass `bash -n` syntax check on Windows Git Bash.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
The mqtt-integration workflow's first cargo-test step was passing three
filters in one invocation:
cargo test ... --lib mqtt:: semantic:: cli::tests
cargo test treats positional args after --lib as ONE TESTNAME and
errored with 'unexpected argument semantic::'. Running the whole --lib
suite is strictly more thorough anyway (all 410 tests instead of just
the ADR-115 subset), so dropping the filter is the right fix.
Refs PR #778.
## Two CI failures on PR #778 fixed
### 1. Rust Workspace Tests (E0601: `main` not found in mqtt_publisher)
Default `cargo build --workspace` compiles examples without forwarding
`--features mqtt`. The example had a crate-level `#![cfg(feature =
"mqtt")]` so the entire file evaporated, leaving zero `main`. Now
provides a stub `main` when the feature is off (prints a hint and
exits 2), and gates the real implementation behind `#[cfg(feature =
"mqtt")]` per-item.
Local verification:
cargo check --no-default-features --examples → clean
### 2. mqtt-integration (mosquitto never became reachable)
`eclipse-mosquitto:2.x` rejects anonymous connections by default and
GH Actions `services:` containers don't easily support volume-mounting
a custom config. Removed the service container and start mosquitto
manually in a step with an inline `allow_anonymous true` listener on
port 11883. Same wire shape, no auth (CI tests protocol behaviour,
not security — production uses mTLS per ADR §3.9).
## Benchmark numbers captured (`docs/integrations/benchmarks.md`)
Ran `cargo bench --features mqtt --bench mqtt_throughput` locally:
| Hot path | Measured | Target | Better by |
|---------------------------------------|----------|--------|-----------|
| state::event_fall encode | 259 ns | <2 µs | 7.7× |
| rate_limiter::allow_first | 49.7 ns | <100 ns| 2× |
| rate_limiter::allow_within_gap | 62.1 ns | <100 ns| 1.6× |
| privacy::decide_hr_strip | 0.24 ns | <50 ns | 208× |
| privacy::decide_presence_keep | 0.24 ns | <50 ns | 208× |
| semantic::bus_tick_all_10_primitives | 717 ns | <10 µs | 14× |
At 1 Hz publish rate per node, the entire ADR-115 hot path costs
~1 µs per node per tick on commodity hardware. A Cognitum Seed
hosting 100 nodes would burn 100 µs/sec — 0.01% load floor. Memory:
~30 KB total FSM state for 10 primitives × 100 nodes.
The numbers exceed every target by ≥1.6×, several by 100×+. No need
to optimise further for v0.7.0.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Ships the SDK-independent half of the Matter Bridge production work:
## `matter::bridge` — endpoint tree assembly
`build_bridge_tree(nodes) -> BridgeTree` walks a list of `(node_id,
friendly_name, [EntityKind])` tuples and produces the Matter endpoint
graph the SDK will materialise:
EP 0 (BridgedDevicesAggregator)
EP 1 (BridgedNode for "Bedroom")
EP 2 (OccupancySensor for Presence + PersonCount vendor attr)
EP 3 (OccupancySensor for SomeoneSleeping)
EP 4 (GenericSwitch for FallDetected)
EP 5 (BridgedNode for "Living") …
Key invariants enforced by tests:
- `PersonCount` collapses onto Presence's endpoint as a vendor
attribute, never gets its own endpoint
- Biometric entities (HR/BR/pose) are skipped entirely — they
never appear in the tree
- Every child endpoint carries `BasicInformation` cluster
- Endpoint IDs are monotonic + unique (verified by sort+dedup test)
- Empty node list yields just the root aggregator
- Multi-node bridges keep per-node endpoint isolation
- `endpoint(id)` lookup resolves every assigned ID
## `matter::commissioning` — setup-code generation
`SetupCodeInput::dev(passcode, discriminator) -> ManualPairingCode`
produces the 11-digit human-readable Matter pairing code that users
scan/enter into Apple Home / Google Home / HA Matter integration.
Validates against Matter Core Spec §5.1.6.1 disallowed-values list
(11111111, 12345678, 87654321, all-same-digit patterns, 0). Rejects
oversized passcode (≥2^27) and discriminator (≥2^12).
The Verhoeff check digit is computed per spec §5.1.4.1.5 — full
D/P/INV tables transcribed. The check digit appended to the body is
self-consistent (verified by a recompute-and-compare test).
`ManualPairingCode::display_4_3_4()` returns the dashed form
(`1234-567-8901`) controllers actually display.
Bit-packing is a placeholder for v0.7.0 — the chunk values are
hashed-then-mod into their decimal widths so the output is
deterministic + input-sensitive + Verhoeff-valid, but not yet
bit-perfect spec-compliant. The fully spec-compliant code (with QR
base-38 payload) lands at P8b when `rs-matter` is integrated; see
ADR-115 §9.10. This module gives the SDK layer a stable testable
contract to build against.
## Tests
- 16 cluster mapping (existing)
- 11 bridge assembly (new): aggregator root, branch-per-node,
PersonCount collapsing, HR/BR skip, BasicInformation cluster on
every endpoint, monotonic+unique IDs, total endpoint count, lookup,
multi-node isolation, empty-node list
- 11 commissioning (new): dev VID/PID defaults, disallowed-passcode
rejection (12 spec values), oversized-passcode rejection,
oversized-discriminator rejection, canonical test vectors accepted,
11-digit code always, 4-3-4 display format, determinism, sensitivity
to passcode change, sensitivity to discriminator change, Verhoeff
self-consistency, invalid-input early return
Total lib tests: **410 passed**, 0 failed, 1 properly ignored.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
Ships the **Matter cluster + device-type mapping table** as pure Rust
types independent of any specific Matter SDK. SDK choice between
`matter-rs` and chip-tool FFI per ADR-115 §9.10 lands in P8 once
spike-validated against real controllers; this commit gives the SDK
work a stable mapping target to build against.
## What this lands
- `matter::clusters` module:
- Spec-defined constants: `CLUSTER_OCCUPANCY_SENSING` (0x0406),
`CLUSTER_SWITCH` (0x003B), `CLUSTER_BOOLEAN_STATE` (0x0045),
`CLUSTER_BRIDGED_DEVICE_BASIC_INFORMATION` (0x0039),
`DEVICE_TYPE_OCCUPANCY_SENSOR` (0x0107),
`DEVICE_TYPE_GENERIC_SWITCH` (0x000F),
`DEVICE_TYPE_AGGREGATOR` (0x000E),
`DEVICE_TYPE_BRIDGED_NODE` (0x0013),
`VENDOR_ATTR_PERSON_COUNT` (0xFFF1_0001),
`EVENT_SWITCH_MULTI_PRESS_COMPLETE` (0x06).
Values transcribed from Matter Core Spec 1.3 §A.1 + Device Library 1.3.
- `matter_mapping(EntityKind) -> Option<MatterClusterMapping>` —
single source of truth implementing ADR §3.11.1:
* Presence / zones / sleeping / room-active / meeting / bathroom
→ OccupancySensing on OccupancySensor endpoints
* Fall / bed-exit / multi-room → Switch.MultiPressComplete events
on GenericSwitch endpoints
* Distress / elderly-anomaly / no-movement → BooleanState (NOT
occupancy — keeps controllers from binding motion-light scenes
to safety alerts)
* Person count → vendor-extension attribute on shared OccupancySensor
* Fall-risk score → vendor attribute on BridgedNode endpoint
* HR / BR / pose / motion-level / motion-energy / presence-score /
RSSI → explicit `None` (no Matter cluster represents them, stay
MQTT-only per §3.11.4)
- `entity_on_matter` + `next_endpoint` helpers.
## Tests (16/16 pass, lib total now 388)
- per-entity mapping correctness for every category (occupancy /
switch event / boolean state / vendor extension / explicitly None)
- distinction between presence (OccupancySensing) and distress
(BooleanState) — critical so controllers don't bind motion scenes to
safety alerts
- `someone_sleeping` lives on its own occupancy endpoint (NOT shared
with raw presence) so controllers can wire scenes independently
- biometric channels (HR / BR / pose) explicitly verified to have
`None` mapping — they NEVER reach Matter
- exhaustiveness canary: every `EntityKind` variant hit so adding a
new variant fails the test until the matter table is updated
- spec-ID sanity: cluster IDs match Matter 1.3 published values
## Why scaffolding-first
Per maintainer decision principle (§9): preserve clean protocols,
avoid fake semantics, ship MQTT first, validate Matter second. This
module locks in the cluster mapping table now so when P8 wires
`rs-matter` (or chip-tool FFI fallback), the wire surface is already
defined and tested — only the SDK calls change, not the protocol
contract.
P8 (Matter Bridge production using matter-rs) and P9 (multi-controller
validation against Apple Home / Google Home / HA) remain on the v0.7.1
docket per §9.10.
Refs #776, PR #778.
Co-Authored-By: claude-flow <ruv@ruv.net>
## Security audit (`mqtt::security`)
New module enforcing the ADR-115 §3.9 / §7 wire-level invariants as
pure functions, callable from both the publisher hot path and the
unit-test suite:
- **Topic safety** — reject `+`, `#`, `\0`, `/` in segment-level
identifiers (node_id, client_id, zone tag). Prevents a malicious
upstream payload from injecting MQTT wildcards that would corrupt
subscription semantics.
- **Path safety** — reject NUL / newline in TLS cert / CA paths.
- **Payload-size cap** — 32 KB hard limit per publish, well below
broker defaults (most brokers cap at 256 KB). Lets the publisher
drop oversized payloads with a WARN instead of crashing.
- **Credential hygiene** — `password_via_env_only` is a canary: if
the CLI ever grows an inline `--mqtt-password` flag, this test
fails on purpose. Today we only accept `--mqtt-password-env <VAR>`.
- **STRICT_TLS upgrade** — `RUVIEW_MQTT_STRICT_TLS=1` promotes the
`PlaintextOnPublicHost` advisory from `MqttConfig::validate` to
fatal. This is the planned v0.8.0 default per ADR §9.5.
- **Discovery prefix sanity** — rejects non-alphanumeric prefixes
outside [_-/], so a malformed `--mqtt-prefix` can't escape the HA
topic namespace.
15 unit tests (mqtt::security) covering every invariant + 1
properly-`#[ignore]`d test for the env-mutating STRICT_TLS path.
## Criterion benchmarks (`benches/mqtt_throughput.rs`)
Micro-benchmarks for the MQTT + semantic hot paths:
- discovery payload generation (presence / heart_rate / fall event)
- state encoders (boolean / numeric / event)
- rate-limiter `allow()` decisions (first sample + within-gap)
- privacy `decide()` (strip HR vs keep presence)
- full bus tick across all 10 semantic primitives
Bench targets (laptop-class release build):
- discovery payload: <5 µs state encode: <2 µs
- rate limit: <100 ns privacy decide: <50 ns
- bus tick (10 prim): <10 µs
Run with `cargo bench -p wifi-densepose-sensing-server --bench
mqtt_throughput --features mqtt`. Numbers will be captured into the
witness bundle in P10.
`criterion` 0.5 added as dev-dep. `[[bench]] required-features = ["mqtt"]`
so default `cargo bench --workspace` doesn't try to build it without
rumqttc.
Lib test count: **372 passed** (357 → 372, +15 security tests).
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds three integration tests (`v2/crates/wifi-densepose-sensing-server/
tests/mqtt_integration.rs`) that prove the publisher works against a
real broker, gated behind `--features mqtt` + `RUVIEW_RUN_INTEGRATION=1`:
1. `discovery_topics_appear_on_broker` — spawn the publisher, subscribe
`homeassistant/#` with rumqttc, drain for 6s, assert that presence/
heart_rate/fall discovery config topics all landed with the exact
JSON shape (device_class, payload_on/off, unique_id namespace).
2. `privacy_mode_suppresses_biometric_discovery` — with
`privacy_mode=true`, biometric topics (heart_rate, breathing_rate,
pose) must NEVER appear on the wire. Semantic primitives
(someone_sleeping, etc) MUST still appear — they're inferred
states, not biometric values, per ADR-115 §3.12.3.
3. `state_messages_published_on_snapshot_broadcast` — push a
VitalsSnapshot through the broadcast channel, assert ON/OFF state
messages reach the broker.
Plus `.github/workflows/mqtt-integration.yml` — spins up Mosquitto
2.0.18 as a GH Actions service container, waits for it via
`mosquitto_pub` health probe, runs both the lib unit suite under
`--features mqtt` and the integration suite. Dumps broker logs on
failure for debugging.
Tests are SKIPPED locally unless `RUVIEW_RUN_INTEGRATION=1` is set —
default `cargo test --workspace` stays fast for developers.
Fixed an unused-import warning in `semantic::bus` (gated `Reason`
behind `#[cfg(test)]`).
Lib test count now: 357 passed across the crate (cli 6 + mqtt 45 +
semantic 66 + everything else 240 — all green under
`cargo test --no-default-features --lib`).
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Two new files under docs/integrations/:
- `home-assistant.md` (~340 lines) — operator guide for both protocols:
* Quick start (Docker + cargo)
* Entity reference (11 raw + 10 semantic, Matter device-type mapping)
* Complete CLI matrix (every --mqtt-*, --matter-*, --semantic-* flag)
* Zone-tag YAML format + threshold-override format
* Privacy mode contract (HR/BR/pose stripped; semantic primitives preserved)
* Three starter HA Blueprints per §9.4 maintainer ACK:
1. Notify on possible distress
2. Dim hallway when someone sleeping
3. Wake-up routine on bed exit
* Lovelace dashboard examples (single-room + multi-node grid)
* Advanced brokers (EMQX, VerneMQ, HiveMQ Edge)
* Troubleshooting recipe matrix
- `semantic-primitives-metrics.md` (~120 lines) — per-primitive
precision/recall reference + methodology for reproducing numbers
+ failure-mode catalogue (v1 → v2 deltas) + threshold-tuning notes.
Numbers grounded in the 1,077-sample ADR-079 paired-capture held-out
subset. Open-set caveats explicitly listed.
README.md Documentation section gets two new rows pointing at the
guides plus a "Works with Home Assistant" + "Works with Matter"
positioning line — matches the ambient-intelligence-platform pitch
[[project-ruview-positioning]].
User guide untouched in this commit; will be updated in P6 once the
release lands with concrete version numbers.
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Lands the remaining six §3.12 v1 primitives:
- `distress` (PossibleDistress) — EWMA baseline HR + 1.5× multiplier
+ agitated motion + no-fall + 60 s dwell → ON. Refractory 5 min
after exit. Baseline only updates when NOT active AND NOT in
candidate-distress state (low motion, HR near baseline) so a
sustained elevated HR doesn't drift the baseline up before the
dwell completes — without this guard the test would never fire.
- `elderly_anomaly` (ElderlyInactivityAnomaly) — current idle stretch
> 2× longest-observed-idle baseline. Baseline floor at 30 min so
the first day doesn't fire spuriously. 24 h refractory per resident.
- `meeting` (MeetingInProgress) — n_persons ≥ 2 + low-amplitude motion
(1–20%) + 10 min dwell → ON. 2 min exit dwell on count drop.
- `fall_risk` (FallRiskElevated) — 0–100 continuous score from
near-fall count in trailing 24 h + recent motion variance. Emits
Scalar every tick; emits Event on upward threshold crossing
(default 70).
- `bed_exit` (BedExit) — edge-triggered event: was in bed_zone, now
not, between 22:00 and 06:00 local (wrap-around window honoured).
- `multi_room` (MultiRoomTransition) — edge-triggered event: zone
exit + different zone enter within 10 s gap. Reason payload carries
from/to zone tags so HA automations can route paths.
Bus wired to dispatch all 10 primitives; `SemanticKind` enum expanded
to match. `tick()` returns up to 10 events per snapshot.
32 new tests (66 semantic + 45 mqtt + 6 cli = **117 total**):
- distress (7): does-not-fire-with-normal-HR, fires-on-sustained-
elevated-HR-with-motion, does-not-fire-during-fall, exits-when-
motion-calms-and-HR-normalises, refractory-blocks-immediate-refire,
refire-allowed-after-refractory, baseline-does-not-track-during-
active.
- elderly_anomaly (5): fires-when-idle-exceeds-2x-baseline, does-not-
fire-before-threshold, motion-clears-active-state, baseline-grows-
to-observed-max, refractory-prevents-repeat-alerts.
- meeting (4): fires-after-dwell-with-2+, does-not-fire-with-1-
person, does-not-fire-with-high-motion, exits-after-2-min-of-low-
count.
- fall_risk (5): warmup-blocks, emits-scalar-when-active, score-
grows-with-falls, emits-event-when-crossing-threshold, fall-
history-evicts-after-24h.
- bed_exit (6): fires-on-bed-to-non-bed-overnight, does-not-fire-
during-day, does-not-fire-without-prior-in-bed, warmup-blocks,
does-not-fire-when-bed-zones-unconfigured, fires-just-after-
midnight-window-start.
- multi_room (5): fires-when-zone-changes-quickly, does-not-fire-
after-long-gap, does-not-fire-on-same-zone-re-entry, warmup-blocks,
handles-simultaneous-zone-swap.
ADR-115 §3.12 inference layer now complete. Each primitive has
warmup, hysteresis, explainability tags, configurable thresholds.
Adding a v2 primitive is one file + one bus entry.
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
ADR-115 §3.12 keystone. Raw signals are not the product — customers want
first-class entities like `binary_sensor.bedroom_someone_sleeping`, not a
Node-RED flow that thresholds breathing rate at night. This commit lands
the inference layer that turns the broadcast channel into 10 v1 semantic
primitives, starting with the 4 highest-leverage ones.
Modules:
- `semantic::common` — `RawSnapshot` projection, `PrimitiveState`,
`PrimitiveConfig` (thresholds matching the v1
catalog in ADR §3.12), `in_window` for time-gated
primitives, `Reason` explainability struct.
- `semantic::sleeping` — SomeoneSleeping FSM: presence + motion<5%
+ BR ∈ [8,20] bpm + 5min dwell. Exit on
presence-drop (immediate) or motion>15%
for 30s.
- `semantic::room_active` — motion >10% in 30s window → ON. Exit on
presence-drop or 10min idle.
- `semantic::bathroom` — presence + zone tagged as bathroom. Safe
in privacy mode (no biometrics in the
derivation).
- `semantic::no_movement` — presence + motion<1% for 30min → ON.
Safety-check primitive for aging-in-place.
- `semantic::bus` — single dispatch that runs all primitives
on each `RawSnapshot`, returns a list of
`SemanticEvent`s for MQTT+Matter publish.
Every primitive has:
- Warmup suppression (60s default, §3.12.4)
- Hysteresis (enter + exit thresholds different)
- Explainability via `Reason::new(&["motion<5%", "br=12bpm", ...])`
- Configurable thresholds via `PrimitiveConfig`
Test coverage (34 tests, all passing under `--no-default-features`):
- common: in_window simple + wrap-around midnight, default thresholds
match ADR catalog, Reason struct.
- sleeping (7 tests): warmup blocks, fires after dwell, no-fire on high
motion, no-fire on BR out of range, exits on presence-drop immediately,
exits on sustained motion only after 30s, brief blip does not exit.
- room_active (6 tests): warmup, fires on high+presence, no-fire without
presence, no-fire below threshold, exits on presence-drop, exits on
extended idle.
- bathroom (5 tests): fires on zone match, ignores other zones, requires
presence, warmup blocks, emits OFF on zone exit.
- no_movement (4 tests): fires after dwell, no-fire with motion, brief
motion resets timer, exits on motion.
- bus (6 tests): empty during warmup, emits room_active, emits bathroom,
multiple simultaneous primitives, event carries node_id+ts, reason
populated for HA debug.
Total cargo test count now:
cli: 6 + mqtt: 45 + semantic: 34 = 85 tests passing
P4.5b (next iteration) lands the remaining 6 primitives: distress
(HR multiple over baseline), elderly_anomaly (long-window inactivity),
meeting (multi-person dwell), fall_risk (gait instability score),
bed_exit (sleeping → presence-out between 22:00-06:00),
multi_room (track_id continuous across zones).
Refs #776.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds `mqtt` and `matter` Cargo features (default off) plus 20+ new CLI
flags wired through cli.rs per ADR-115 §3.8 / §3.10 / §3.11 / §3.12:
- MQTT (HA-DISCO): --mqtt, --mqtt-host/--mqtt-port/--mqtt-username/
--mqtt-password-env/--mqtt-client-id/--mqtt-prefix, TLS controls
(--mqtt-tls/--mqtt-ca-file/--mqtt-client-cert/--mqtt-client-key),
rate controls (--mqtt-refresh-secs, --mqtt-rate-{vitals,motion,count,
rssi,pose}, --mqtt-publish-pose).
- Privacy (ADR-106): --privacy-mode strips HR/BR/pose pre-publish.
- Matter (HA-FABRIC): --matter, --matter-setup-file, --matter-reset,
--matter-vendor-id (dev VID 0xFFF1 per §9.9), --matter-product-id.
- Semantic (HA-MIND): --semantic (default ON), thresholds/zones files,
--semantic-baseline-window-days, --no-semantic <PRIMITIVE> repeatable.
rumqttc 0.24 added as optional dep with rustls (Windows-friendly parity
with ureq in this crate). matter-rs deferred to P7 spike per §9.10.
6 unit tests cover defaults, compound flag composition, and repeatable
--no-semantic. Tests pass:
cargo test -p wifi-densepose-sensing-server --no-default-features cli::tests
6 passed; 0 failed.
Branch coordination: this work is on feat/adr-115-ha-mqtt-matter off
main, parallel to ADR-110 work on adr-110-esp32c6 (no file overlap).
Refs #776 (ADR-115 implementation tracking issue).
Co-Authored-By: claude-flow <ruv@ruv.net>
ADR-115 lands the dual-protocol HA integration design:
- MQTT auto-discovery (HA-DISCO) carrying full RuView telemetry
- Matter Bridge (HA-FABRIC) carrying the standardised subset across
Apple Home / Google Home / Alexa / SmartThings / HA
- Semantic Automation Primitives (HA-MIND) — 10 v1 inferred states
(someone-sleeping, possible-distress, room-active, elderly-anomaly,
meeting-in-progress, bathroom-occupied, fall-risk-elevated, bed-exit,
no-movement, multi-room-transition) that turn raw signals into HA
entities, Matter events, and Apple Home scene triggers — the inference
layer that moves RuView from "RF sensing" to "ambient intelligence
infrastructure". All 13 §9 open questions ACK'd by maintainer.
P1 (this commit) — `mqtt` and `matter` Cargo features (default off) +
20+ new CLI flags wired through cli.rs:
- --mqtt / --mqtt-host / --mqtt-port / --mqtt-username /
--mqtt-password-env / --mqtt-client-id / --mqtt-prefix /
--mqtt-tls / --mqtt-ca-file / --mqtt-client-cert / --mqtt-client-key
- --mqtt-refresh-secs / --mqtt-rate-{vitals,motion,count,rssi,pose} /
--mqtt-publish-pose
- --privacy-mode (ADR-106 primitive-isolation contract)
- --matter / --matter-setup-file / --matter-reset /
--matter-vendor-id (dev VID 0xFFF1 per §9.9) / --matter-product-id
- --semantic (default ON) / --semantic-thresholds-file /
--semantic-zones-file / --semantic-baseline-window-days /
--no-semantic <PRIMITIVE> (repeatable)
6 unit tests cover: defaults safe (mqtt off, vid=0xFFF1, semantic on),
compound flag composition, repeatable --no-semantic. All pass:
cargo test -p wifi-densepose-sensing-server --no-default-features cli::tests
test result: ok. 6 passed; 0 failed.
rumqttc 0.24 added as optional dep (gated behind `mqtt` feature) with
rustls instead of openssl for Windows parity with the rest of the
workspace. matter-rs intentionally absent until P7 spike validates the
SDK choice (§9.10).
Coordinates with ADR-110 work (different branch, different files).
This branch is feat/adr-115-ha-mqtt-matter off main. ADR-110 work
continues on adr-110-esp32c6.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ui): unbreak viz.html — OrbitControls importmap, WS URL, toast NPE (#760)
Three independent bugs were stacking to make ui/viz.html unusable from `main`:
1. Three.js r160 removed `examples/js/OrbitControls.js`, so the script-tag
load 404'd and `new THREE.OrbitControls(...)` threw. Switch to an
importmap that pulls the ES module build, then re-expose
`window.THREE` and `THREE.OrbitControls` so the existing component
modules (scene.js, body-model.js, …) keep working without a wider
refactor.
2. The WebSocket client was hardcoded to `ws://localhost:8000/ws/pose`,
but the sensing-server listens on `--ws-port` (8765 default, 3001 in
the Docker image) at `/ws/sensing`. Reuse the existing
`buildSensingWsUrl()` helper from `sensing.service.js` so port
pairings are handled centrally, and add a `?ws=…` query-string
override for non-standard setups. The websocket-client.js default is
also updated to derive from `window.location` instead of the dead
`:8000/ws/pose` literal.
3. `ToastManager.show()` called `this.container.appendChild(...)` even
when `init()` had never been called, throwing a TypeError that
killed the rest of page initialization. Auto-init the container
lazily on first show (patch from issue reporter).
Closes#760.
Co-Authored-By: claude-flow <ruv@ruv.net>
* fix(ui): single module script + mutable THREE — OrbitControls validated
Browser validation against the previous commit caught two stacked issues:
1. `import * as THREE from 'three'` returns a frozen Module Namespace
Object — assignment `THREE.OrbitControls = OrbitControls` silently
no-ops, so the global never gets the OrbitControls reference.
2. Two separate `<script type="module">` blocks (one installing the
THREE global, one consuming it via Scene) are independently
async-resolved. The second can finish dependency loading first and
call `new THREE.OrbitControls(...)` before the first script has run.
Fixed by spreading the namespace into a plain mutable object and merging
all initialization into a single module script with `await import()` for
component modules. Order is now strictly: import THREE → install
window.THREE → import components → run init().
Validated via agent-browser: page logs `[VIZ] Initialization complete`,
WebSocket targets the correct `ws://127.0.0.1:3001/ws/sensing` endpoint
(derived from buildSensingWsUrl), toast lazy-init confirmed via eval.
Co-Authored-By: claude-flow <ruv@ruv.net>
PR #744 moved the files into 9 thematic folders via git mv but missed
the READMEs due to a working-directory issue with git add. This PR
adds the actual READMEs:
- examples/research-sota/README.md (main overview)
- examples/research-sota/01-physics-floor/README.md
- examples/research-sota/02-placement/README.md
- examples/research-sota/03-spatial-intelligence/README.md
- examples/research-sota/04-rssi/README.md
- examples/research-sota/05-cross-room-reid/README.md
- examples/research-sota/06-structure-detection/README.md
- examples/research-sota/07-negative-results/README.md
- examples/research-sota/08-verticals/README.md
- examples/research-sota/09-quantum-fusion/README.md
Each sub-README documents:
- Scripts + headlines table
- Why this folder bounds/composes with others
- Sample output / honest scope
- Cross-references to related loop notes + ADRs
Main README covers:
- Folder map with thread numbers
- Cross-folder dependency graph
- 8-entry headline findings table
- Reading order for newcomers (4 scripts in suggested order)
- Honest scope (synthetic-physics caveats)
Eighth exotic vertical. Recovers what R13 NEGATIVE physically excluded.
Demonstrates the loop's architecture is SENSOR-AGNOSTIC — same primitives
work with classical CSI today and quantum sensors in 5-20y.
User-prompted: opened docs/research/quantum-sensing/11-quantum-level-
sensors.md indicating quantum-integration interest. Repo already has
nvsim (NV-diamond magnetometer simulator, ADR-089) as a standalone
leaf crate.
Four quantum modalities catalogued:
- NV-diamond magnetometer (1 pT/sqrt(Hz), 5-10y edge)
- Atomic clock (10^-15 stability, 5-10y edge)
- SQUID magnetometer (1 fT/sqrt(Hz), 15-20y if room-temp possible)
- Quantum-illuminated radar (+6 dB SNR, 15-20y edge)
Classical vs quantum loop primitive comparison:
- Breathing rate: +-1 BPM -> +-0.1 BPM (10x)
- HR rate: +-5 BPM -> +-0.5 BPM (10x)
- HRV contour: NOT possible (R13) -> NV-magnetometer enables it
- BP: NOT possible (R13) -> atomic-ToA PWV enables it
- Position precision: 25 cm -> 3 mm (80x)
- Multi-scatterer penalty: 4.7 dB -> 1 dB (3.7 dB recovery)
- Through-rubble: 2 m -> 5 m+ (2.5x)
WHAT R13 NEGATIVE NO LONGER RULES OUT WITH QUANTUM:
R13 ruled out HRV contour + BP from CSI due to 5 dB SNR shortfall.
NV-diamond cardiac magnetometry resolves this — heart magnetic fields
(~50 pT) detectable, contour-preserving, penetrates clothing/rubble.
The 5 dB R13 shortfall was SENSOR-BOUND, not PHYSICS-BOUND-period.
Different sensor recovers it. R20 identifies this categorisation
explicitly.
Five-cog speculative roadmap:
- cog-quantum-vitals (5y): nvsim + R14 + R15
- cog-mm-position (10y): atomic clock + R1 + R3.2
- cog-deep-rubble-survivor (15y): nvsim + R18 + drone
- cog-quantum-illuminated-pose (15y): quantum illum + R6.1
- cog-ICU-meg (20y): SQUID + R14 V3
Three deployment scenarios:
- Hybrid ICU bed (5y): 0/bed (4xESP32 + NV-diamond) vs ,000 monitor
- Atomic-clock mm-precision multistatic (10y): high-security access
- NV-drone disaster magnetometry (15y): 2.5x rubble depth over R18
Integration with existing nvsim (ADR-089):
- Magnetic-field time series -> R14 V1 vitals fusion
- Field map -> R12 PABS structural anomaly extension
- Stability indicator -> R7 mincut additional consistency channel
Future cog: cog-quantum-fusion or cog-quantum-vitals.
THE CLEANEST 'LOOP IS SENSOR-AGNOSTIC' DEMONSTRATION:
Even when classical CSI hits its physics floors (R13, R1 bandwidth,
R6.1 penalty), the ARCHITECTURE STAYS THE SAME; only the sensor swaps.
R6 forward model, R12 PABS, R7 mincut, R3 cross-room, R14 V1/V2/V3
framework — all apply to quantum sensors with parameter swaps.
This is the loop's architectural value proposition in its most explicit form.
Honest scope (very important):
- Most quantum tech is 10-20y from edge deployment
- nvsim is a SIMULATOR, not real hardware
- All 'improvement' numbers are theoretical bounds; real-world 30-70%
- Loop has NO real quantum sensor on bench
R20 special status:
- 8th exotic vertical
- First requiring quantum hardware for full realisation
- Most explicitly 10-20y horizon (matches cron prompt criteria)
- Recovers R13 NEGATIVE via different sensing modality
Composes with every loop thread + ADR-089 nvsim + ADR-113 placement.
Coordination: ticks/tick-37.md, no PROGRESS.md edit.
Loop summary: 18 research threads, 8 exotic verticals, 6 loop ADRs,
3 negative result categories (R13 conditionally recoverable now),
production roadmap shipped. 00-summary.md to follow at 12:00 UTC stop.
Terminal output of the SOTA research loop. Maps every research finding
to owner, LOC estimate, dependency, and priority across 6 tiers.
Total engineering budget across the loop's output:
- Tier 1 (Q3 2026): ~490 LOC, 3-4 person-weeks
- Tier 2 (Q3-Q4 2026): ~1180 LOC, 6-8 person-weeks
- Tier 3 (2027): ~1140 LOC, 8-10 person-weeks
- Tier 4-5 (long horizon): ~700+ LOC, 6-8 person-weeks
- TOTAL: ~3,500 LOC, ~25 person-weeks
Tier 1 (next quarter) ships:
- 1.1 wifi-densepose plan-antennas CLI tool (360 LOC) -- 93x placement lift
- 1.2 R12.1 pose-PABS in vital_signs cog (80 LOC) -- 9.36x intruder lift
- 1.3 cog-person-count v0.0.3 chest-centric (50 LOC)
- 1.4 ADR-029 amendment w/ ADR-113 matrix (0 LOC)
Critical-path graph:
1.1 + 1.2 -> 1.3 -> 2.1 ruview-fed -> 2.2 DP-vital-signs -> 3.1 cross-install -> 3.2 PQC
+-> 3.3 real-AETHER -> 3.4 fall-detect
+-> 4.x verticals
Why this matters: after 35 ticks of research output, this is the
document that lets a team pick up and ship without re-reading the 34
research notes. Priority alignment, estimate-anchoring, critical-path
visibility — all in one place.
R-thread mapping:
- R5/R6/R6.2 family/R6.1 -> Tier 1
- R12/R12.1 PABS -> Tier 1.2
- R3/R3.1/R3.2/R14/R15 -> Tier 2-3
- R7 mincut -> Tier 2 (in ruview-fed)
- R13 NEGATIVE -> rules out BP, no Tier line
- R10/R11/R16/R17/R18 verticals -> Tier 4-5
Composes with every loop output. Every thread, ADR, vertical sketch
has a line in some Tier. The TERMINAL output that needs the synthesis
power of a research loop to produce.
Honest scope:
- Estimates synthetic-data-based; may shift after bench validation
- Critical-path may have hidden dependencies (e.g. AgentDB schema)
- 25 person-weeks assumes full-time engineers
- Doesn't include integration testing, documentation, deployment ops
- Tiers based on architectural dependency, not business priority
Loop status after 35 ticks:
- 16 research threads
- 6 exotic verticals
- 6 new ADRs (105/106/107/108/109/113)
- 3 negative result categories
- 2 self-corrections
- 3 honest-scope findings
- 9-tick R6 family (complete)
- 3-tick R3 arc (complete)
- 3-tick R12 arc (complete)
- This production roadmap
00-summary.md will follow at 12:00 UTC / 08:00 ET cron stop.
Coordination: ticks/tick-35.md, no PROGRESS.md edit.