Releases: ruvnet/RuView
Release v1408
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
▶ Callbutton - 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
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.
- homecore-native domain. Unknown domains create entities anyway
- 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 callsthis._form?.setSubmitError(null)before
each attempt to clear stale text, and on non-2xx responses it
surfaces the server's body text inline viasetSubmitError.
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
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
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-clickon 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-clickfrom 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
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
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
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
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; onhc-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
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:
homecoreorhomecore-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
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...