Files
ruvnet--RuView/api-docs/adr/ADR-297-multi-node-semantic-correctness.md
T
2026-08-11 17:41:30 +00:00

2.9 KiB

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.