mirror of
https://github.com/ruvnet/RuView
synced 2026-08-11 20:41:44 +00:00
59 lines
2.9 KiB
Markdown
59 lines
2.9 KiB
Markdown
# ADR-297: Multi-node semantic correctness — per-node inference, node-keyed rate limiting, stale state
|
|
|
|
- **Status**: Accepted — initial implementation (this PR)
|
|
- **Date**: 2026-08-11
|
|
- **Deciders**: ruv
|
|
- **Tags**: multi-node, mqtt, home-assistant, correctness, sensing-server
|
|
|
|
## Context
|
|
|
|
The external review confirmed three defects on the multi-node path — the core
|
|
mechanism RuView uses to reduce blind spots and room dependence:
|
|
|
|
1. The active `NodeInfo` payload carries RSSI/position/subcarrier/sync but **no
|
|
per-node classification**; the MQTT mapper reads `node.classification` and
|
|
falls back to the room aggregate when absent, so every node can publish the
|
|
same aggregate presence value (issues 1540, 1554).
|
|
2. The MQTT `RateLimiter` is keyed by `EntityKind` only
|
|
(`mqtt/state.rs:65`), so one node consumes the numeric publish slot and the
|
|
others are suppressed until the interval expires, while availability still
|
|
says online (issue 1541).
|
|
3. In the UDP vital path, top-level classification is taken from the
|
|
latest-arriving node while other features are fused, so with disagreeing
|
|
nodes room presence can flip at packet frequency (issue 1555).
|
|
|
|
## Decision
|
|
|
|
- **Separate the types.** Introduce `NodeInference` (per-node classification +
|
|
confidence + freshness) distinct from `RoomInference` (the fused room
|
|
aggregate). `NodeInfo` carries a `NodeInference`; the room aggregate is
|
|
computed explicitly and never overwrites node state. No silent fallback from
|
|
node to room.
|
|
- **Key the rate limiter by (node, entity).** `RateLimiter` becomes keyed on
|
|
`(NodeId, EntityKind)` so nodes no longer starve each other; per-entity
|
|
behavior per node is preserved.
|
|
- **Deterministic fusion.** Room classification is a pure function of the set
|
|
of current per-node inferences (e.g. freshness-weighted vote), not
|
|
last-writer-wins; identical inputs yield identical room state.
|
|
- **Stale entities cannot stay online.** An entity whose backing node has not
|
|
reported within N expected publish intervals transitions to unavailable/
|
|
stale rather than holding a frozen value while availability says online.
|
|
|
|
## Consequences
|
|
|
|
- Multi-node HA/MQTT output becomes semantically correct; distinct nodes
|
|
report distinct state and no longer suppress one another.
|
|
- Schema change to `NodeInfo`/the MQTT contract; existing single-node
|
|
deployments keep working (one node = one inference). Consumers reading the
|
|
old aggregate-only shape need the migration accessor.
|
|
- Aligns with ADR-295 (freshness) and the review's call for one canonical
|
|
`NodeInference`/`RoomInference` contract.
|
|
|
|
## Validation
|
|
|
|
- Unit/integration tests: per-node classification round-trips through the MQTT
|
|
mapper with no room fallback; two nodes with different rates both publish
|
|
(no starvation); disagreeing nodes produce deterministic, non-flapping room
|
|
state; a silent node's entities go stale, not frozen-online.
|
|
- `cargo test -p wifi-densepose-sensing-server`.
|