Skip to content

Commit 20ad75f

Browse files
feat(ADR-131): HOMECORE-UI dashboard + BFF gateway — review-fixed (supersedes #1082) (#1099)
* feat(ADR-131): HOMECORE-UI operational dashboard + BFF gateway Complete two-tier Cognitum operator dashboard (ADR-131), served by homecore-server at /homecore, plus the single-origin BFF gateway that wires it to real backends. Front-end (zero-dep vanilla TS/JS + CSS, exact Cognitum design tokens): - All 10 panels (§4.1-4.10): dashboard, SEED fleet + detail, fleet map, entities (live WS subscribe_events, never polls), rooms, COGs, calibration wizard, events + automation builder, witness/audit, settings. - §6 UX invariants in code: first-class provenance, prominent stale/veto/ fragility, null(not-trained) vs withheld vs error, --mono everywhere, Hailo vs CPU COG distinction. - api.js calls the gateway routes in production; mock demoted to a dev-only ?demo=1 fixture (no mock in prod); typed error states. - Tests under plain node: import-graph, boot, render-smoke (22), interaction (3), prod-errors (13) — 5 files green; bundle ~137 KB (~37x smaller than HA), <2 ms/cold-render. BFF gateway (homecore-server/src/gateway.rs, compiled + tested on Rust 1.89): - /api/cal/* reverse-proxy to the calibration API (ADR-151). - GET /api/homecore/rooms with the RoomState adapter (breathing->breathing_bpm, heartbeat:null->heart_bpm:null, injected anomaly.threshold/room_id). - GET /api/homecore/cogs supervisor over /var/lib/cognitum/apps/. - GET /api/homecore/appliance from /proc + TCP service probes. - SEED-device/appliance routes return typed 503 upstream_unavailable. - cargo test -p homecore-server = 12/12; run live (curl-verified); fixed a real double-v1 proxy-URL bug found during live testing. Honest scope: W1/W2/W4/W6-appliance functional; W3/W5/W6-Hailo/federation return typed 503 (depend on services/hardware not in this repo). Co-Authored-By: claude-flow <ruv@ruv.net> * fix(homecore-ui): resolve code-review findings — SSRF guard, CORS/trace coverage, §6 honesty, crash guards Addresses the high-effort review of PR #1082: - SECURITY: cal_proxy rejects path-traversal/confused-deputy SSRF (`.`/`..` segments, backslash, %2e%2e/%2f, absolute) on raw+decoded forms → 400, before attaching the server-side calibration bearer. - CORRECTNESS: /api/homecore/* + /api/cal/* now covered by the shared CORS allowlist (build_cors_layer, exported from homecore-api) + TraceLayer — previously merged outside router()'s layers (no CORS, no tracing). - §6 HONESTY (no fabricated data): dashboard renders '—' for null metrics (not "null%"/"null°C"); cogs Hailo pill reflects the REAL appliance probe (not hardcoded "connected"); room anomaly threshold passed through / null, not a fabricated 0.5. - ROBUSTNESS: cogs asArray(hef) guards a non-array manifest field; calibration progress guards target<=0 (no NaN%/Infinity%); restart clears the poll timer. - CLEANUP: mock.js is now a cached DYNAMIC import (demo-only) — never bundled in production (§2.2). - New ui/tests/unit-fixes.mjs pins the above; ADR-131 + CHANGELOG updated. Co-Authored-By: claude-flow <ruv@ruv.net> --------- Co-authored-by: Nick Ruest <127058086+nicholas-ruest@users.noreply.github.com>
1 parent 1df6d1e commit 20ad75f

36 files changed

Lines changed: 5514 additions & 4 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Large diffs are not rendered by default.

docs/adr/ADR-131-homecore-ui-operational-dashboard.md

Lines changed: 444 additions & 0 deletions
Large diffs are not rendered by default.

v2/Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/crates/homecore-api/src/app.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ pub fn router(state: SharedState) -> Router {
4242
.with_state(state)
4343
}
4444

45-
fn build_cors_layer() -> CorsLayer {
45+
/// Build the audited CORS allowlist layer (HC-05). Exposed so the
46+
/// integration binary can apply the SAME allowlist to routes merged in
47+
/// outside `router()` (e.g. the ADR-131 BFF gateway), instead of leaving
48+
/// `/api/homecore/*` and `/api/cal/*` with no CORS coverage at all.
49+
pub fn build_cors_layer() -> CorsLayer {
4650
let raw = std::env::var("HOMECORE_CORS_ORIGINS").ok();
4751
let origins: Vec<HeaderValue> = match raw {
4852
Some(v) if !v.trim().is_empty() => v

v2/crates/homecore-api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub mod state;
77
pub mod tokens;
88
pub mod ws;
99

10-
pub use app::{router, AppState};
10+
pub use app::{build_cors_layer, router, AppState};
1111
pub use error::{ApiError, ApiResult};
1212
pub use state::SharedState;
1313
pub use tokens::LongLivedTokenStore;

v2/crates/homecore-server/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ clap = { version = "4", features = ["derive", "env"] }
3737
anyhow = "1"
3838
serde_json = "1"
3939
axum = { version = "0.7", features = ["macros"] }
40+
# Static-file serving for the HOMECORE-UI dashboard (ADR-131) mounted at
41+
# /homecore, request tracing, and the CORS allowlist applied to BOTH the
42+
# homecore-api routes AND the merged BFF gateway routes (ADR-131 §11).
43+
tower-http = { version = "0.6", features = ["fs", "trace", "cors"] }
44+
# BFF gateway (ADR-131 §11): reverse-proxy the calibration API + aggregate
45+
# upstreams. rustls is requested here, but NOTE this is a WORKSPACE-WIDE
46+
# concern: cargo feature-unification means a sibling crate that enables
47+
# reqwest's default `native-tls` re-introduces OpenSSL into the final binary
48+
# regardless of this opt-out. A real "no OpenSSL on the appliance" guarantee
49+
# requires every crate that pulls reqwest to align on rustls-only (tracked in
50+
# CHANGELOG / ADR-131 security note).
51+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
52+
serde = { version = "1", features = ["derive"] }
53+
# Concurrent fan-out of per-bank RoomState fetches in the gateway (§11 perf).
54+
futures = "0.3"
55+
56+
[dev-dependencies]
57+
# Drive the assembled router in integration tests via ServiceExt::oneshot.
58+
tower = { version = "0.5", features = ["util"] }
59+
http-body-util = "0.1"
4060

4161
[features]
4262
default = []

v2/crates/homecore-server/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,29 @@ export RUST_LOG="homecore=debug,homecore_api=info"
116116
| `--db` | `HOMECORE_DB` | `sqlite::memory:` | SQLite path (`:memory:` for ephemeral) |
117117
| `--location-name` | `HOMECORE_LOCATION` | `Home` | Friendly name returned by `/api/config` |
118118
| `--no-recorder` || off | Disable SQLite recorder (low-resource deployments) |
119+
| `--ui-dir` | `HOMECORE_UI_DIR` | `<crate>/ui` | HOMECORE-UI asset dir served at `/homecore` (ADR-131); empty disables the mount |
120+
121+
## HOMECORE-UI dashboard (ADR-131)
122+
123+
This binary also serves the **HOMECORE-UI** — the complete operational dashboard
124+
for the two-tier Cognitum stack (v0 Appliance → SEEDs → ESP32 nodes) — at
125+
`/homecore`, alongside the HA-compat `/api` surface. It is a zero-dependency,
126+
no-build-step vanilla TS/JS + CSS frontend living in `ui/`:
127+
128+
```bash
129+
cargo run -p homecore-server # then open http://localhost:8123/homecore/
130+
```
131+
132+
It drives the live `/api` + `/api/websocket` (`subscribe_events`) endpoints; panels
133+
backed by services not in this binary (SEED HTTPS API, calibration ADR-151,
134+
federation ADR-105) render against a DEMO-flagged contract-conformant mock until
135+
those endpoints land (ADR-131 §7.1). Frontend tests + benchmark run under plain
136+
`node` (no `npm install`):
137+
138+
```bash
139+
cd ui && npm test # import graph + render-smoke + interaction (24 checks)
140+
cd ui && npm run bench # bundle budget (~137 KB, ~37× smaller than HA) + render timing
141+
```
119142

120143
## Comparison to Home Assistant
121144

0 commit comments

Comments
 (0)