feat(gateway): ingest RuView RF context via /api/field (ADR-264 §8) - #2
feat(gateway): ingest RuView RF context via /api/field (ADR-264 §8)#2fallen-pc wants to merge 3 commits into
Conversation
Adds an opt-in bridge (`--rf-upstream <url> [--rf-poll-ms <ms>]`) that polls a RuView `wifi-densepose-sensing-server`'s `GET /api/field` (ADR-262 P3), verifies each event's provenance receipt (`rufield_provenance::is_fusable`), and keeps a bounded ring of the resulting `RfContext`s at `GET /api/rf-context`. Deliberately storage-only, not fusion. `rucelium_worldgraph::fuse_rf_context` requires pairing an RfContext against a *specific* existing environmental sample's WorldGraph node to compute a Plausibility verdict, and nothing in this codebase defines which biome zone/sensor a given RuView device's RF context is supposed to corroborate. Inventing that pairing here would be exactly the fabricated correlation ADR-264 Sec8 and this project's honesty discipline exist to prevent - the zone-pairing design is deliberate follow-up work, not guessed at. Every ingested event is tagged verified: bool (unverified events are stored and visible, never silently dropped or upgraded), and /api/rf-context reports "fused": false explicitly so a consumer never has to infer that from absence. The ApiFieldPayload this bridge decodes only declares the one field it needs (`events`) - the two informational fields on RuView's real response (`signer_pubkey_hex`, `dev_signing_key`) are deliberately left undeclared, since serde drops unknown fields by default. That sidesteps the exact class of bug fixed in ruvnet#1, where a *different* consumer of this same endpoint over-specified those two fields (wrong name on one, wrong type on the other) and broke on every real payload. Live-verified end to end against a real RuView instance (real ed25519-signed events, real WDP_RUFIELD_SIGNING_SEED): 2,240 events ingested across 35 polls, 0 unverified, 0 poll failures, all correctly labelled synthetic/simulated_node_* per the RuView-side fix in ruvnet/RuView#1720. New tests: verified/unverified tagging, non-WifiCsi events correctly excluded from RF context (still counted as received), bounded-ring eviction, poll-failure counting never touching the ring, plus CLI flag parsing for --rf-upstream/--rf-poll-ms.
Extends the RF-context bridge to also run every verified event through rufield_fusion::RuFieldFusion, so /api/rf-context now returns real room-state inferences (person_present, breathing, etc.) alongside the raw context ring - not just stored data, but RuField's own room-state rules actually firing on it. This is deliberately the OTHER kind of fusion from what the previous commit ruled out. RuFieldFusion needs nothing external - no environmental sample, no biome, no zone mapping - it's a temporal-window fusion purely within the RF domain, over this upstream's own events. There is nothing to fabricate here, unlike rucelium_worldgraph::fuse_rf_context (still not called - see the module docs for why that cross-domain pairing is still deliberate follow-up work). Only verified events reach the engine, mirroring rufield-viewer's own rule: unverified events stay visible in the ring (flagged) but are excluded from fusion entirely. /api/rf-context now reports "fused_into_worldgraph": false explicitly (renamed from the previous commit's "fused" for clarity, since this commit adds a within-RF fusion that IS real) so a consumer never has to infer the WorldGraph boundary from absence. New tests: a verified presence-bearing event produces a real person_present inference from RuField's default room-state rules; an unverified event never reaches the engine at all (empty inferences). Live-verified end to end against a real RuView instance: person_present (confidence 1.0), breathing (0.92), and nocturnal_scratch (0.8), each with real supporting_events pointing at actual RuView event ids.
|
Added a second commit to this PR: wires the ingested RF context into To be clear about scope — this is a different fusion from
|
Room-state inferences produced by the previous commit only ever existed for the ~2s their producing rule's TTL covers - computed live, on request, from whatever the fusion engine's window holds *right now*. A person_present blip that fired between two polls of /api/rf-context was gone by the next request, even one second later. Every other durable thing in this gateway (ObservationStore, EventStore) doesn't behave that way. Real fix, not the full one: this is in-memory history, not disk durability. RF inferences still have no biome node_id/sequence to cite into EventStore the way a flood alert does (same structural gap as fuse_rf_context and the earlier zone-pairing problem - checked pipeline.rs's maybe_alert()/EvidenceRef before writing this, and citing RF evidence there would mean fabricating a registry entry that doesn't exist). So: a bounded 32-entry ring of timestamped InferenceSnapshots, recorded only when a poll actually fed the fusion engine something new (an empty or all-unverified poll is not history, it's noise), exposed as inference_history at /api/rf-context. Survives across polls; does not survive a restart. New tests: a verified batch captures a snapshot with the right timestamp and inference; an empty or all-unverified batch records nothing; the ring is bounded and drops oldest first. Live-verified: 28 distinct snapshots accumulated over ~10s against a real RuView instance, growing toward the 32-entry cap - confirmed real accumulating history, not a single instantaneous read relabelled.
|
Third commit: room-state inferences only existed for their ~2s TTL, computed live on request — a `person_present` blip between two polls was gone even a second later, unlike everything else durable in this gateway. Added a bounded 32-entry in-memory history ring (`inference_history` at `/api/rf-context`), recorded only when a poll actually fed the engine something new. Checked `pipeline.rs`'s `maybe_alert`/`EvidenceRef` first to see if RF inferences could raise real signed local alerts the way flood readings do — they can't without fabricating a biome `node_id`/`sequence` registry entry for RF sources, which is the same structural gap as the zone-pairing problem this PR already defers. So this stays in-memory, not disk-durable; that's still open. Live-verified: 28 distinct snapshots accumulated over ~10s against a real RuView instance. |
Summary
Adds an opt-in RF-context bridge to
rucelium-gateway:--rf-upstream <url> [--rf-poll-ms <ms>]polls a RuViewwifi-densepose-sensing-server'sGET /api/field(ADR-262 P3), verifies each event's provenance receipt (rufield_provenance::is_fusable), and keeps a bounded ring of the resultingRfContexts atGET /api/rf-context.Why storage-only, not fusion
rucelium_worldgraph::fuse_rf_contextrequires pairing anRfContextagainst a specific existing environmental sample's WorldGraph node to compute aPlausibilityverdict — and nothing in this codebase currently defines which biome zone or sensor a given RuView device's RF context is supposed to corroborate. Inventing that pairing here would be exactly the kind of fabricated correlation ADR-264 §8 (and this project's honesty discipline generally) exists to prevent.So this PR deliberately stops at: get real, verified RF context flowing and visible. Every ingested event is tagged
verified: bool— an unverified event is stored (visible, like a forged-data badge) but never silently dropped or upgraded — and/api/rf-contextreports"fused": falseexplicitly, so a consumer never has to infer that from absence. The zone-pairing design for actual fusion is deliberate follow-up work, not guessed at.A deliberate design choice, informed by #1
This bridge's
ApiFieldPayloadonly declares the one field it needs (events). RuView's real/api/fieldresponse also carriessigner_pubkey_hexanddev_signing_key, but this struct doesn't declare either — serde drops unknown fields by default, so it's inert to whatever shape those two informational fields take. That sidesteps the exact class of bug fixed in #1, whererufield-viewer's consumer of this same endpoint over-specified those two fields (wrong name on one, wrong type on the other) and broke on every real payload.Test plan
cargo test -p rucelium-gateway— 63/63 passing (52 lib + 1 e2e + 4 push-federation + 6 restart), no regressionsWifiCsievents correctly excluded from RF context (still counted as received), bounded-ring eviction, poll-failure counting never touching the ring,--rf-upstream/--rf-poll-msCLI parsingWDP_RUFIELD_SIGNING_SEED): 2,240 events ingested across 35 polls, 0 unverified, 0 poll failures, all correctly labelledsynthetic/simulated_node_*per the RuView-side fix in fix(rufield): simulated source now feeds /api/field, honestly RuView#1720.