Skip to content

Releases: ruvnet/RuView

Release v1408

26 May 19:35

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-ui iter 5): Call Service from Services page

CRUD increment 5/6. Each service pill on the Services page now has
a ▶ Call button that opens a modal letting the operator POST a
JSON service_data payload to /api/services// and
inspect the round-tripped response.

Modal contents:

  • heading "Call ."
  • target URL displayed as code (POST /api/services/...)
  • service_data JSON textarea (default {}, live-validated as
    JSON object — same rules as EntityForm.attributes)
  • response
     block: green border on 2xx, red on non-2xx,
    pretty-printed JSON when parseable
  • Close + Call buttons in footer; Call disabled on invalid JSON
    or while pending; renders "Calling…" briefly during the POST

Reuses <hc-modal> from iter 1. No new components — all of iter 5
lives in frontend/src/pages/Services.ts (~140 LOC delta).

Browser-verified end-to-end against homecore-server (13 services
seeded across 6 domains):

  • 13/13 service pills have a ▶ Call button
  • Modal opens with correct heading and target URL
  • Live validation: [1,2,3] → red "must be a JSON object";
    {broken json: → red "JSON parse: …"; valid → green ✓
  • Call button disabled on invalid input
  • Successful call: green-bordered response containing
    {"called":"switch.turn_on", "acknowledged":true,
    "service_data":{"entity_id":"light.kitchen_ceiling","brightness":200}}
  • Toast "Called switch.turn_on → 200"
  • homecore.ping with empty body (default {}) succeeds too
  • 0 console errors related to this flow

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:99c78f512c7ccc68164544c050ec98bcbaac8822

Release v1407

26 May 19:20

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-ui iter 4): live per-field validation + inline server errors

CRUD increment 4/6. The form now shows validity feedback on every
keystroke instead of only on Create click, makes the warning vs error
distinction visible (amber vs red), and propagates backend 4xx
responses into the form's own error surface.

frontend/src/components/EntityForm.ts (~80 LOC delta):

  • Three new @State fields tracking per-field validity: _idValid,
    _stateValid, _attrsValid (each is {ok:true} | {ok:false, level: 'err'|'warn', msg} or null when untouched).
  • Pure validators outside the class so they can be unit-tested:
    validateEntityId, validateState, validateAttrs.
  • validateEntityId now warns (amber, not red) if the domain prefix
    is outside the standard HA set. KNOWN_DOMAINS lists ~40 standard
    domains (sensor, light, switch, binary_sensor, climate, cover,
    fan, media_player, lock, camera, vacuum, climate, scene, script,
    automation, input_*, person, device_tracker, zone, weather, etc.)
    • homecore-native domain. Unknown domains create entities anyway
      (backend regex still passes them) but the operator sees the soft
      signal.
  • Sigils render below each field: ✓ green when ok, ✗ red on err,
    ! amber on warn. Field borders adopt the level color via
    .invalid / .warn classes.
  • New public method isValid() so the host can bind a disabled
    state on its Save button (unused for now; ready for a follow-up).
  • New public method setSubmitError(msg) so the host can surface
    server-side rejection text inline in the form's red error block,
    not just at the page top.

frontend/src/pages/Dashboard.ts (small delta):

  • _onSubmit() now calls this._form?.setSubmitError(null) before
    each attempt to clear stale text, and on non-2xx responses it
    surfaces the server's body text inline via setSubmitError.
    Page-top error block is no longer hijacked for form errors.

Browser-verified end-to-end (real homecore-server :8123):

entity_id field:
BadID → red border + "must match domain.snake_case…"
light.kitchen_test → green ✓ "entity_id OK"
madeup_domain.foo → amber border + "unknown domain 'madeup_domain' — HA-standard…"

state field:
empty → red ✗ required
"on" → green ✓

attributes field:
empty → green ✓ (defaults to {})
[1,2,3] → red ✗ "must be a JSON object…"
{"key": → red ✗ "JSON parse: Unexpected end of JSON input"
{"friendly_name":"Test"} → green ✓

Server-error inline:
Force 401 via wrong token → form red block shows
"server rejected (401): unauthorized"

Successful create: still works, toast still shown, 0 console errors.

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:3f5a7411db2d449f603532435c5cc68079ffb222

Release v1406

26 May 19:10

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore iter 3): DELETE /api/states/ + confirm modal in UI

CRUD increment 3/6. Full delete path lands end-to-end.

Backend (homecore-api):
rest.rs +18 LOC — new delete_state handler. Idempotent (matches HA's
removal semantics): returns 204 No Content whether the entity existed
or not. 4xx only for malformed entity_id or auth failure.
app.rs +6 LOC — adds .delete(rest::delete_state) to the
/api/states/:entity_id route alongside existing GET + POST.

Backend curl smoke:
POST /api/states/sensor.test_delete 201
DELETE /api/states/sensor.test_delete 204
GET /api/states/sensor.test_delete 404

Frontend:
components/StateCard.ts +25 LOC — small × delete button in the
card's top-right corner. opacity 0 by default, fades in on hover
or keyboard focus. dispatches hc-state-card-delete (NOT
hc-state-card-click) with stopPropagation so the card's own
click-to-edit handler doesn't also fire.

pages/Dashboard.ts +45 LOC — deletingState (StateView | null), a
confirm modal that names the entity_id in the body, Cancel /
Delete buttons in the footer (Delete styled in muted red),
_confirmDelete() dispatches DELETE with bearer, toast on
success, grid refresh.

Browser-verified end-to-end on real homecore-server :8123:

  • Hover card → × button visible
  • Click × → DELETE confirm modal (NOT edit modal — stopPropagation works)
  • Modal names entity_id in code block
  • Cancel: entity preserved, modal closes
  • Delete: backend GET-after-DELETE returns 404, grid card vanishes,
    toast "Deleted sensor.delete_target"
  • 0 unexpected console errors (1 expected 404 from verification fetch)

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:c0bb6f4fc77461e3981dd8f019f8964cf876235d

Release v1405

26 May 18:55

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-ui iter 2): Edit Entity modal + shadow-DOM focus delegation

CRUD increment 2/6 — clicking any state card on the Dashboard opens
the Add Entity modal in EDIT mode: pre-populated, entity_id locked,
"Save" primary button, idempotent POST to /api/states/ (backend
returns 200 if existed, 201 if created — same handler).

frontend/src/components/StateCard.ts:

  • card div is now role="button" tabindex=0, dispatches
    hc-state-card-click on click + Enter/Space keydown
  • aria-label="Edit <entity_id>" for screen readers
  • shadowRootOptions delegatesFocus=true so the outer Tab sequence
    can reach the inner focusable div (caught by browser agent —
    without this Tab couldn't pierce the shadow root)

frontend/src/pages/Dashboard.ts:

  • new state: editingState (null = create, StateView = edit)
  • _openEdit() catches hc-state-card-click from the grid container
  • modal heading switches: "Add entity" ↔ "Edit <entity_id>"
  • primary button text switches: "Create" ↔ "Save"
  • EntityForm receives .editing=true so entity_id input is disabled
  • submit toast reads "Updated" or "Created" depending on mode

Browser-verified end-to-end (real homecore-server :8123, 12 entities):

  • Click light.kitchen_ceiling → modal opens with all 4 attributes
    (brightness=230, color_temp_kelvin=4000, friendly_name,
    supported_color_modes) pre-populated
  • Change state to "off", click Save → toast "Updated
    light.kitchen_ceiling = off", grid card reflects new state
  • Backend curl confirms /api/states/light.kitchen_ceiling.state = "off"
  • Enter key on focused card opens the modal too
  • 0 console errors

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:89190b6c2d357dff3ecbe8daaea225b7569032b0

Release v1404

26 May 18:40

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-ui iter 1): Modal + EntityForm + Add Entity flow

First CRUD increment. Click "+ Add entity" on the Dashboard
toolbar → modal opens → form with entity_id / state / attributes
fields → Create validates client-side then POSTs /api/states/
→ modal closes, toast confirms, dashboard refreshes.

New components:
frontend/src/components/Modal.ts (~110 LOC) — reusable accessible
overlay. open property; closes on Escape and backdrop click.
Heading prop; default + footer slots.

frontend/src/components/EntityForm.ts (~130 LOC) — three-field form
with public requestSubmit()/requestCancel() methods. Client-side
validation:
- entity_id matches /^[a-z][a-z0-9_].[a-z][a-z0-9_]$/
- state non-empty
- attributes parses as a JSON object (rejects array/scalar)
Emits hc-entity-submit / hc-entity-cancel events for host to
handle. Footer buttons live in the host (modal slot=footer).

frontend/src/pages/Dashboard.ts (+60 LOC) — toolbar with
"+ Add entity" button, modal state, POST handler that wraps
fetch with bearer token, success toast (3 s), refresh().

Browser-verified end-to-end (real homecore-server :8123):

  • Toolbar button visible: Y
  • Modal opens: Y
  • 3/3 validation paths fire correctly:
    BadID → "entity_id must match domain.snake_case"
    blank state → "state must not be empty"
    [1,2,3] attrs → "attributes must be a JSON object"
  • Successful create: light.test_bulb POSTed; modal closes; toast
    "Created light.test_bulb = on"; grid count went 10 → 11
  • Persistence: hard reload, count stays
  • 0 console errors (Lit dev-mode notices excluded)

Note: TypeScript caught a name collision — attributes is reserved
on HTMLElement (NamedNodeMap). Renamed the Lit @Property to
entityAttrs so the class extends LitElement cleanly.

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:e7215a16e57656aa1fe6874ba260fc8844dd665c

Release v1403

26 May 18:25

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-server): seed 10 default entities on boot (--no-seed-entities to opt out)

Companion to the seed_default_services() commit. Dashboard + States
pages now have content on every fresh --db :memory: boot, not just
after bash scripts/homecore-seed.sh.

Adds:

  • new CLI flag --no-seed-entities (default: enabled)
  • seed_default_entities(hc) mirroring the bash script's 10-entity
    set (4 RuView sensing-derived + 6 conventional HA fixtures)
  • Boot log:
    Service registry seeded with 13 default service(s)
    State machine seeded with 10 default entities

Two seeds stay in sync — integrations overwrite the same entity_ids
via /api/states/ POST. Run with --no-seed-entities when wiring
real plugins that populate the state machine themselves.

Empirical (after rebuild + fresh restart):
GET /api/states → 10 entities
GET /api/services → 6 domains, 13 services

homecore-server --db :memory: is now enough for the web UI to be
fully populated on first paint.

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:0979faccd4fa3fd6f8d76070371b3846b5b8a7c2

Release v1402

26 May 18:15

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-server): seed 13 default services across 6 domains on boot

Operators (and the new web UI) saw "No services registered" on every
vanilla boot because nothing in the boot sequence called
ServiceRegistry::register(). The Assist pipeline registers intent
handlers — a different surface — but /api/services stayed empty
until a plugin or integration loaded.

Adds seed_default_services() after HomeCore::new(). Each handler
is a FnHandler that echoes the call back as a JSON acknowledgement
so the service registry is exercise-able from day one. Integrations
override these by re-registering the same ServiceName with a real
handler later.

Seeded set:

homeassistant: restart, stop, reload_core_config
light: turn_on, turn_off, toggle
switch: turn_on, turn_off, toggle
scene: apply
automation: trigger
homecore: ping, snapshot_state (HOMECORE-native)

Boot log now reports:

Service registry seeded with 13 default service(s)

GET /api/services now returns 6 domains with 13 services total.
The HOMECORE web UI's Services page shows them under proper
domain headings.

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:75f984e515fe65bdb3d19dbae735513304635b18

Release v1397

26 May 16:47

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(homecore-ui): wire nav router + States / Services / Settings pages

Before: clicking Dashboard / States / Services / Settings highlighted
the active nav button but the page content never changed. AppShell
dispatched hc-navigate events but no listener acted on them.

After (~232 LOC across 4 files):

  • main.ts (+20 LOC) tiny router: NAV_TO_TAG maps nav id → page
    custom element; on hc-navigate, swap the AppShell's child.
  • pages/States.ts (~86 LOC) HA-style entity table with 5 s refresh.
  • pages/Services.ts (~82 LOC) domain-grouped service registry,
    friendly empty state when no services registered.
  • pages/Settings.ts (~90 LOC) backend config readout + bearer-token
    editor (localStorage["homecore.token"]).

Browser-verified all 4 nav clicks swap content; 0 console errors.
Dashboard → 10 entity cards; States → 10-row table; Services →
empty state (0 domains); Settings → config + token editor.

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:4253c0e4fc7007ff8bc3d5b6fa887b977a7d2602

Release v1388

26 May 03:14

Choose a tag to compare

Automated release from CI pipeline

Changes:
feat(docker): bundle homecore-server (HOMECORE / ADRs 126-134) in the image

The HOMECORE native Rust port of Home Assistant landed in v0.10.0
(PR #800). The published Docker image now ships its binary alongside
sensing-server and cog-ha-matter so a single docker run brings up
the full RuView + HA-wire-compatible stack.

Dockerfile.rust:

  • cargo build --release -p homecore-server in the build stage
  • strip the new binary
  • copy /app/homecore-server in the runtime stage
  • sanity-check: image build now fails if /app/homecore-server isn't
    executable (same guard pattern that already covers sensing-server
    and cog-ha-matter)
  • EXPOSE 8123 (HA-compat REST + WebSocket port — homecore-api
    binds 0.0.0.0:8123 by default per its --bind CLI flag)

docker-entrypoint.sh:

  • new dispatch keyword: homecore or homecore-server
    Usage: docker run --network host ruvnet/wifi-densepose:latest homecore
    Defaults --bind to 0.0.0.0:8123 (overridable via HOMECORE_BIND env)

The existing two dispatch paths (no arg → sensing-server, cog-ha-matter
→ HA + Matter cog) keep working unchanged. Three-binary image, one
entrypoint, operator picks the role at run time.

Triggers a workflow rebuild on push to main per the docker workflow's
path filter; the multi-arch (amd64 + arm64) image will be published
to Docker Hub as ruvnet/wifi-densepose:latest after CI green.

Refs ADRs 126-134, v0.10.0 release.

Co-Authored-By: claude-flow ruv@ruv.net

Docker Image:
ghcr.io/ruvnet/RuView:8cb8a37dc41b1dd936e563334fead7ccea9e4c8e

Release v1387

26 May 02:57
e96ebae

Choose a tag to compare

Automated release from CI pipeline

Changes:
HOMECORE: native Rust/WASM/TS port of Home Assistant — ADRs 125-134 implementation (#800)

  • feat(adr-125 iter 3): BFLD PrivacyGate + semantic-event naming at HAP boundary

Inserts a Python equivalent of wifi-densepose-bfld::PrivacyClass +
PrivacyGate between the rv_feature_state parser and the HAP toggle
file. ADR-125 §2.1.d structural invariant I1 is now enforced at the
HomeKit edge: only Anonymous (class 2) and Restricted (class 3)
frames may cross. Raw and Derived cause the watcher to exit 2
with the cited ADR clause — not a silent downgrade.

Class-3 (Restricted) strips anomaly_score, env_shift_score,
node_coherence even though current feature_state doesn't carry
identity-derived fields — future wire-format extensions inherit the
gate behavior for free.

Operator-facing semantic naming follows ADR-125 §2.1.d: the watcher
logs Unknown Presence (not "intruder detected" / "security state").
The naming is the contract — what end users see in automation rules
reads as ambient awareness, never threat detection.

Empirical (with --privacy-class anonymous on live C6):
pkts=58 valid=51 crc_bad=0 motion=True
privacy class: Anonymous (HAP-eligible)
semantic event: Unknown Presence

Refuse path validated:
$ ~/hap-venv/bin/python c6-presence-watcher.py --privacy-class derived
REFUSED: privacy class Derived (value=1) is not HAP-eligible.
ADR-125 §2.1.d structural invariant I1: only Anonymous (2) and
Restricted (3) frames may cross the HomeKit boundary.
$ echo $?
2

Branch: feat/adr-125-apple-fabric (kept off main while docker build
for sha 9fda90f is still compiling; this commit touches only
scripts/, not any docker workflow path-filter).

Refs ADR-125 §2.1.d, ADR-118 §2.1/§2.2.

Co-Authored-By: claude-flow ruv@ruv.net

  • docs(adr-125 iter 4): CHANGELOG bullet for the APPLE-FABRIC e2e

Pre-merge checklist item 5. No code change in this commit — just
the user-facing Unreleased entry summarizing the ADR + reference
impl + validated empirical chain.

Co-Authored-By: claude-flow ruv@ruv.net

  • feat(adr-125 tier1 #1): multi-characteristic accessory + JSON-state IPC

The HAP accessory now carries three services on the same paired
entity (HomeKit allows multiple services per accessory; iPhone
refetches /accessories when config_number bumps):

  • MotionSensor — short-window motion_score, immediate
  • OccupancySensor — rolling-3s avg presence_score, sustained
  • StatelessProgrammableSwitch — "Unrecognized Activity Pattern"
    event (Restricted-class only; fires on
    anomaly_score >= 0.7); ADR-125 §2.1.d
    semantic naming, not security state

New JSON IPC contract /tmp/ruview-state.json between watcher
and HAP daemon:

{ "motion": bool, "occupancy": bool, "anomaly_ts": float,
"ts": float }

Atomic writes (tmp + rename). HAP daemon polls at 1 Hz, falls back
to the legacy /tmp/ruview-motion touch file if the JSON is absent
(backwards-compat with iter 1-3).

Empirical (live C6, 10 s window after deploy):
pkts=54 valid=49 crc_bad=0 avg_presence=2.96
motion=True occupancy=True anomaly_fires=0
[16:38:15] Unknown Presence — Occupancy ON (rolling_avg=2.79)

Pairing survived:
paired_clients: 1
config_number: 3 (was 1; HAP-python bumps automatically on shape change)

Tier 1 #1 (multi-characteristic) of the Tier 1+2 sprint. Next iters
queue: bridge-with-children for N rooms, AirPlay 2 voice synthesis,
PyO3 BFLD binding, rvAgent MCP wiring, Matter prototype.

Refs ADR-125 §2.1.c (bridge topology), §2.1.d (semantic events),
ADR-118.

Co-Authored-By: claude-flow ruv@ruv.net

  • feat(adr-125 tier1+2 iter 2): sensing-server-equivalent for @ruvnet/rvagent

scripts/ruview-sensing-server.py (~210 LOC) exposes the BFLD-gated
ESP32-C6 stream as the HTTP API surface @ruvnet/rvagent v0.1.0
(ADR-124, npm) expects. Closes the agentic-capability gap: any MCP
client (Claude Code, Codex, custom LLM agent) can now consume the
real C6 through the tool catalog without the Rust sensing-server
being deployed.

Endpoints (mirrors tools/ruview-mcp/src/tools/*.ts):

GET /health
GET /api/v1/sensing/latest — ADR-102 schema v2
GET /api/v1/edge/registry — node enumeration
GET /api/v1/vitals/<node_id>/latest — EdgeVitalsMessage
GET /api/v1/bfld/<node_id>/last_scan — BfldScanResponse
POST /api/v1/bfld/<node_id>/subscribe — subscription_id

c6-presence-watcher.py now writes a companion /tmp/ruview-last- feature.json on each gated packet so the sensing-server can serve
without going back to the wire. Atomic tmp+rename. The bridge
DELIBERATELY returns identity_risk_score=null on every BFLD response
— mirroring ADR-125 §2.1.d at the HTTP boundary even though the
rvagent schema's slot is nullable.

Live smoke test against the real C6 (node_id=12):

$ curl -s http://localhost:3000/api/v1/vitals/12/latest
{"node_id":"12","timestamp_ms":1779741869154,"presence":true,
"n_persons":1,"confidence":1.0,"breathing_rate_bpm":18.75,
"heartrate_bpm":40.0,"motion":1.0}

$ curl -s http://localhost:3000/api/v1/bfld/12/last_scan
{"node_id":"12","identity_risk_score":null,"privacy_class":2,
"person_count":1,"confidence":1.0,"presence":true,
"timestamp_ns":1779741869154607104}

$ curl -s -X POST 'http://localhost:3000/api/v1/bfld/12/subscribe?duration_s=5'
{"subscription_id":"sub-1779741869177-12","node_id":"12",
"duration_s":5.0,"endpoint_hint":"poll GET ..."}

Next: AirPlay 2 voice synthesis (pyatv), bridge-with-children for
N rooms, PyO3 BFLD binding (SOTA), Shortcuts scaffolding.

Refs ADR-124 (@ruvnet/rvagent contract), ADR-125 §2.1.d, ADR-118.

Co-Authored-By: claude-flow ruv@ruv.net

  • feat(adr-125 tier1+2 iter 3): production HAP bridge with N child accessories

scripts/ruview-hap-bridge.py (~170 LOC) implements the ADR-125 §2.1.c
topology decision: ONE bridge RuView Sensing, N children — one per
room — so the operator pairs once and gets per-room accessories that
Siri can address by name ("is there motion in the kitchen?").

State per room comes from /tmp/ruview-state..json. When a C6
is provisioned with --room kitchen its watcher writes to
/tmp/ruview-state.kitchen.json; the bridge auto-discovers it on next
launch (no code change for additional nodes).

Legacy /tmp/ruview-state.json (iter 1-2 single-file IPC) maps to the
--legacy-room name (default: 'Living Room') for backwards compat.

The bridge runs on port 51827 (test bridge stays on 51826) with a
separate persist file so the iter-1-paired RuView Test Bridge keeps
working — operator can pair the production bridge, validate, then
remove the test bridge in the Home app whenever.

Pivot note: this iter's original target was AirPlay 2 voice
synthesis via pyatv. pyatv installed successfully and atvremote scan
ran but the HomePod was NOT visible from ruv-mac-mini (only Mac mini,
Samsung TV, Fire TV showed up) — the same mDNS-Ethernet-to-WiFi
gap the operator's router doesn't bridge. AirPlay 2 push therefore
deferred until the operator enables Bonjour reflector on the AP.
Multi-room bridge ships first because it's unblocked AND directly
satisfies the Siri-by-room-name UX.

Empirical (deployed on ruv-mac-mini, prod_bridge_pid=64094):
$ dns-sd -B _hap._tcp local.
Add 3 15 local. _hap._tcp. RuView Test Bridge 224DF9
Add 3 15 local. _hap._tcp. RuView Sensing 0B4FC4
Add 3 15 local. _hap._tcp. Main Floor (Ecobee)

[bridge] child accessory ready: 'Living Room' <- /tmp/ruview-state.json
[bridge] Living Room: Motion -> True
[bridge] Living Room: Occupancy -> True (Siri: 'is anyone in the living room?')

Setup code for pairing the new bridge: 629-88-678.

Tier 1 §2.1.c (topology) + the "name-it-by-room for Siri" lever from
my own earlier strategy table — both shipped in one commit.

Refs ADR-125 §2.1.c.

Co-Authored-By: claude-flow ruv@ruv.net

  • feat(adr-125 tier1+2 iter 4): semantic-events MCP endpoint per §2.1.d

GET /api/v1/semantic-events/<node_id>/latest exposes the three
ADR-125 §2.1.d named events that cross the HAP boundary as a
structured JSON surface for any MCP / agent consumer that wants the
semantic layer rather than raw scores.

Response shape:

{
"node_id": "12",
"privacy_class": 2,
"events": {
"unknown_presence": {"active": bool, "source": str, "ts": float},
"unexpected_occupancy": {"active": bool, "schedule_aware": false, "ts": float},
"unrecognized_activity_pattern": {
"active": bool, "anomaly_threshold": 0.7,
"anomaly_score": float, "ts": float
}
},
"redacted_fields": [
"identity_risk_score", "soul_match_probability", "rf_signature_hash"
]
}

Live response from real C6 (node_id=12):

{
"unknown_presence": {"active": true, ...},
"unexpected_occupancy": {"active": true, "schedule_aware": false, ...},
"unrecognized_activity_pattern": {"active": false, "anomaly_score": 0.0, ...}
}

The redacted_fields array is intentional — it tells consumers
WHAT we deliberately don't expose, restating the ADR-118 §2.5 /
ADR-125 §2.1.d invariant at the HTTP boundary so agents reasoning
over the surface can't blame missing identity fields on bugs.

unexpected_occupancy.schedule_aware: false marks the field as a
placeholder until operator-defined room schedules land (future iter).
Agents that branch on this can fall back to raw occupancy until then.

Refs ADR-125 §2.1.d (semantic-events naming contract).

Co-Authored-By: claude-flow ruv@ruv.net

  • feat(adr-125 tier1+2 iter 5): rvagent MCP consumer — agentic chain proven

scripts/rvagent-mcp-consumer.py (~155 LOC) is an MCP JSON-RPC 2.0
stdio client that spawns the published @ruvnet/rvagent v0.1.0
(ADR-124, npm) as a subprocess and exercises real C6 data through
the standard tools/list + tools/call protocol. This is the "agen...

Read more