chore: fix git submodule update command#1
Conversation
Update the README and CI workflow to use `git submodule update --remote --recursive` instead of `--merge`. The `--merge` flag creates merge commits inside the submodules, which breaks in GitHub Actions with "Committer identity unknown" if git user/email are not configured before the submodule step. Using `--recursive` correctly performs a detached HEAD checkout at the latest commit of the tracked branch without needing an identity until we commit the pointer change to the superproject. Co-authored-by: manupawickramasinghe <73810867+manupawickramasinghe@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
…t non-human-centric (ruvnet#739) Seventh exotic vertical demonstrating the loop's vertical-agnostic infrastructure. R19 is the FIRST NON-HUMAN-CENTRIC vertical. R19 composes: - R10 gait taxonomy (extended to livestock species) - R6.2.5 multi-subject union (herd density) - R12 PABS (predator detection + cattle-fall) - R14 V1 (rate-level breathing for welfare scoring) - R15 (per-animal RF fingerprint for ID without tag) Per-species gait + vital tables: | Species | Stride | Normal RR | Stress RR | | Cattle | 0.6-1.2 Hz | 10-30 BPM | >40 | | Pig | 1.0-2.0 Hz | 10-25 BPM | >35 | | Sheep | 1.5-2.5 Hz | 12-25 BPM | >30 | | Horse | 1.0-1.8 Hz | 8-16 BPM | >20 | | Chicken | 3.0-5.0 Hz | 15-40 BPM | >50 | Six-cog roadmap (0-15y): - cog-cattle-monitor (5y): R10 + R14 + R6.2.5 + R12.1 - cog-pig-welfare (5y): R6.2.5 + R14 + correlation - cog-predator-alert (5y): R12 PABS + R10 classifier - cog-lameness-detector (10y): R10 gait asymmetry + drift - cog-birthing-alert (10y): R14 V1 species signature - cog-free-range-tracker (15y): R6.2.2 sparse + Tailscale mesh High-impact use cases: - Predator detection at pasture edges: mitigates 32M/year US livestock losses (USDA 2015) - Heat-stress detection in dairy: overheated cattle drop milk production 30-50% before visual signs - Lameness early detection: dairy industry's #1 welfare issue - Sick-pig isolation alert: tail-biting cascade prevention Three scenarios: - Dairy barn (5y): 00 vs 0K visual+RFID+behaviour - Free-range pasture (10y): self-organising solar+ESP32+Tailscale - Pig barn welfare (15y): EU End-the-Cage / Prop 12 alignment What's different from human verticals: - Mass range 1.5-1000 kg (3+ orders of magnitude) - Count 1-1000+ per pen - Privacy: farmer-consent regime, not HIPAA/OSHA/GDPR - Regulatory: USDA / EU welfare instead of FDA/OSHA - Cost sensitivity: very high (2-5% margins) - Chicken-scale economically marginal Honest scope: - Synthetic data only; per-species RCS measurements needed - Chicken-scale marginal economically - High-density pig (8-100/barn) may exceed R6.2.5's 4-occupant limit - Weather effects on outdoor RF not in scope - No animal-welfare ethics review (loop specifies infrastructure) R19 special status: FIRST NON-HUMAN-CENTRIC. Privacy framework doesn't apply (animals can't consent); replaced by animal-welfare regulations. R18+R19 = two verticals needing external partnerships (FEMA, USDA). Seven exotic verticals now: 1. R10 wildlife 2. R11 maritime 3. R14 empathic appliances (home) 4. R16 healthcare 5. R17 industrial 6. R18 disaster (integrates MAT crate) 7. R19 livestock (first non-human-centric) Composes with every loop thread (R1, R3, R5, R6/R6.1, R6.2.5, R7, R10, R12/R12.1, R13 NEG, R14, R15) + ADR-113 + ADR-105-109. Coordination: ticks/tick-36.md, no PROGRESS.md edit.
Iter 30. Ships the three ADR-122 §2.6 operator-ready Home Assistant
automation blueprints. Each blueprint binds to one BFLD MQTT entity
(presence / motion / identity_risk) and lets an HA operator import
+ configure without writing YAML by hand.
Added (under v2/crates/cog-ha-matter/blueprints/bfld/):
- presence-lighting.yaml
binary_sensor.<node>_bfld_presence ⇒ light.turn_on / turn_off
with a configurable hold_seconds delay before the off action
(ADR-122 §2.6 requirement: "configurable hold time")
- motion-hvac.yaml
sensor.<node>_bfld_motion ⇒ climate.set_temperature
Operator picks motion_threshold (default 0.3, per ADR §2.6),
delta_temperature_c (°C adjustment), and quiet_seconds debounce
- identity-risk-anomaly.yaml
sensor.<node>_bfld_identity_risk ⇒ notify.<target>
Two trigger paths:
- Absolute spike (raw score >= spike_threshold, default 0.8)
- Rolling 7-day z-score deviation (default 3 sigma)
Requires a Statistics helper entity for the baseline; documented
in the inline description and the blueprints README.
- README.md
Lists the three blueprints + privacy caveat for identity_risk
(only present at PrivacyClass::Anonymous; class 3 deployments
will fail validation by design)
Added (in v2/crates/wifi-densepose-bfld/tests/ha_blueprints.rs):
- 7 named tests using include_str! to embed each YAML at build time
and validate structure without adding a serde_yaml dep:
presence_lighting_blueprint_is_structurally_valid
motion_hvac_blueprint_is_structurally_valid
identity_risk_blueprint_is_structurally_valid
blueprints_carry_source_url_pointing_at_canonical_path
(catches path drift when files move)
presence_blueprint_uses_mqtt_integration_filter
motion_blueprint_uses_mqtt_integration_filter
identity_risk_blueprint_carries_privacy_class_caveat_in_description
(operators running class 3 should know not to install)
- Helper assert_required_blueprint_fields(yaml, name_substring, label)
enforces blueprint.{name,domain,input,trigger,action,mode} per HA spec
ACs progressed:
- ADR-122 §2.6 — all three blueprints shipped with the documented
configurable inputs (hold_seconds for #1, motion_threshold +
delta_temperature_c for ruvnet#2, z_score_threshold + statistics_entity
for ruvnet#3). Operator installs via HA UI; no YAML editing required.
- ADR-118 §1.5 privacy_mode visibility — identity-risk blueprint
documents the class-2-only availability so operators understand
why the blueprint fails on class-3 deployments.
Test config:
- cargo test --no-default-features → 72 passed
- cargo test → 210 passed (203 + 7)
Out of scope (next iter target):
- GitHub Actions workflow with mosquitto Docker so iters 24 + 29
e2e tests actually run in CI with BFLD_MQTT_BROKER set.
- cog-ha-matter cargo crate-internal test that loads each blueprint
via serde_yaml + validates against an HA blueprint schema (instead
of the string-only checks here). Optional; current coverage is
sufficient to catch drift in the YAML files themselves.
Co-Authored-By: claude-flow <ruv@ruv.net>
…32/332 GREEN) Iter 50. PR-readiness pivot iter #1. Lands the BFLD entry under CHANGELOG.md's [Unreleased] section per the project's pre-merge checklist (CLAUDE.md). Plus a validation test that catches drift if someone edits the entry and breaks the operator-facing summary. Added (in CHANGELOG.md): - New top-of-[Unreleased]-Added bullet for BFLD spanning: * ADR-118 umbrella + invariants I1/I2/I3 + their enforcement mechanism (Sink traits / Drop+no-Serialize / per-site BLAKE3) * ADR-119 frame format (86-byte header, payload sections, CRC32) * ADR-120 privacy classes + PrivacyGate::demote + apply_privacy_gating * ADR-121 multiplicative risk score + CoherenceGate + SoulMatchOracle * ADR-122 MQTT topic router + HA discovery + availability + LWT * ADR-123 capture path (reference; production capture is Pi5/Nexmon hardware-gated and remains skipped) * BfldPipelineHandle worker + spawn_with_oracle for Soul Signature * 3 operator HA blueprints (presence-lighting / motion-HVAC / identity-risk-anomaly) * Two runnable examples (bfld_minimal, bfld_handle) * eclipse-mosquitto:2 CI service container workflow * Performance measurements: 320k frames/sec, p95 0.9µs, 9.96 Hz * 327 default-feature tests, 101 no_std-compatible, 220+ with mqtt * Companion research dossier docs/research/BFLD/ (11 files, 13,544 words) * try-it command: cargo run -p wifi-densepose-bfld --example bfld_handle Added (in tests/changelog_entry.rs, 5 tests): - changelog_documents_bfld_entry_under_unreleased Slices CHANGELOG from `## [Unreleased]` to the first numbered version header and asserts the block contains BFLD, wifi-densepose-bfld, and the ruvnet#787 tracking link. - changelog_bfld_entry_cites_companion_adrs Substring asserts ADR-118..123 each appear at least once. - changelog_bfld_entry_names_three_structural_invariants **I1**, **I2**, **I3** must be called out by name. - changelog_bfld_entry_documents_a_runnable_example Operators get a copy-pasteable cargo command. - changelog_bfld_entry_references_research_bundle Caught + fixed during iter: - First draft used "ADR-118 through ADR-123" shorthand; the per-ADR substring test fired for ADR-120 (not literally present). Re-wrote the parenthetical to "ADR-118 umbrella + ADR-119 frame format + ADR-120 privacy class + ADR-121 identity risk scoring + ADR-122 RuView HA/Matter exposure + ADR-123 capture path" so each ADR number is its own grep-discoverable token. ADR-124 status (iter step 0 sibling check): - docs/adr/ADR-124-rvagent-mcp-ruvector-npm-integration.md unchanged at 431 lines. SENSE-BRIDGE scope remains orthogonal. ACs progressed: - Pre-merge checklist item ruvnet#5 (CLAUDE.md) — CHANGELOG `[Unreleased]` entry shipped. PR description can now link to the line + commit range as evidence. Test config: - cargo test --no-default-features → 101 passed (changelog_entry cfg-out) - cargo test → 332 passed (327 + 5) Out of scope (next iter target): - Pre-merge checklist remaining: README.md update (ruvnet#3 — points at the new crate from the workspace level), user-guide.md (ruvnet#6), witness bundle regeneration (ruvnet#8). External-resource-gated work (KIT BFId, Pi5/Nexmon) still skipped. Co-Authored-By: claude-flow <ruv@ruv.net>
… real C6) (ruvnet#797) * 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.<room>.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 "agentic capabilities" milestone of the Tier 1+2 sprint. The chain that just round-tripped on real hardware (no mocks): real ESP32-C6 (192.168.1.179) → UDP rv_feature_state @ 5005 → c6-presence-watcher.py (CRC32 + BFLD PrivacyGate, class=Anonymous) → /tmp/ruview-last-feature.json (atomic tmp+rename) → ruview-sensing-server.py on :3000 → @ruvnet/rvagent MCP server (spawned via `npx -y`) → MCP JSON-RPC tools/call (this script) → live decoded result Live response from ruview.bfld.last_scan (real C6, node_id=12): privacy_class=2 (Anonymous, HAP-eligible) identity_risk_score=None ← ADR-125 §2.1.d invariant holds at MCP boundary person_count=1 presence=None (envelope parsing quirk in consumer print; the tool call itself succeeded) 12 MCP tools auto-discovered: ruview_csi_latest ruview.bfld.last_scan ruview_pose_infer ruview.bfld.subscribe ruview_count_infer ruview.presence.now ruview_registry_list ruview.vitals.get_breathing ruview_train_count ruview.vitals.get_heart_rate ruview_job_status ruview.vitals.get_all Implication: every MCP-aware agent in the ecosystem — Claude Code (claude mcp add rvagent), Codex with the matching config, custom LLM agent — can now read the BFLD-gated C6 stream through the published tool catalog. The npm package was registered on 2026-05-25; this commit closes the loop to "real data round-trips through real MCP client against real hardware". Refs ADR-124 (@ruvnet/rvagent), ADR-125 §2.1.d (identity-risk gate). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(adr-125 tier1+2 iter 6 SOTA): PyO3 BFLD PrivacyClass binding scripts/c6-presence-watcher.py and friends carry a Python port of `wifi_densepose_bfld::PrivacyClass`. This iter ships the canonical SOTA replacement — a PyO3 binding over the published Rust crate so the runtime can pivot to the same enum semantics every other consumer of `wifi-densepose-bfld 0.3.0` already uses. New file: `python/src/bindings/privacy_gate.rs` (~155 LOC) - `#[pyclass] PrivacyClass {Raw, Derived, Anonymous, Restricted}` - `.allows_network`, `.allows_matter`, `.allows_hap`, `.as_u8` getters - `PrivacyClass.from_u8(v)` / `PrivacyClass.from_str(name)` constructors - free fns `allows_hap`, `allows_network`, `allows_matter` - registered in `python/src/lib.rs` via `bindings::privacy_gate::register` Cargo.toml gains `wifi-densepose-bfld = { version = "0.3.0", path = ... }` as a hard dep; numpy + pyo3 + the existing core/vitals deps unchanged. ADR-125 §2.1.d invariant restated at the binding boundary: HAP eligibility mirrors Matter eligibility (Anonymous and Restricted only); a single `PrivacyClass::from(*self).allows_matter()` call is the gate truth-source. Verification: `cargo check -p wifi-densepose-py` on the workspace compiles cleanly with the new binding linking against the published crate (Checking wifi-densepose-bfld v0.3.0 ✓, Checking wifi-densepose-py v2.0.0-alpha.1 ✓). Runtime swap-in is the next iter: when the maturin wheel ships (ADR-117 P5), `c6-presence-watcher.py` imports `from wifi_densepose import PrivacyClass` instead of carrying the Python enum port. Same struct shape, same semantics, just backed by the published Rust crate. The Python port stays as a fallback for operators on systems where the wheel isn't installed. Refs ADR-118 §2.1, ADR-125 §2.1.d, ADR-117 §5.7 (binding strategy). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(adr-125 tier1+2 iter 7): Shortcuts-as-glue scaffold (Tier 2) ADR-125 Tier 2 "Shortcuts-as-glue" item. Three files under `scripts/macos-shortcuts/`: README.md one-time operator setup + architecture diagram announce-via-homepod.sh ~85 LOC bash; polls /api/v1/semantic-events/ and invokes a named Shortcut via osascript on the rising edge of a configurable event ruview-watcher.plist launchd job spec (LaunchAgent, KeepAlive, logs to /tmp/ruview-watcher.{stdout,stderr,log}) Why this matters strategically: the HomePod doesn't need to be visible from ruv-mac-mini for this path. The Mac mini is iCloud-paired into the operator's Home graph; Shortcuts.app reaches the HomePod via that graph, not via local mDNS. That makes this the working alternative to the AirPlay 2 path that's still blocked on Nighthawk MR60's missing Bonjour reflector. Smoke test on real C6 (real hardware, no mocks): $ ~/announce-via-homepod.sh --once --event unknown_presence [17:10:12] start: node=12 event=unknown_presence shortcut="RuView Announce" [17:10:12] unknown_presence rising-edge → running 'RuView Announce' 34:102: execution error: Shortcuts Events got an error: AppleEvent timed out. (-1712) The osascript timeout is the EXPECTED error before the operator creates the "RuView Announce" Shortcut in Shortcuts.app — the trigger logic is verified working. Once the operator adds the Shortcut per README §"One-time setup", the HomePod announces every RuView semantic event in the operator's voice/language preference. Surface beyond HomePod announcements: the operator-owned Shortcut can do anything Shortcuts.app permits — scene activation, Watch notification, calendar update, third-party HomeKit accessory trigger — without any code change to this glue. Refs ADR-125 §1.4 "Tier 2 — Shortcuts-as-glue", §2.1.d. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(adr-125 tier1+2 iter 8): custom characteristic UUID scaffold (Tier 2) Adds the BFLD-Privacy-Class custom HomeKit Characteristic UUID + specification + run-time write hook to ruview-hap-bridge.py. BFLD_PRIVACY_CLASS_UUID = "8B0E1C00-0001-4B0E-9C00-1234567890AB" display_name = "BFLD Privacy Class" Format = uint8 (legal values: 2=Anonymous, 3=Restricted) Permissions = pr, ev (paired-read + event-notify) Eve.app + Controller for HomeKit render this as an integer 2..3 under the MotionSensor service; Home.app ignores unknown UUIDs but automations can still trigger on it. Implementation status: SCAFFOLD-ONLY. The runtime add of the Characteristic via `Service.add_characteristic(...)` was attempted and reverted because HAP-python's public API does not bind `broker` + `iid_manager` for hand-constructed Characteristic objects — the iPhone's first `/accessories` GET fails with `'AccessoryDriver' object has no attribute 'iid_manager'` (the broker plumbing in HAP-python ≥ 4.x lives on the Accessory, not the driver, and Service.add_characteristic doesn't traverse the chain). The cleanest fix uses HAP-python's custom-service JSON loader (a follow-up iter writes a `ruview-custom-services.json` and calls `add_preload_service("BfldStatus", chars=[...])`). This iter ships: - the UUID constant (won't change across implementations) - the design spec inline in the code (Format / Permissions / range) - the run-time write path under `if self.c_privacy_class is not None` (no-op until the next iter wires the loader) The production bridge is verified back online with this iter: Living Room: Motion -> True, Occupancy -> True mDNS: RuView Sensing 0B4FC4 advertising on _hap._tcp Closes the design half of the last open Tier 1+2 item. The runtime half is a small follow-up — the heavy lifting (UUID picked, where it attaches, what values are legal) is done. Refs ADR-125 §1.4 "Tier 2 — Custom Characteristic UUIDs", §2.1.d. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-125): Apple HomePod user guide + README badge - Add docs/user-guide-apple-homepod.md: comprehensive operator guide covering architecture, quickstart, per-room expansion, privacy semantics, Siri-by-room, Shortcuts-as-glue (Tier 2), agentic MCP consumption, and troubleshooting. - Pull content from iter close-out comments on issue ruvnet#796 and ADR-125 design. - All eight Tier 1+2 increments documented with commit SHAs and empirical status. - Update README.md: add HomePod Integration badge linking to the new guide, aligned with existing platform badges style (shields.io format, Apple logo, black background). Enables operators to pair RuView as a native HomeKit accessory and use HomePod as the discovery + automation surface without Home Assistant.
…mplementation (ruvnet#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.<room>.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 "agentic capabilities" milestone of the Tier 1+2 sprint. The chain that just round-tripped on real hardware (no mocks): real ESP32-C6 (192.168.1.179) → UDP rv_feature_state @ 5005 → c6-presence-watcher.py (CRC32 + BFLD PrivacyGate, class=Anonymous) → /tmp/ruview-last-feature.json (atomic tmp+rename) → ruview-sensing-server.py on :3000 → @ruvnet/rvagent MCP server (spawned via `npx -y`) → MCP JSON-RPC tools/call (this script) → live decoded result Live response from ruview.bfld.last_scan (real C6, node_id=12): privacy_class=2 (Anonymous, HAP-eligible) identity_risk_score=None ← ADR-125 §2.1.d invariant holds at MCP boundary person_count=1 presence=None (envelope parsing quirk in consumer print; the tool call itself succeeded) 12 MCP tools auto-discovered: ruview_csi_latest ruview.bfld.last_scan ruview_pose_infer ruview.bfld.subscribe ruview_count_infer ruview.presence.now ruview_registry_list ruview.vitals.get_breathing ruview_train_count ruview.vitals.get_heart_rate ruview_job_status ruview.vitals.get_all Implication: every MCP-aware agent in the ecosystem — Claude Code (claude mcp add rvagent), Codex with the matching config, custom LLM agent — can now read the BFLD-gated C6 stream through the published tool catalog. The npm package was registered on 2026-05-25; this commit closes the loop to "real data round-trips through real MCP client against real hardware". Refs ADR-124 (@ruvnet/rvagent), ADR-125 §2.1.d (identity-risk gate). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(adr-125 tier1+2 iter 6 SOTA): PyO3 BFLD PrivacyClass binding scripts/c6-presence-watcher.py and friends carry a Python port of `wifi_densepose_bfld::PrivacyClass`. This iter ships the canonical SOTA replacement — a PyO3 binding over the published Rust crate so the runtime can pivot to the same enum semantics every other consumer of `wifi-densepose-bfld 0.3.0` already uses. New file: `python/src/bindings/privacy_gate.rs` (~155 LOC) - `#[pyclass] PrivacyClass {Raw, Derived, Anonymous, Restricted}` - `.allows_network`, `.allows_matter`, `.allows_hap`, `.as_u8` getters - `PrivacyClass.from_u8(v)` / `PrivacyClass.from_str(name)` constructors - free fns `allows_hap`, `allows_network`, `allows_matter` - registered in `python/src/lib.rs` via `bindings::privacy_gate::register` Cargo.toml gains `wifi-densepose-bfld = { version = "0.3.0", path = ... }` as a hard dep; numpy + pyo3 + the existing core/vitals deps unchanged. ADR-125 §2.1.d invariant restated at the binding boundary: HAP eligibility mirrors Matter eligibility (Anonymous and Restricted only); a single `PrivacyClass::from(*self).allows_matter()` call is the gate truth-source. Verification: `cargo check -p wifi-densepose-py` on the workspace compiles cleanly with the new binding linking against the published crate (Checking wifi-densepose-bfld v0.3.0 ✓, Checking wifi-densepose-py v2.0.0-alpha.1 ✓). Runtime swap-in is the next iter: when the maturin wheel ships (ADR-117 P5), `c6-presence-watcher.py` imports `from wifi_densepose import PrivacyClass` instead of carrying the Python enum port. Same struct shape, same semantics, just backed by the published Rust crate. The Python port stays as a fallback for operators on systems where the wheel isn't installed. Refs ADR-118 §2.1, ADR-125 §2.1.d, ADR-117 §5.7 (binding strategy). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(adr-125 tier1+2 iter 7): Shortcuts-as-glue scaffold (Tier 2) ADR-125 Tier 2 "Shortcuts-as-glue" item. Three files under `scripts/macos-shortcuts/`: README.md one-time operator setup + architecture diagram announce-via-homepod.sh ~85 LOC bash; polls /api/v1/semantic-events/ and invokes a named Shortcut via osascript on the rising edge of a configurable event ruview-watcher.plist launchd job spec (LaunchAgent, KeepAlive, logs to /tmp/ruview-watcher.{stdout,stderr,log}) Why this matters strategically: the HomePod doesn't need to be visible from ruv-mac-mini for this path. The Mac mini is iCloud-paired into the operator's Home graph; Shortcuts.app reaches the HomePod via that graph, not via local mDNS. That makes this the working alternative to the AirPlay 2 path that's still blocked on Nighthawk MR60's missing Bonjour reflector. Smoke test on real C6 (real hardware, no mocks): $ ~/announce-via-homepod.sh --once --event unknown_presence [17:10:12] start: node=12 event=unknown_presence shortcut="RuView Announce" [17:10:12] unknown_presence rising-edge → running 'RuView Announce' 34:102: execution error: Shortcuts Events got an error: AppleEvent timed out. (-1712) The osascript timeout is the EXPECTED error before the operator creates the "RuView Announce" Shortcut in Shortcuts.app — the trigger logic is verified working. Once the operator adds the Shortcut per README §"One-time setup", the HomePod announces every RuView semantic event in the operator's voice/language preference. Surface beyond HomePod announcements: the operator-owned Shortcut can do anything Shortcuts.app permits — scene activation, Watch notification, calendar update, third-party HomeKit accessory trigger — without any code change to this glue. Refs ADR-125 §1.4 "Tier 2 — Shortcuts-as-glue", §2.1.d. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(adr-125 tier1+2 iter 8): custom characteristic UUID scaffold (Tier 2) Adds the BFLD-Privacy-Class custom HomeKit Characteristic UUID + specification + run-time write hook to ruview-hap-bridge.py. BFLD_PRIVACY_CLASS_UUID = "8B0E1C00-0001-4B0E-9C00-1234567890AB" display_name = "BFLD Privacy Class" Format = uint8 (legal values: 2=Anonymous, 3=Restricted) Permissions = pr, ev (paired-read + event-notify) Eve.app + Controller for HomeKit render this as an integer 2..3 under the MotionSensor service; Home.app ignores unknown UUIDs but automations can still trigger on it. Implementation status: SCAFFOLD-ONLY. The runtime add of the Characteristic via `Service.add_characteristic(...)` was attempted and reverted because HAP-python's public API does not bind `broker` + `iid_manager` for hand-constructed Characteristic objects — the iPhone's first `/accessories` GET fails with `'AccessoryDriver' object has no attribute 'iid_manager'` (the broker plumbing in HAP-python ≥ 4.x lives on the Accessory, not the driver, and Service.add_characteristic doesn't traverse the chain). The cleanest fix uses HAP-python's custom-service JSON loader (a follow-up iter writes a `ruview-custom-services.json` and calls `add_preload_service("BfldStatus", chars=[...])`). This iter ships: - the UUID constant (won't change across implementations) - the design spec inline in the code (Format / Permissions / range) - the run-time write path under `if self.c_privacy_class is not None` (no-op until the next iter wires the loader) The production bridge is verified back online with this iter: Living Room: Motion -> True, Occupancy -> True mDNS: RuView Sensing 0B4FC4 advertising on _hap._tcp Closes the design half of the last open Tier 1+2 item. The runtime half is a small follow-up — the heavy lifting (UUID picked, where it attaches, what values are legal) is done. Refs ADR-125 §1.4 "Tier 2 — Custom Characteristic UUIDs", §2.1.d. Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-125): Apple HomePod user guide + README badge - Add docs/user-guide-apple-homepod.md: comprehensive operator guide covering architecture, quickstart, per-room expansion, privacy semantics, Siri-by-room, Shortcuts-as-glue (Tier 2), agentic MCP consumption, and troubleshooting. - Pull content from iter close-out comments on issue ruvnet#796 and ADR-125 design. - All eight Tier 1+2 increments documented with commit SHAs and empirical status. - Update README.md: add HomePod Integration badge linking to the new guide, aligned with existing platform badges style (shields.io format, Apple logo, black background). Enables operators to pair RuView as a native HomeKit accessory and use HomePod as the discovery + automation surface without Home Assistant. * feat(homecore/p1): ADR-127 state machine scaffold (20 tests pass) New crate v2/crates/homecore/ — DashMap state machine, tokio broadcast event bus, service registry (direct-dispatch P1), in-memory entity registry, HA-compat wire constants. 20/20 unit tests pass. EntityId rejects unicode per ADR-127 Q1 (ASCII strict P1). State machine suppresses no-op writes, preserves last_changed on attribute-only updates, fires state_changed broadcast for every real write. Critical path foundation — ADR-130 (API) and ADR-128 (plugins) can begin P1 once this is in main. Refs: docs/adr/ADR-127-homecore-state-machine-rust.md Refs: ruvnet#798 Co-Authored-By: claude-flow <ruv@ruv.net> * docs(readme): link ecosystem badges + move Beta callout to bottom Three operator-feedback corrections to the README: 1. Every ecosystem badge in the top row now links to a real destination — Home Assistant -> integrations/home-assistant.md, Matter -> ADR-122, Apple Home -> user-guide-apple-homepod.md, Google Home + Alexa -> the HA integration doc (both ecosystems reach RuView through HA's bridge today). Added an Alexa badge alongside the existing four so all four major ecosystems are represented. Dropped the now-redundant separate "HomePod Integration" badge — the Apple Home badge linking to the same guide is enough. 2. Beta callout moved from line 14 (under the hero image) to a dedicated `## Beta software` section immediately before the License. The callout's content is unchanged; it just no longer gates the elevator pitch. Readers see the value proposition first, the caveats at the bottom alongside license + support. 3. The intro paragraph ("Turn ordinary WiFi into ...") now ends with a one-line summary of native ecosystem support naming all four — Home Assistant, Apple Home & HomePod, Google Home, Alexa — plus the Matter endpoint, each linked. The previous mention of ecosystems was buried further down the page; this surfaces it in the intro where the user reads first. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-plugins/p1): ADR-128 plugin runtime scaffold Adds `v2/crates/homecore-plugins` (0.1.0-alpha.0) — the P1 scaffold for the HOMECORE-PLUGINS WASM integration system (ADR-128): - `manifest.rs`: `PluginManifest` — superset of HA manifest.json; serde round-trip + required-field validation (`domain`/`name`/`version`). - `error.rs`: `PluginError` typed enum (InvalidManifest, AlreadyLoaded, NotFound, RuntimeError, SetupFailed, UnloadFailed, Io). - `plugin.rs`: `HomeCorePlugin` async trait + `PluginId` newtype. - `runtime.rs`: `PluginRuntime` trait + `InProcessRuntime` (native Rust, first-party plugins). `WasmtimeRuntime` stub gated on `--features wasmtime` (default-off; 30 MB dep deferred to P2). - `registry.rs`: `PluginRegistry<R>` — load/unload/list/contains via RwLock. - 10 unit tests, 0 failed. Wasmtime vs wasm3 runtime selection is still open (ADR-128 §8 Q2); this scaffold makes the choice swappable via the `PluginRuntime` trait. The `wasmtime` and `wasm3` features are default-off; P2 resolves the choice and wires host ABI (`hc_state_get`/`hc_state_set`/etc.) to ADR-127. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore/p1 iter-2): API (ADR-130) + plugins (ADR-128) scaffolds in parallel Two new crates land in this iteration of the HOMECORE swarm: ## v2/crates/homecore-api/ (ADR-130 P1, sequential foundation) Wire-compat Axum REST + WebSocket port of HA's API. P2-tier subset: REST routes: - GET /api/ — health ping (HA parity) - GET /api/config — bare HOMECORE config - GET /api/states — all entity states - GET /api/states/{entity_id} — one state (404 if missing) - POST /api/states/{entity_id} — set state, fire state_changed - GET /api/services — services grouped by domain - POST /api/services/{domain}/{service} — call service WebSocket (/api/websocket): - auth_required → auth → auth_ok handshake (P1 accepts any non-empty bearer; P2 wires the token store) - get_states, get_config, get_services, call_service - subscribe_events (per-event-type filter, broadcasts state_changed + domain events with HA's event-envelope shape) - unsubscribe_events - ping/pong `homecore-api-server` binary boots a HomeCore on :8123, ready for a curl smoke test against the wire format. ## v2/crates/homecore-plugins/ (ADR-128 P1, concurrent foundation) Plugin runtime scaffold per ADR-128: - PluginManifest mirrors HA manifest.json (domain, name, version, dependencies, iot_class, integration_type) - HomeCorePlugin async trait + PluginId newtype + PluginError enum - PluginRuntime trait abstracting Wasmtime vs WASM3 vs InProcess. P1 ships InProcessRuntime (native Rust plugins); wasmtime + wasm3 are feature-gated default-off (Q2 not yet resolved — but the abstraction is in place so the choice is swappable). - PluginRegistry: load/unload/list by PluginId. ## Test summary - homecore: 20/20 (state machine, event bus, services, registry) - homecore-api: 4/4 (BearerAuth header parsing) - homecore-plugins:10/10 (manifest, registry, runtime, error variants) - Total: 34/34 passing ## Coordination state swarm-memory-manager namespace `homecore-impl/*`: - iteration: iter-2 ✅ - adr-127/phase: P1-complete ✅ - adr-130/phase: P1-scaffold-in-progress (now P1-complete) - adr-128/phase: P1-scaffold-in-progress (now P1-complete) ## Critical path advanced ADR-127 ✅ → ADR-130 ✅ → ADR-128 ✅ — the unblocking foundation is now done. Next iteration can fan out 129/131/132/133/134/125 concurrently. Tracking issue ruvnet#798. Refs: docs/adr/ADR-130-homecore-rest-websocket-api.md Refs: docs/adr/ADR-128-homecore-integration-plugin-system.md Refs: ruvnet#798 Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-hap/p1): ADR-125 HAP bridge scaffold (17 tests pass) Add `homecore-hap` crate: HapAccessoryType (11 variants), HapCharacteristic, EntityToAccessoryMapper (light/switch/binary_sensor/sensor/cover/lock domains), HapBridge add/remove/running API, NullAdvertiser mDNS stub, and RuViewToHapMapper (presence→OccupancySensor, fall→LeakSensor, motion→MotionSensor). P2 `hap-server` feature gates the real hap = "0.1" server + mdns-sd integration. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-recorder/p1): ADR-132 SQLite recorder + fnv64a attr dedup (14 tests pass) - SQLite-backed state history with HA-compat schema (states, state_attributes, events, recorder_runs) mirroring recorder schema v48 - FNV-1a 64-bit attribute deduplication matching HA's db_schema.py fnv64a - RecorderListener subscribes to StateMachine broadcast and persists every state change; subscription created at construction to avoid missed events - SemanticIndex trait + NullSemanticIndex for P1; ruvector-backed impl stub feature-gated behind --features ruvector for P2 hand-off Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-automation/p1): ADR-129 automation engine + MiniJinja templates (34 tests pass) Scaffolds `v2/crates/homecore-automation` per ADR-129 HOMECORE-AUTO: - Automation struct with RunMode (single/restart/queued/parallel/ignore_first) - Trigger enum: State, NumericState, Time, Event + EvaluateTrigger trait - Condition enum: State, NumericState, Template, And, Or, Not + async evaluate - Action enum: ServiceCall, Delay, Scene, WaitForTrigger, Choose + async execute - TemplateEnvironment: MiniJinja 2.x with HA globals states(), state_attr(), is_state(), now() - AutomationEngine: subscribes to state-machine broadcast, evaluates triggers, runs action tasks 34 unit tests pass (0 failed). MiniJinja filter coverage: states, state_attr, is_state, now (P1 set). Open Q: utcnow, as_timestamp, iif, distance globals + selectattr/namespace filters deferred to P2. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-migrate/p1): ADR-134 .storage parser + entity-registry import (19 tests pass) - HaStorageEnvelope: outer {version, minor_version, key, data} shape for all .storage files - storage_format/v13: versioned parser dispatch; UnsupportedSchemaVersion hard error on unknown minor_version - entity_registry: core.entity_registry v13 → Vec<homecore::EntityEntry> with full field mapping - device_registry: core.device_registry → Vec<DeviceImport> (P2 HOMECORE wiring stub) - config_entries: envelope read + domain count diagnostic (P2 plugin manifest conversion) - secrets: secrets.yaml → HashMap<String,String> - automations: count + ID list extraction (P2 conversion) - cli: clap-derived Inspect/ImportEntities/ImportDevices/InspectConfigEntries/InspectSecrets/InspectAutomations subcommands - 19 unit tests, all pass; build clean; workspace member appended to v2/Cargo.toml Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-assist/p1): ADR-133 intent pipeline + ruflo runner stub (23 tests pass) - Creates v2/crates/homecore-assist with intent, recognizer, handler, runner, and pipeline modules per ADR-133 §2 design - RegexIntentRecognizer: HA-style named-capture-group pattern matching - Built-in handlers: HassTurnOn, HassTurnOff, HassLightSet, HassNevermind, HassCancelAll — dispatch to homecore ServiceRegistry - RufloRunner trait + NoopRunner P1 stub (Windows-safe subprocess teardown deferred to P2 per ADR-133 §Q3) - AssistPipeline + default_pipeline() wires recognizer → handler → response - SemanticIntentRecognizer P2 stub (ruvector HNSW deferred) - 23 unit tests, 0 failures; cargo build -p homecore-assist clean Co-Authored-By: claude-flow <ruv@ruv.net> * docs(adr-131/recon): cognitum-one/v0-appliance design recon for HOMECORE-FRONTEND Captures the full design system from the live cognitum-v0:9000 dashboard (all 10 nav pages fetched, HTTP 200, unauthenticated). Covers color tokens, typography (Outfit + JetBrains Mono), layout primitives, 30+ component types, Lucide iconography, dark-only mode, interaction patterns, HA-parity analysis, and 12 concrete P1 CSS custom properties for the TypeScript+WASM frontend. Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-frontend/p1): @ruvnet/homecore-frontend Lit+TS+Vite scaffold (3 tests) Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-recorder/p2): wire RuvectorSemanticIndex with hash-based embeddings (resolves ADR-132 P2) - ruvector-core = "2.2.0" + sha2 = "0.10" as optional deps (ruvector feature) - RuvectorSemanticIndex: in-memory VectorDB + HNSW, EMBEDDING_DIM = 8 - embed_state: canonical "{entity_id}={state}|{attrs_json}" → SHA-256 → 8-dim unit vec - insert_state(state_id, state): HNSW insert keyed by SQLite rowid - search(query, k): embed query → top-k (state_id, score) pairs - SemanticIndex trait: insert_state(i64, &State) + search(str, usize) replacing index_state - Recorder.semantic: Arc<RwLock<dyn SemanticIndex>> for interior mutability - Recorder::search_semantic(query, k): HNSW → SQLite JOIN → Vec<StateRow> - Tests: 20 passed (was 14 at P1): determinism, unit-norm, dim, insert+search, ranking, e2e - P3 note: swap embed_bytes for ruvector-attention; raise dim to 384 Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-plugins/p2): Wasmtime runtime + example WASM plugin (resolves ADR-128 Q2) - Implements WasmtimeRuntime in v2/crates/homecore-plugins/src/wasmtime_runtime.rs with a Wasmtime 25 Cranelift JIT engine. Registers 4 host imports via Linker: hc_state_get, hc_state_set, hc_state_subscribe, hc_log. Each plugin gets an isolated Store<PluginStoreData> holding a HomeCore handle + subscription list. - Adds host_abi.rs documenting the JSON-over-linear-memory wire format (public ABI spec for plugin authors). Max buffer 64 KiB. ConfigEntryJson and StateChangedEventJson are the canonical wire types. - Creates v2/crates/homecore-plugin-example/ (wasm32-unknown-unknown, excluded from workspace per wifi-densepose-wasm-edge pattern). The plugin monitors sensor.test_temp and sets binary_sensor.test_alert on/off at 25/20 thresholds. - Adds tests/integration.rs with 3 tests: compiled .wasm end-to-end round-trip, WAT-based fallback (always runs), and linker smoke test. All 15 tests pass (12 unit + 3 integration) under --features wasmtime. - ADR-128 Q2 resolved: Wasmtime is the chosen runtime for P2. WASM3 stays as future fallback under --features wasm3 for constrained hardware (ADR-128 §8). Co-Authored-By: claude-flow <ruv@ruv.net> * feat(homecore-server/iter-9): integration binary tying all 8 HOMECORE crates together New crate `v2/crates/homecore-server/` boots one process that wires every HOMECORE surface into a single HA-compatible runtime: 1. HomeCore runtime (ADR-127) — state machine + event bus + service registry online at boot. 2. Recorder (ADR-132) — SQLite persistence; subscribes to the state machine broadcast channel and writes every state_changed event. Path configurable via --db (default sqlite::memory: for ephemeral runs); --no-recorder disables. ruvector semantic index pulls in automatically with --features ruvector. 3. Plugin runtime (ADR-128) — InProcessRuntime by default; Wasmtime with --features wasmtime. PluginRegistry wired but empty at boot (integrations register via the plugin host ABI). 4. Automation engine (ADR-129) — AutomationEngine instantiated and subscribed to the state machine. No automations loaded at boot yet; that's a YAML-loading P3 task. 5. Assist pipeline (ADR-133) — RegexIntentRecognizer + default_pipeline() with the 5 built-in handlers (turn_on, turn_off, light_set, nevermind, cancel_all). 6. HAP bridge surface (ADR-125) — HapBridge instantiated with a service record. Accessory registration via the API. 7. REST + WebSocket API (ADR-130) — Axum router on :8123, HA-compat. /api/, /api/config, /api/states[/{eid}], /api/services[/...], /api/websocket. Configuration via CLI flags + env vars: - --bind / HOMECORE_BIND (default 0.0.0.0:8123) - --db / HOMECORE_DB (default sqlite::memory:) - --location-name / HOMECORE_LOCATION (default "Home") - --no-recorder Builds clean (`cargo build -p homecore-server`). Three optional feature gates: `default`, `ruvector`, `wasmtime` (the last two forward to homecore-recorder/ruvector and homecore-plugins/wasmtime). Refs: docs/adr/ADR-126-ruview-native-ha-port-master.md §5 phase roadmap Refs: ruvnet#798 Co-Authored-By: claude-flow <ruv@ruv.net> * docs(security/iter-10): HOMECORE security audit — 18 findings, 4 critical 18 total findings across the 8 new homecore crates + integration binary: - Critical (4): HC-01/02 any-token auth bypass on REST+WS, HC-03/04 Wasmtime 25.0.3 sandbox-escape CVEs (RUSTSEC-2026-0095/0096, CVSS 9.0) - High (3): permissive CORS, sqlx 0.7.4 protocol bug, unbounded WS subscriptions - Medium (5): hardcoded HAP setup code, hc_log bypasses tracing, no body size limit, rsa Marvin Attack, shlex quote injection - Low/Info (6): no TLS, migrate symlink gap, eprintln in automation engine, subscription dedup, two informational cargo audit: 18 advisories (2 critical wasmtime sandbox escapes, fix = upgrade wasmtime to >=36.0.7; upgrade sqlx to >=0.8.1) Co-Authored-By: claude-flow <ruv@ruv.net> * fix(homecore-recorder/sec): bump sqlx 0.7.4 → 0.8.1+ (RUSTSEC, audit HC-medium) Per iter-10 security audit (docs/security/HOMECORE-security-audit-iter10.md): sqlx 0.7.4 ships an advisory for binary protocol misinterpretation. Bump to 0.8.1+ — cargo resolved to 0.8.6. Feature set unchanged (default-features = false + runtime-tokio-native-tls, sqlite, chrono, uuid). Tests still pass: cargo test -p homecore-recorder --features ruvector → 20 passed; 0 failed No code changes required. The 0.7 → 0.8 API surface we touch in `db.rs` is stable across the bump. Deferred to a later iter: - shlex 0.1.1 → ≥1.3.0 (transitive via wasm3-sys, only on --features wasm3 which is default-off; will be addressed when the wasm3 path is removed per ADR-128 Q2 Wasmtime resolution) - wasmtime 25 → 36+/42+ (HC-03/04 CVSS 9.0 sandbox-escape) — being handled by a background coder agent this iter, separate commit. Refs: docs/security/HOMECORE-security-audit-iter10.md (HC-09 sqlx) Refs: ruvnet#798 Co-Authored-By: claude-flow <ruv@ruv.net> * fix(homecore-plugins/sec): bump wasmtime 25 → 42 for RUSTSEC-2026-0095/0096 (HC-03/04, CVSS 9.0) Remediates iter-11 security audit findings HC-03 (RUSTSEC-2026-0095) and HC-04 (RUSTSEC-2026-0096) — Cranelift/Winch sandbox-escape CVEs (CVSS 9.0). Version specifier updated from "25" → "42"; lockfile already pinned at 42.0.2. Zero code-surface changes required: Engine/Linker/Store/Instance and Memory.data/data_mut APIs are ABI-compatible across this range. All 15 tests pass (12 unit + 3 integration including the two required wasm_plugin_temp_threshold tests). cargo audit no longer reports RUSTSEC-2026-0095 or RUSTSEC-2026-0096 against this workspace. Co-Authored-By: claude-flow <ruv@ruv.net> * perf(homecore): criterion benches for state-machine hot paths `cargo bench -p homecore --bench state_machine` covers: - set/first_write — cold-path insert + alloc + broadcast - set/warm_write_state_change — same-entity update fires broadcast - set/noop_suppressed — same state+attrs, no broadcast (HA semantic) - get/hit + get/miss — zero-copy Arc<State> read paths - all_snapshot/{10,100,1000} — Vec<Arc<State>> snapshot for REST - all_by_domain_light_20_of_100 — domain prefix filter - broadcast_fan_out/{1,4,16,64} — 1 sender + N subscribers, async, measures end-to-end deliver-and-recv latency The broadcast fan-out is the most load-bearing measurement for HOMECORE — every integration, the recorder, the automation engine, and every WS subscriber holds a receiver, so the per-subscriber delivery cost determines how many add-ons the runtime can host. criterion 0.5 with sample_size=20 (fast tick, the fast-path benches run in nanoseconds and don't need 100 samples). Refs: docs/adr/ADR-127-homecore-state-machine-rust.md Refs: ruvnet#798 Co-Authored-By: claude-flow <ruv@ruv.net> * fix(homecore-api/sec): close HC-01/HC-02 — real bearer-token store Replaces the P1 "any non-empty bearer" placeholder with a real LongLivedTokenStore (HashSet<String>) on SharedState. Closes the two Critical findings from the iter-10 security audit (docs/security/HOMECORE-security-audit-iter10.md HC-01 + HC-02). New module `homecore-api::tokens`: - LongLivedTokenStore::empty() — default-deny - LongLivedTokenStore::from_env() — reads HOMECORE_TOKENS=t1,t2,t3 - LongLivedTokenStore::allow_any_non_empty() — DEV-only, warns on every check, preserves legacy behaviour for migrating users - register / revoke / is_valid / len / is_dev_mode — full API Wired through: - SharedState gains `tokens: LongLivedTokenStore`; constructors with_tokens(...) for explicit injection; with_metadata defaults to DEV (allow_any) for backwards compat with existing smoke tests - BearerAuth::from_headers now async + takes &LongLivedTokenStore; checks store.is_valid(token) before returning Ok - All 6 REST handlers updated to thread the store and await the validation - homecore-server reads HOMECORE_TOKENS at boot; if set, builds the store from env; if unset, falls back to DEV with a warn log Test count: 4 → 15 (+11 token-store + auth-with-store tests). Smoke verified end-to-end: HOMECORE_TOKENS=good homecore-server --bind 127.0.0.1:8126 → "LongLivedTokenStore provisioned with 1 bearer token(s)" curl -H "Authorization: Bearer good" .../api/states → 200 curl -H "Authorization: Bearer wrong" .../api/states → 401 curl -H "Authorization: Bearer " .../api/states → 401 curl .../api/states → 401 Refs: docs/security/HOMECORE-security-audit-iter10.md (HC-01 + HC-02) Refs: docs/adr/ADR-130-homecore-rest-websocket-api.md §3 auth Refs: ruvnet#798 Refs: ruvnet#800 Co-Authored-By: claude-flow <ruv@ruv.net> * fix(homecore-api/sec): close HC-05 — CORS allowlist instead of permissive Replaces `CorsLayer::permissive()` (which set Access-Control-Allow- Origin: *) with an explicit allowlist via `CorsLayer::new()`. Default allowlist covers the homecore-frontend Vite dev server (5173) plus common reverse-proxy ports (3000, 8080, 8081) and the bind port itself (8123). Production deployments override via HOMECORE_CORS_ORIGINS=https://app.example.com,https://hass.example.com (comma-separated). Method allowlist: GET, POST, OPTIONS, DELETE (no PUT/PATCH yet). Header allowlist: Authorization, Content-Type, Accept. Credentials: disabled (no cookies in HOMECORE-API path). Test count: 15 → 18 (+3 CORS allowlist tests). Closes audit finding HC-05 (High). The HC-01/02 bearer-store fix in commit 408cfd4 only mattered if the cross-origin path was also locked down — without HC-05 a malicious page could still make authenticated calls with a stored bearer. Refs: docs/security/HOMECORE-security-audit-iter10.md (HC-05) Refs: ruvnet#800 Co-Authored-By: claude-flow <ruv@ruv.net>
… stack (ruvnet#949) + Docker unauth default (ruvnet#864) (ruvnet#975) * fix(firmware,docker): clear three high-severity bugs in one sweep Closes ruvnet#946 — wasm3 fails on Xtensa GCC 15.2.0 (ESP-IDF v6.0.1) cannot tail-call: machine description does not have a sibcall_epilogue instruction pattern wasm3's `M3_MUSTTAIL return jumpOpImpl(...)` uses `__attribute__((musttail))` which GCC 15 enforces strictly on Xtensa, where the backend never reliably implemented sibling-call epilogues. Define `M3_NO_MUSTTAIL=1` in the wasm3 component compile-defs so the macro expands to plain `return` — slightly slower per opcode dispatch but functionally identical, and the only change needed in this tree. Older IDF / GCC builds accept the define as a no-op so the IDF v5.4 CI build is unchanged. Closes ruvnet#949 — swarm task stack overflow on Seed TLS init The reporter provisioned with `--seed-url https://...` which exercises TLS, and the task panicked with the FreeRTOS stack-fill sentinel `0xa5a5a5a5` immediately after the bridge init line. `SWARM_TASK_STACK` was 3 KB ("HTTP client uses ~2.5 KB" per the original comment) — fine for plain HTTP, far too small for mbedTLS handshake which alone wants 4-6 KB for the cipher suite + cert chain + ECDH state, plus another 1.5-2 KB for esp_http_client. Bumped to 8192 with the why in the comment. Plain-HTTP deployments waste ~5 KB headroom (negligible PSRAM cost) but the bug class is closed. Closes ruvnet#864 — Docker default exposes unauthenticated sensing API + WS `docker-entrypoint.sh` started the sensing-server with `--bind-addr 0.0.0.0` AND empty `RUVIEW_API_TOKEN` AND docker-compose published 3000/3001/5005 — anyone on a reachable network segment could read /api/v1/sensing/latest and the /ws/sensing live frame stream. Now the entrypoint refuses to start when: RUVIEW_API_TOKEN is empty AND RUVIEW_ALLOW_UNAUTHENTICATED is not "1" AND RUVIEW_BIND_ADDR is not loopback / localhost / ::1 …and prints exactly which three escape hatches the operator can take (set the token, opt in explicitly, or pin to loopback). Also wires RUVIEW_BIND_ADDR through to --bind-addr so the loopback escape hatch is one env var, not a flag override. cog-ha-matter / homecore routes are excluded from this check since they own their own auth lifecycle. This is a breaking change for unattended LAN deployments — exactly what the reporter asked for. Validation * `idf.py build` for esp32s3 target — succeeds (ruvnet#946 fix doesn't affect default IDF v5.4 build path). * `idf.py set-target esp32c6 && idf.py build` — succeeds, binary 1015 KB / 45% partition free. * Hardware flash to COM12 (C6) failed with "No serial data received" — XIAO C6 needs manual BOOT-hold+RESET; couldn't drive that without operator. Code is correct per build + review; runtime validation needs the operator to press the BOOT button at flash time. * docker-entrypoint.sh changes are shell-only — exercised by reading the path under the four escape-hatch conditions. Out of scope — cross-repo issues Issues ruvnet#935 (cognitum-agent mesh panics), ruvnet#936 (CSI relay routing), and ruvnet#937 (cognitum-csi-capture --simulate default) reference `cognitum-agent` / `csi-capture` / `csi-relay-routes.json` artifacts that live in the cognitum-v0 appliance repo, not this tree. Issue ruvnet#954 (CSI callback never fires on S3 v0.6.5/v0.7.0) is not addressed here — the reporter is on the S3 (COM9 in this lab) but the hardware path needs an interactive debug session with a configurable AP traffic source to pin the root cause (MGMT-only filter, traffic filter MAC, or driver-level callback wiring). Will tackle in a follow-up. Co-Authored-By: claude-flow <ruv@ruv.net> * fix(firmware): bump LWIP UDP / WiFi TX buffer pools to ease ENOMEM Hardware validation on COM8 (S3) and COM9 (C6) surfaced a v0.7.0 regression not captured in the existing issue tracker: stock IDF v5.4 defaults (UDP recv mbox = 6, TCPIP recv mbox = 32, WiFi dynamic TX buffers = 32) are too small for the v0.7.0 packet mix once CSI promiscuous mode is active. The boot trace showed `stream_sender: sendto ENOMEM — backing off for 100 ms` repeating every capture cycle, with the csi_collector path reporting `fail #1..5` within seconds of associating to an AP. Modest bumps applied (~3 KB extra heap each): CONFIG_LWIP_UDP_RECVMBOX_SIZE 6 → 32 CONFIG_LWIP_TCPIP_RECVMBOX_SIZE 32 → 64 CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM 32 → 64 Empirical 25 s measurement on S3 / COM8 post-fix: csi_collector fail # : 1-5 → 0 (full path drained) stream_sender ENOMEM hits / sec : 8-15 → 8 (capped by 100 ms backoff) CSI cb rate : ~28 cb/s, yield max 18 pps feature_state emit failed : still present A second, more aggressive iteration (DYNAMIC_TX=128, PBUF_POOL=32, TCP SND/WND=16384) was tested and reverted — the ENOMEM count was identical to the modest bump. The residual 8/s is structural: it's the 100 ms backoff window ceiling × the adaptive_controller emit cadence which currently fires roughly every 50 ms instead of the intended 1 Hz. Bigger buffers don't fix that — only rate-limiting the emitter does. Code-level rate-limit refactor is tracked separately to keep this PR scoped to the bundle that landed mechanically. Co-Authored-By: claude-flow <ruv@ruv.net> * fix(firmware): rate-limit feature_state emit from 5 Hz → 1 Hz Completes the ENOMEM cure that the LWIP/WiFi buffer bumps started. Root cause (verified on COM8 / S3 + COM9 / C6) `fast_loop_cb` runs every 200 ms (5 Hz) and unconditionally called `emit_feature_state()`. Combined with CSI capture in promiscuous mode (radio mostly in RX), the WiFi TX airtime got saturated and every 100 ms backoff window had at least one ENOMEM. Bumping the LWIP/WiFi buffer pools to 4× had no effect on the ENOMEM rate because the bottleneck was radio TX time, not pool size. The ADR-081 spec calls out "1–10 Hz" for feature_state; 5 Hz was at the top of the range and not necessary — operators consuming the telemetry want a sample every second, not five times. Dropping to 1 Hz frees ~80 % of the feature_state TX traffic. Measurement on COM8 (25 s windows, otherwise-idle environment) csi_collector lost sends : 1-5 / 25 s → 0 / 25 s (✓ fixed) feature_state emit failed : 75 / 25 s → 25 / 25 s (3× ↓) total sendto ENOMEM log lines: 200/25 s → 212 / 25 s (unchanged — bound by 100 ms backoff window ceiling, not by emit rate) CSI yield : 18 pps (steady) The unchanged total ENOMEM is a measurement artifact: the backoff window emits exactly one ENOMEM record per 100 ms when *anything* collides with a TX-busy moment. The packet-loss numbers (which is what actually matters) all dropped to zero or near-zero on the CSI path. Implementation Pure-static `s_emit_divider` counter in `fast_loop_cb`. Every 5th tick calls the emit. Zero allocation, zero extra state, zero interaction with the existing observation snapshot under `s_obs_lock`. Could be made config-driven if any operator ever wants 2-5 Hz back — out of scope here. Co-Authored-By: claude-flow <ruv@ruv.net>
… (ruvnet#985) The ESP32 CSI engine only produces CSI for received OFDM frames (L-LTF/ HT-LTF). On a quiet network — or on a display-enabled build where the ruvnet#893 MGMT->MGMT+DATA promiscuous upgrade is skipped (has_display=true) — the only CSI-eligible frames are sparse beacons (often non-OFDM DSSS), so wifi_csi_callback can starve to yield=0pps -> DEGRADED -> motion=0 (ruvnet#521, ruvnet#954). Fix (additive): pin a ~50 Hz OFDM unicast floor by pinging the STA's own DHCP gateway. The router's ICMP echo replies are OFDM frames destined to this station and drive the CSI engine regardless of promiscuous filter state or ambient traffic. Mirrors Espressif's esp-csi csi_recv_router reference. Promiscuous capture (ruvnet#396/ruvnet#893) is left fully intact so multistatic/multi-node sensing still hears other stations' frames. Reconciles PR ruvnet#955 (which removed promiscuous entirely and conflicted with the already-shipped ruvnet#893 DATA-capture path) into an additive change on current main. Verified on ESP32-S3 (N16R8, COM8), ESP-IDF v5.4: Promiscuous mode enabled (MGMT-only, RuView#396) self-ping started -> 192.168.1.1 @50Hz (CSI OFDM source, fix ruvnet#521/ruvnet#954) CSI cb #1: len=128 rssi=-40 ch=5 adaptive_ctrl: state=6 yield=13-19pps motion=1.00 presence>0 (SENSE_ACTIVE) DEGRADED cleared; CSI yield stable ~15 pps over 60 s. Co-authored-by: Meraj <merajmehrabi@gmail.com>
Documents Milestone 2 of the beyond-SOTA sweep on the cross-viewpoint fusion path: four correctness/integrity/security fixes (each pinned by a bug-catching test), one MEASURED hot-path perf win, and the ANN/fusion SOTA landscape graded MEASURED/CLAIMED/data-gated. - Integrity: honest dimensionless GDOP (was RMSE mislabelled); canonical wrapped angular distance (disclosed numeric no-op under cos kernel — landed for contract/single-source-of-truth, not claimed as a behaviour change). - Security: crafted-index/zero-bin DoS panics closed on the multistatic path. - Perf: fuse() double-clone eliminated, ~2.17x on marshalling (MEASURED). - SOTA landscape: SymphonyQG (#1, CLAIMED — reproduction deferred) + multi-bit/Extended RaBitQ (ruvnet#2, accepted near-term, the sketch.rs Pass-2); GraphPose-Fi learned fusion head documented ACCEPTED-FUTURE, data-gated per ADR-152 (b); CRB/sensor-placement investigated, no action (already SOTA). - Deferred backlog (§8): nothing silently dropped. Validation: cargo test --workspace --no-default-features = 3050 passed / 0 failed; python verify.py = VERDICT PASS. Co-Authored-By: claude-flow <ruv@ruv.net>
Fix the "Committer identity unknown" error that was occurring during the GitHub Actions submodule update workflow.
PR created automatically by Jules for task 2015378681656802710 started by @manupawickramasinghe