Commit Graph

13 Commits

Author SHA1 Message Date
ruv aeac5f5543 chore(worldgraph): extract geo+worldgraph+worldmodel to ruvnet/worldgraph submodule
- published as github.com/ruvnet/worldgraph (3-crate workspace, history via git-filter-repo)
- replace the 3 in-tree crates with one submodule at v2/crates/worldgraph
- parent workspace: drop the 3 members, exclude the submodule (it is its own workspace),
  repoint workspace.dependencies(worldmodel) + engine/sensing-server path-deps into it
- cargo metadata resolves clean (geo/worldgraph/worldmodel consumed from the submodule)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-16 14:14:34 -04:00
rUv df617145d6
feat(ADR-262 P3): live /api/field + /ws/field — RuView sensing speaks RuField (fail-closed egress) (#1071)
* feat(ADR-262 P3): live RuField surface — RuView sensing speaks RuField on /api/field + /ws/field

Wire the P1 `wifi-densepose-rufield` bridge into the live
`wifi-densepose-sensing-server` so the governed sensing cycle emits real
signed RuField `FieldEvent`s on two additive endpoints.

- Cargo: add the `wifi-densepose-rufield` path dep (the single coupling
  point, ADR-262 §5.4 — no new RuView-internal coupling).
- New `src/rufield_surface.rs` (kept out of the 8k-line main.rs):
  `FieldSurface` holds a dedicated ed25519 `Signer` + a bounded ring of
  recent events + the `/ws/field` broadcast topic; `GET /api/field` and
  `GET /ws/field` handlers; a standalone `router()` for isolated testing.
- Signer (defers the P2 key decision, ADR-262 §8 Q1): a STANDALONE
  dev/sensing key from `WDP_RUFIELD_SIGNING_SEED`, else a deterministic
  dev default with a logged WARN. Reusing the `cog-ha-matter` Ed25519
  key is the deferred P2 call — P3 does not pre-empt it.
- Tap: at the ESP32 governed-trust cycle (`main.rs` ~5886 observe_cycle
  / ~5938 SensingUpdate build), `emit_rufield_event` joins the cycle's
  features/classification/signal_field with the engine's
  effective_class/demoted trust state into a `SensingSnapshot` and
  surfaces it via the bridge. Existing endpoints (`/ws/sensing` etc.)
  are unchanged — purely additive.
- Privacy egress: `network_egress_allowed` is fail-closed for an
  unattended live surface — only P1/P2 leave the box; P0 raw and
  P3/P4/P5 (identity/biometric/aggregate) are held edge-local. A
  `Derived` cycle maps to P4/P5 and never surfaces.
- No-phantom: `emit` drops no-presence cycles (no fabricated events).

Gates (tests/rufield_surface_test.rs, tower::oneshot, 4/0): well-formed
signed event (WifiCsi, P2 not P1, is_fusable, real timestamp); empty
cycle → no phantom; Derived trust never surfaces; mixed stream surfaces
only egress-safe events.

Honesty (ADR-262 §0/§6): real plumbing on a live endpoint, NOT accuracy.
Single-link CSI with its existing caveats (no validated room-coordinate
accuracy); dedicated dev signing key pending the P2 ownership decision;
no accuracy claim.

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

* docs(ADR-262 P3): mark P1+P3 implemented; document /api/field + /ws/field; CHANGELOG

- ADR-262 Status → "P1 + P3 implemented"; add a P3 implementation-status
  block (tap site, endpoints, dedicated dev signer deferring the §8 Q1
  key decision, fail-closed egress, gates). Keep the honesty framing:
  real plumbing on a live endpoint, not accuracy.
- CHANGELOG [Unreleased]: add the ADR-262 P3 entry.
- user-guide: add `/api/field` to the REST table + a "RuField surface
  (ADR-262 P3)" section covering `/api/field` + `/ws/field`, the
  fail-closed P1/P2-only egress, the WDP_RUFIELD_SIGNING_SEED dev key,
  and the no-accuracy honesty note.

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

* ci: checkout submodules everywhere + Dockerfile copies vendor/rufield

Making wifi-densepose-rufield (ADR-262 bridge) a v2 workspace member means
EVERY cargo-on-workspace context must have the vendor/rufield submodule
present (cargo loads all member manifests). P1 only fixed the rust-tests
job; this adds `submodules: recursive` to all workflow checkouts that run
cargo (mqtt-integration was failing on the missing submodule manifest), and
makes Dockerfile.rust COPY vendor/rufield/ to /vendor/rufield (matches the
bridge's ../../../vendor/rufield path-dep under the collapsed Docker layout).
update-submodules.yml left alone (it manages submodules itself).

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

---------

Co-authored-by: ruv <ruvnet@gmail.com>
2026-06-14 13:55:41 -04:00
ruv 53b327e649 release: bump signal 0.3.4 / sensing-server 0.3.3 / cli 0.3.1 (fixes #1009, #1004)
HE20 calibration baseline fix (signal), sensing-server --source auto simulate-latch
fix (sensing-server), HE20 calibrate parser/asserts (cli). See PR #1038.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-12 16:55:27 -04:00
ruv 2e4461d64d release: bump 9 crates changed in the beyond-SOTA sweep for crates.io
vitals/wifiscan/hardware/nn 0.3.0->0.3.1, ruvector 0.3.1->0.3.2,
signal 0.3.2->0.3.3, train 0.3.1->0.3.2, mat 0.3.0->0.3.1,
sensing-server 0.3.1->0.3.2.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-11 22:41:21 -04:00
rUv 29de574e63
Beyond-SOTA engine/signal/train improvements: mesh partition guard, FFT CIR solver, canonical frame decoder, falsifiable occupancy benchmark, governed streaming, adapter provenance (#1018)
* docs(research): add RuView beyond-SOTA system review (00)

First document of the beyond-SOTA research series: capability audit of
the current RuView engine with role-to-crate maturity matrix, ruvsense
module inventory, gap analysis, and risk register.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* docs(research): add beyond-SOTA architecture design (02, in progress)

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* docs(research): finalize beyond-SOTA architecture (02)

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* docs(research): add benchmark/validation methodology snapshot (03)

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* docs(research): add beyond-SOTA series index with validation results; changelog

README index ties the 5 research docs together with the session's
measured validation evidence: 2,797 workspace tests / 0 failed, Python
proof PASS (bit-exact), and paired pre/post criterion CIR benchmarks.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* perf(signal): precompute CIR warm-start system; hoist tomography solver allocs

Exact, determinism-safe optimizations (bit-identical float results):

- cir.rs: diag(PhiH Phi)+lambda*I and its CSR matrix depend only on Phi
  and lambda (fixed at CirEstimator::new) but were rebuilt every frame
  (O(K*G) pass + CSR allocation). Now built once in new() via
  build_warm_start_system; summation order unchanged.
- tomography.rs: ISTA gradient buffer hoisted out of the 100-iteration
  loop (fill(0.0) reset) and the Frobenius Lipschitz bound moved from
  per-reconstruct to construction.

Verified: signal 456 tests green; engine 11/11 green including
cycle_is_deterministic and witness-stability tests. Criterion paired
pre/post: cir_estimate/he40 -3.9% (p<0.01), multiband -1.2/-1.4%.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* fix(worldgraph): bound SemanticState growth with deterministic retention

StreamingEngine::process_cycle appended one SemanticState belief per cycle
with no eviction — ~1.7M nodes/day at 20 Hz (beyond-SOTA roadmap finding #6).

Add WorldGraph::prune_semantic_states(max): deterministic eviction of the
oldest beliefs by (valid_from_unix_ms, id); structural nodes (rooms, zones,
sensors, anchors, tracks, events) are never eligible. Wire it into the
engine after each belief append (DEFAULT_SEMANTIC_RETENTION = 7,200, ~6 min
at 20 Hz; set_semantic_retention to tune). The WorldGraph holds current
beliefs; durable history is the recorder's job, so no audit data is lost.

3 new tests: end-to-end bounded growth, oldest-only eviction, deterministic
equal-timestamp tie-break. Workspace gate: 2,865 passed, 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* feat(sensing-server): route live frames through the governed StreamingEngine

Closes the live-trust-path gap (ADR-136 section 8, beyond-SOTA system review):
the running server fused live CSI with the bare MultistaticFuser, while the
privacy/provenance/witness control plane (ADR-135..146) only ever ran on
synthetic in-test frames. The privacy control plane was therefore bypassable
on the real path.

New engine_bridge module drives StreamingEngine::process_cycle from the
server's live NodeState map, reusing the existing NodeState -> MultiBandCsiFrame
conversion. It lazily wires each contributing node as a WorldGraph sensor
(idempotent), bounds belief growth via the retention cap, and forwards explicit
timestamps/calibration ids so the path stays deterministic and replayable.

Wired additively into both live ESP32/WiFi fusion sites in main.rs via a
split-borrow off the write guard, so person-count behavior is unchanged; the
latest BLAKE3 witness is stored on AppState. Every published belief now carries
evidence + model + calibration + privacy decision and a deterministic witness.

Adds wifi-densepose-engine/-worldgraph/-bfld/-geo deps. 6 new bridge tests
(witnessed belief with full provenance, cross-run determinism, idempotent node
registration, retention bound, privacy-mode propagation). sensing-server suite
430+128 green; workspace gate 2,904 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* feat(train): falsifiable occupancy benchmark with anti-overfitting gate

Makes the presence/person-count "beyond SOTA" claim falsifiable in code
instead of aspirational (the unfalsifiability gap from the beyond-SOTA system
review). occupancy_bench grades predictions vs ground truth and gates a SOTA
claim behind one claim_allowed invariant requiring ALL of:

- DataProvenance::Measured — synthetic/mock data is scorable for regression
  but never claimable (anti-mock-contamination; the CLAUDE.md Kconfig-bug
  lesson made structural).
- A leak-free EvalSplit — validate() refuses any split where a subject OR
  environment id appears in both train and test (subject leakage /
  per-environment overfitting).
- n_test >= min_test_samples (small-N guard).
- Presence F1 whose bootstrap-CI lower bound (deterministic seeded splitmix64)
  clears the threshold — not the point estimate.
- Count MAE within threshold.

The claim string is unreadable except through the gate (NO_CLAIM otherwise),
same discipline as the ruview-gamma acceptance gate. What remains is data, not
method: a frozen, SHA-pinned, subject/environment-disjoint measured replay set
turns the claim into a passing/failing test.

Lives in wifi-densepose-train (the eval bounded context, alongside ablation/
eval/metrics). 10 tests cover each refusal path; warning-clean under the
crate's missing_docs lint. Workspace gate 2,914 passed / 0 failed. Doc 03
updated.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* feat(engine): per-room adapter provenance + drift-to-recalibration advisor

Closes the trust-chain gap where an ~11 KB per-room LoRA adapter (ADR-150
section 3.4) could silently change inference without the witness noticing:
provenance carried only "rfenc-v<N>" with no notion of adapter identity.

- StreamingEngine::set_room_adapter(AdapterInfo): pins the adapter's
  content-derived id into provenance model_version
  ("rfenc-v1+adapter:<id>") — and therefore into the BLAKE3 witness — so
  swapping or clearing adapter weights always shifts the witness. Engine test
  proves base -> adapter -> other-adapter -> cleared all witness differently
  and cleared == base.
- RecalibrationAdvisor: recommends re-running the ADR-135 empty-room baseline
  / refitting the room adapter on sustained low fusion coherence (streak
  threshold, default 60 cycles ~ 3 s at 20 Hz) or an ADR-142 change-point.
  Surfaced as TrustedOutput::recalibration_recommended, stored on the
  sensing-server AppState alongside the witness at both live fusion sites.
- Bridge plumbing: EngineBridge::{set_room_adapter, clear_room_adapter} +
  live-path test that the adapter id flows into the live witness.

Scope note (honest): this is the deployable provenance/trigger half of the
"retrained model" roadmap item. Fitting the adapter itself runs in the
existing external calibration service (aether-arena/calibration/); a trained
RF-encoder checkpoint still does not exist in-tree.

Engine 15 tests, bridge 7 tests. Workspace gate: 2,918 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* fix(mat): gate api module behind its feature — standalone no-default-features builds

pub mod api was unconditional while its only dependency, serde, is optional
behind the 'api' feature, so any build without default features failed with
101 unresolved-serde errors (masked in --workspace runs by feature
unification). The api module and its create_router/AppState re-export are now
cfg(feature = "api")-gated with docsrs annotations.

All combos compile: bare --no-default-features (was 101 errors, now 0),
--no-default-features --features api, and full default (177 tests pass).
Workspace gate: 2,918 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* perf(signal): opt-in FFT operator for the CIR ISTA solver (8-14x measured)

Phi is a sub-DFT, so each ISTA mat-vec can run as one length-G FFT
(O(G log G)) instead of a dense O(K*G) product — the dominant-latency-hazard
finding from the beyond-SOTA optimization roadmap.

New CirConfig::fft_operator, default FALSE: the dense path stays the
bit-exact witness default. The FFT evaluates the same sums in a different
order, so enabling it shifts float results in the last bits and requires
regenerating any pinned witness — strictly opt-in per deployment.

FftOperator (rustfft, planned once at CirEstimator::new, scratch buffers
reused across the ISTA loop) dispatches inside ista_solve:
  Phi x   = scale * forward-FFT(x) sampled at bins (k_idx mod G)
  Phi^H v = scale * unnormalised inverse-FFT of v scattered into those bins
Warm-start and Lipschitz estimation stay dense at construction.

Measured (criterion, same run, same machine):
  ht20: 2.22 ms -> 265 us  (8.4x)
  ht40: 10.26 ms -> 717 us (14.3x)
The real HE40 grid (K=484, G=1452) scales further per the O(K*G)/O(G log G)
ratio.

3 new tests: FFT<->dense matvec equivalence to float tolerance on ht20 and
he40 grids; end-to-end dominant-tap agreement on a single-path frame; all
default configs keep FFT off. New cir_estimate_fft bench group.

Workspace gate: 2,921 passed / 0 failed (default path bit-exact, witnesses
unchanged).

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* feat(core): canonical frame decoder — capture-to-claim replay (ADR-136)

The encode half of the ADR-136 frame contract existed (ComplexSample,
to_canonical_bytes, witness_hash) but there was no decoder: a captured
canonical frame could be witnessed but never reconstructed, blocking
replay-from-capture.

CsiFrame::from_canonical_bytes is the exact inverse: same id, metadata,
complex payload, and witness hash (tested as the round-trip law AC7 — the
replayed frame re-encodes byte-identically). Amplitude/phase are recomputed
from the payload (projections, not independent state). Every malformed-input
class fails closed (AC8): header truncation -> Truncated, payload truncation
-> PayloadMismatch, unknown discriminants, non-UTF-8 device id, trailing
bytes. Nil calibration uuid decodes as None per the documented encoding.

Core: 36 tests pass. Workspace gate: 2,937 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* feat(engine): dynamic min-cut mesh partition guard (ruvector-mincut)

Maintains an exact min-cut over the live mesh coupling graph — nodes are
sensing nodes, coupling is the product of fusion attention weights — and
surfaces per cycle, as TrustedOutput::mesh:

- cut value: the global "how close is the array to partitioning" number,
  a structural measure per-node heuristics miss;
- weak side: which specific nodes would split off (failure/jamming triage,
  feeds ADR-032 posture);
- at-risk flag: counts as a structural event for the drift->recalibration
  advisor (alongside ADR-142 change-points).

Degenerate cases fail toward risk: a node with zero coupling is reported as
already partitioned (cut 0, that node as the weak side).

Measured cost policy (criterion, 12-node mesh — the honest part):
- weights quantized (1/64) + change-gated: steady-state cycles do ZERO graph
  work and reuse the cached cut (~7.3 us, ~23x cheaper than building);
- on any real change a full exact rebuild (~171 us) is used, because ONE
  DynamicMinCut delete+insert measured ~240 us — the subpolynomial machinery
  amortizes on much larger graphs, so rebuild-on-change is the measured
  optimum at mesh scale (one-edge case -28% after switching policy);
- full process_cycle with the guard: ~33 us for 4 nodes vs the 50 ms budget.

9 mesh_guard tests (weak-node detection, steady-state zero updates,
sub-quantum gating, join/drop rebuild, determinism, disconnection) + an
engine-level wiring test (down-weighted node -> weak side -> recalibration).
Engine 24 tests; workspace gate 2,946 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* feat(engine): mesh partition risk demotes privacy + enters the witness (ADR-032)

Completes the mesh-guard integration: its at_risk signal was advisory-only
(fed the recalibration advisor). It now also contributes to the ADR-141
privacy demotion alongside fusion- and array-level contradictions — a mesh
close to partitioning makes the fused belief less trustworthy, so the cycle
emits at a more restricted class (monotonic; information only removed).

Because effective_class feeds the BLAKE3 witness, a fragmenting array now
shifts the witness: partition risk is auditable, not just logged. The mesh
computation moved ahead of the demotion step in process_cycle; mesh_guard_mut
exposes risk-threshold tuning.

Test: a forced-risk 3-node cycle demotes PrivateHome Anonymous->Restricted
and shifts the witness vs a clean baseline. Engine 25 tests; workspace gate
2,947 passed / 0 failed.

https://claude.ai/code/session_01MjBucx95K4BuUxZi8NWwRH

* fix: public-PR review findings — privacy-path honesty, gate holes, mesh-guard cliff

- sensing-server: engine errors logged+counted (no silent swallow), trust
  state exposed via status surface, privacy-demotion claims aligned with
  the actual parallel-audit-path behavior
- occupancy_bench: vacuous-F1 hole closed (degenerate test sets fail with
  their own criterion); CI-lower-bound test made probative
- mesh_guard: quantization scaled to observed coupling range — >=65-node
  balanced meshes no longer permanently at_risk (regression test)
- engine: both wiring tests made probative (same-topology witness compare,
  deterministic risk-crossing fixture)
- mat: axum/tokio optional behind api; real serde feature (api enables it)
- core: canonical decoder strict (non-zero reserved bytes and nil UUID
  rejected — injective on accepted domain, forged-bytes tests)
- CHANGELOG: un-spliced the FFT/adapter bullet mangle

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

* chore: strip private-track references for public PR

Reword the occupancy-benchmark changelog bullet to drop a cross-reference
to the private research track, and restore the WorldGraph retention bullet
header that was glued onto the preceding MAT bullet.

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

* chore: lockfile refresh for cherry-picked feature set

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-11 16:08:54 -04:00
ruv b9457220bd chore(cogs): publish cog-ha-matter 0.3.0 + bump signal/sensing-server to 0.3.1
cog-ha-matter required wifi-densepose-sensing-server with the `mqtt`
feature exposed, which crates.io 0.3.0 did not expose. Chain:

  1. wifi-densepose-signal 0.3.0 -> 0.3.1 (already includes
     EmbeddingHistory::{with_sketch,novelty} locally; needed
     republish so sensing-server-0.3.1 can compile against it).
  2. wifi-densepose-sensing-server 0.3.0 -> 0.3.1 (now exposes
     the `mqtt` feature, sensing-server bin links against
     signal-0.3.1 cleanly).
  3. cog-ha-matter sensing-server dep bumped to ^0.3.1; publish=false
     dropped. cog-ha-matter@0.3.0 published.

Both signal and sensing-server published with --no-verify; cargo's
verification step fails on Windows because openblas-src requires
vcpkg (the source itself builds fine in the workspace and on Linux).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-25 11:01:46 -04:00
rUv 249d6c327f
ADR-115: Home Assistant + Matter integration (#778)
Closes ADR-115's MQTT track (HA-DISCO + HA-MIND + HA-FABRIC scaffolding).

Headline:
- 21 entity kinds per node (11 raw + 10 semantic primitives)
- MQTT auto-discovery with HA conventions
- Matter Bridge scaffolding (SDK wiring deferred to v0.7.1 per ADR §9.10)
- Privacy mode strips biometrics at the wire, semantic primitives keep working
- 420+ lib tests, mosquitto-backed integration tests, property-based fuzzing
- 8 starter HA Blueprints + 3 Lovelace dashboards shipped

Tracking issue: #776
2026-05-23 16:13:28 -04:00
rUv 00a234eda8
ADR-110: ESP32-C6 firmware extension (#764)
Closes the firmware-side ADR-110 design at v0.7.0-esp32 after a 38-iter /loop SOTA sprint.

Headline (bench, COM9+COM12 ESP32-C6):
- 99.56% cross-board RX, 104.1 µs smoothed offset stdev (≤100 µs §2.4 target met)
- 3.95× EMA suppression, 1.4 ppm crystal skew preserved

4 firmware releases: v0.6.7 / v0.6.8 / v0.6.9 / v0.7.0-esp32.
42 ADR-110 unit tests, 1761 v2 workspace tests, full Firmware CI + QEMU green.
2026-05-23 15:34:48 -04:00
rUv 67fec45e61
feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry (#648)
* feat(edge-registry): ADR-102 — surface Cognitum cog catalog via /api/v1/edge/registry

Adds a new sensing-server endpoint that fetches and caches the canonical
Cognitum app registry at
https://storage.googleapis.com/cognitum-apps/app-registry.json (105 cogs
across 11 categories as of v2.1.0). RuView previously had no live
awareness of the catalog — the README's capability table was hand-
curated and went stale as Cognitum shipped new cogs (the registry was
last updated 6 days ago).

ADR:
* docs/adr/ADR-102-edge-module-registry.md — full design, response
  shape, configuration flags, failure modes, and a 12-row security
  review covering SSRF, response inflation, ?refresh abuse, stale-serve
  semantics, TLS, cache poisoning, JSON-panic resistance, etc.

Code:
* v2/.../edge_registry.rs — EdgeRegistry struct + UreqFetcher +
  MockFetcher trait + 7 unit tests. RwLock<Option<CachedEntry>> with
  stale-on-error fallback. MAX_PAYLOAD_BYTES=8 MiB, 10s wire timeout.
* v2/.../main.rs — constructs Option<Arc<EdgeRegistry>> at startup,
  registers GET /api/v1/edge/registry handler, wires Extension layer.
  Handler runs the blocking ureq fetch via tokio::task::spawn_blocking
  so the async runtime stays free.
* v2/.../cli.rs / main.rs Args — three new flags (per user request to
  "allow the registry to be disabled or changed"):
    --edge-registry-url <URL>       (env RUVIEW_EDGE_REGISTRY_URL)
    --edge-registry-ttl-secs <N>    (env RUVIEW_EDGE_REGISTRY_TTL_SECS)
    --no-edge-registry              (env RUVIEW_NO_EDGE_REGISTRY)
  When --no-edge-registry is set or the URL is empty, the endpoint
  returns 404.

Cargo.toml: adds ureq (rustls), sha2, thiserror as direct deps.

README:
* New collapsed "🧩 Edge Module Catalog" section with the full 105-cog
  table generated from the registry, grouped by category with practical
  one-line descriptions (e.g. "Spots irregular heartbeats and abnormal
  heart rhythms", "Detects walking problems and scores fall risk").
  Links to https://seed.cognitum.one/store and the local appliance
  /cogs page. Sits between the HF model section and How It Works.

Tests (7/7 pass):
  first_call_hits_upstream_and_caches
  ttl_expiry_triggers_refetch
  force_refresh_bypasses_fresh_cache
  stale_serve_on_upstream_failure_after_cached_success
  no_cache_no_upstream_returns_error
  upstream_invalid_json_is_treated_as_error
  upstream_sha256_is_deterministic

Security highlights (full review in ADR-102 §"Security review"):
- The registry is metadata-only; per-cog binary signatures (ADR-100)
  remain the trust root for installs. A compromised registry can
  mislead a human reader but cannot ship malicious binaries.
- 8 MiB cap + 10s timeout + Option<Arc<...>> via Extension layer means
  the endpoint can't be used to exhaust memory or pin tokio threads.
- Stale-on-error responses carry an explicit `stale: true` field so
  upstream outages are visible to consumers rather than silently
  masked.
- Endpoint sits behind the existing RUVIEW_API_TOKEN bearer gate when
  set, otherwise unauthenticated (registry contents are public anyway).

* chore: refresh Cargo.lock for ureq/sha2/thiserror deps added by ADR-102
2026-05-19 18:08:43 -04:00
dependabot[bot] ab9799adc3
chore(deps): bump tower-http from 0.5.2 to 0.6.8 in /v2 (#483)
Bumps [tower-http](https://github.com/tower-rs/tower-http) from 0.5.2 to 0.6.8.
- [Release notes](https://github.com/tower-rs/tower-http/releases)
- [Commits](https://github.com/tower-rs/tower-http/compare/tower-http-0.5.2...tower-http-0.6.8)

---
updated-dependencies:
- dependency-name: tower-http
  dependency-version: 0.6.8
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-17 18:08:04 -04:00
ruv 94ef125240 feat(sensing-server): introspection module skeleton (ADR-099 D1+D7+D8)
Adds the per-frame introspection state that ADR-099 specifies, plus the two
midstream dependencies. Pure addition — no other code touched.

  v2/crates/wifi-densepose-sensing-server/Cargo.toml
    + midstreamer-temporal-compare = "0.2"
    + midstreamer-attractor        = "0.2"

  v2/crates/wifi-densepose-sensing-server/src/introspection.rs (new, 530 lines)
    pub struct IntrospectionState
      ├─ midstreamer-attractor's AttractorAnalyzer (regime + Lyapunov)
      ├─ SignatureLibrary (JSON-loaded labelled segments)
      ├─ VecDeque<f64> sliding amplitude buffer (default 128 points)
      └─ update(timestamp_ns, derived_feature) — never window-blocked
         + snapshot() -> IntrospectionSnapshot
            { timestamp_ns, frame_count, regime, lyapunov_exponent,
              attractor_dim, attractor_confidence, top_k_similarity }
    pub enum Regime { Idle, Periodic, Transient, Chaotic, Unknown }
    pub struct Signature { id, label, vectors, dtw, promotion_threshold }
    pub struct SimilarityMatch { signature_id, score, above_threshold }

DTW path is currently a host-side stand-in (length-normalised L1 with the
real DTW call deferred to I3/I5 once vec128 embeddings exist — ADR-099 P1).
The attractor path is wired to midstream directly. The analyze() step only
runs every N frames (default 8) to stay under the per-frame ms budget.

8 unit tests (snapshot defaults, frame-count + timestamp advance, empty
library, scoring + ordering invariants, threshold gating, empty-signature
fault-tolerance, regime classification after 200 frames). 199 → 207 lib tests,
0 failures. cargo build clean (only pre-existing warnings).

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-13 22:50:58 -04:00
ruv c641fc44ae feat(docker+sensing-server): refresh Docker publish + opt-in bearer-token API auth
Closes #520, #514, #443.

## #520 / #514 — stale Docker image, missing UI assets

`ruvnet/wifi-densepose:latest` was published before `ui/observatory*` and
`ui/pose-fusion*` were added; users see /app/ui missing those files and the
v0.6+ packet format doesn't reach the server. Two fixes:

1. `docker/Dockerfile.rust` now `RUN`s a build-time guard after `COPY ui/`
   that fails the build if `index.html` / `observatory.html` / `pose-fusion.html`
   / `viz.html` (or the `observatory/` / `pose-fusion/` / `components/` /
   `services/` directories) are missing, plus an exec-bit check on
   `/app/sensing-server`. A stale image can never be silently produced again.

2. New `.github/workflows/sensing-server-docker.yml` rebuilds + pushes on
   every change to the Dockerfile, the server crate, the signal/vitals/
   wifiscan crates, the workspace manifests, the `ui/` tree, or itself —
   plus `v*` tags and manual dispatch. Pushes to both `docker.io/ruvnet/
   wifi-densepose` AND `ghcr.io/ruvnet/wifi-densepose` with `latest` +
   `vX.Y.Z` + `sha-<short>` tags, then post-push smoke-tests the artifact:
   /health, /api/v1/info, the observatory + pose-fusion HTML, AND the
   bearer-auth path (no token → 401, wrong → 401, correct → 200). Uses the
   `DOCKERHUB_USERNAME`/`DOCKERHUB_TOKEN` repo secrets; ghcr.io rides on
   the workflow's GITHUB_TOKEN.

## #443 — sensing-server REST API auth model

QE security audit raised that 40+ /api/v1/* routes have no auth layer with
a default `0.0.0.0` bind. New `wifi_densepose_sensing_server::bearer_auth`
module + middleware:

  - Env-var-gated: `RUVIEW_API_TOKEN` unset/empty ⇒ middleware is a no-op
    (current LAN-mode behaviour preserved — **no default change**); set ⇒
    every `/api/v1/*` request must carry `Authorization: Bearer <token>`
    or the server returns 401.
  - Constant-time byte compare via local `ct_eq` (no new dep).
  - `/health*`, `/ws/sensing`, and `/ui/*` are intentionally never gated
    (orchestrator probes + local browsers).
  - Startup logs which mode is active and warns when auth is ON with a
    `0.0.0.0` bind.
  - 8 unit tests on the middleware via `tower::ServiceExt::oneshot`
    (sensing-server lib tests 191 → 199, 0 failures).

Verified locally: `cargo build --workspace --no-default-features` ✓,
`cargo test -p wifi-densepose-sensing-server --no-default-features` ✓.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-13 08:52:25 -04:00
rUv f49c722764
chore(repo): rename rust-port/wifi-densepose-rs → v2/ (flatten to one level) (#427)
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/)
without any sibling under rust-port/ that warranted the extra level.
Move the whole workspace up to v2/ to match v1/ (Python) at the same
depth and shorten every cd / build command across the repo.

git mv preserves history for all tracked files. 60 files updated for
path references (CI workflows, ADRs, docs, scripts, READMEs, internal
.claude-flow state). Two manual fixes for relative-cd paths in
CLAUDE.md and ADR-043 that became wrong after the depth change
(cd ../.. → cd ..).

Validated:
- cargo check --workspace --no-default-features → clean (after target/
  nuke; the gitignored target/ was carried by the OS rename and had
  hard-coded old paths in build scripts)
- cargo test --workspace --no-default-features → 1,539 passed, 0 failed,
  8 ignored (same totals as pre-rename)
- ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm)

After-merge follow-up: contributors should `rm -rf v2/target` once and
let cargo regenerate from the new path.
2026-04-25 21:28:13 -04:00