mirror of
https://github.com/ruvnet/RuView
synced 2026-06-26 13:03:19 +00:00
efadeb3a73dd374e5db7599e30e1bbc49a0e9021
89 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
eb996294fb |
feat(adr-118/p1.3): Sink marker traits + PrivacyClass::try_from (17/17 GREEN)
Iter 3. Lands the structural enforcement of ADR-118 invariant I1
("raw BFI never exits the node") and ADR-120 §2.2 ("Sink marker types").
Added:
- src/sink.rs:
* Sink trait with MIN_CLASS and KIND associated constants
* LocalSink (Raw OK), NetworkSink (Derived+ only), MatterSink (Anonymous+)
* Hierarchy: MatterSink: NetworkSink (every Matter sink is a NetworkSink)
* check_class<S>(class) runtime gate, returns PrivacyViolation{reason:KIND}
* Zero-sized kind tags: LocalKind / NetworkKind / MatterKind
- PrivacyClass::as_u8() const helper
- TryFrom<u8> for PrivacyClass (0..=3 valid; 4..=255 → InvalidPrivacyClass)
- BfldError::InvalidPrivacyClass(u8) variant
tests/sink_enforcement.rs adds 8 tests:
privacy_class_try_from_accepts_all_four_valid_bytes
privacy_class_try_from_rejects_out_of_range_bytes
privacy_class_byte_roundtrip_is_stable
local_sink_accepts_all_classes
network_sink_rejects_raw_frames
network_sink_accepts_derived_anonymous_restricted
matter_sink_rejects_raw_and_derived
matter_sink_accepts_anonymous_and_restricted
Out of scope (next iter):
- BfldFrame (header + payload + section length-prefixes + CRC32 over payload)
— needs the `crc` crate dependency.
- PrivacyGate::demote(frame, target_class) transformer (ADR-120 §2.4).
- compile-fail test that proves a sink-trait bound rejects Raw at compile
time — needs `trybuild` integration; deferred to a separate iter.
cargo test -p wifi-densepose-bfld --no-default-features → 17 passed, 0 failed
(3 frame_header_size + 6 header_roundtrip + 8 sink_enforcement)
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
be4dad6ede |
feat(adr-118/p1.2): header encode/decode + 6 round-trip tests (9/9 GREEN)
Iter 2 of the BFLD rollout. Adds the canonical little-endian wire form for
BfldFrameHeader with safe (no unsafe) encoders/decoders. Covers ADR-119 AC5
(round-trip preservation), AC6 (deterministic serialization), and partial
AC1 (constant wire size) / AC4 (rejects bad magic + bad version).
Added:
- BfldFrameHeader::empty() — convenience constructor with magic/version set
- BfldFrameHeader::to_le_bytes() -> [u8; 86]
- BfldFrameHeader::from_le_bytes(&[u8; 86]) -> Result<Self, BfldError>
- Field-level doc strings on every header field (clears all 21 missing-docs
warnings the iter 1 commit logged)
- tests/header_roundtrip.rs — 6 named tests:
header_roundtrip_preserves_all_fields
header_serialization_is_deterministic
header_magic_is_at_offset_zero_little_endian (LE byte order proof)
parsing_rejects_invalid_magic
parsing_rejects_unsupported_version
wire_size_is_constant
Implementation notes:
- Used #[derive(Default)] on BfldFrameHeader so empty() can build cleanly.
- to_le_bytes copies packed fields into locals first to dodge unaligned-
borrow lints; from_le_bytes uses try_into() on byte slices.
- All field reads/writes are #[forbid(unsafe_code)] compliant.
Out of scope (next iter targets):
- BfldFrame (header + payload sections + section-length prefixes + CRC32
computation over payload bytes only) — needs the `crc` crate dependency.
- PrivacyGate::demote(...) skeleton (ADR-120 §2.4).
- SinkMarker traits (LocalSink / NetworkSink / MatterSink) — ADR-120 §2.2.
cargo test -p wifi-densepose-bfld --no-default-features → 9 passed, 0 failed
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
c965e3e6c0 |
feat(adr-118/p1): scaffold wifi-densepose-bfld crate + frame header (3/3 tests GREEN)
Land P1 of the BFLD rollout — the wire-format primitives: - New workspace member: v2/crates/wifi-densepose-bfld - PrivacyClass enum (Raw/Derived/Anonymous/Restricted) with allows_network() and allows_matter() const helpers reflecting ADR-120 §2.2 and ADR-122 §2.4 - BfldFrameHeader (#[repr(C, packed)]) per ADR-119 §2.1 - BFLD_MAGIC = 0xBF1D_0001, BFLD_VERSION = 1 - BfldError variants for InvalidMagic / UnsupportedVersion / Crc / PrivacyViolation - soul-signature cargo feature (gated, default OFF) per ADR-118 §1.4 - Compile-time size assertion via static_assertions::const_assert_eq! - 3 acceptance tests in tests/frame_header_size.rs (all pass) Bug fix: - ADR-119 AC1 claimed BfldFrameHeader is 40 bytes. Actual packed layout sums to 86 bytes. Updated AC1 and §2.1 prose to match. const_assert in frame.rs pins the value structurally — a future field addition that breaks the size fails to compile. Out of scope for this iter (deferred to later P1 commits): - Field-level missing-docs warnings (21) — addressed alongside accessor helpers - Payload section parsing — needs the section-length prefix tests - Round-trip serialize/parse — covered by a fixture-based test in the next iter cargo test -p wifi-densepose-bfld --no-default-features → 3 passed, 0 failed Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
be4efecbcd |
cog-ha-matter (ADR-116 P8): app-registry entry stub + release checklist
Two closing P8 deliverables that complete the local-side publishing
scaffolding. The remaining work is all credential-bearing user
action.
1. `cog/app-registry-entry.json` — the exact JSON payload to paste
into cognitum-one's `app-registry.json`. Schema discovered by
fetching the live registry (105 cogs, 11 categories) and
matching the existing `ruview-densepose` entry verbatim. Keys:
id, name, category, version, size_kb, difficulty, description,
featured, config[], sha256, binary_size
cog-ha-matter slots in under `category: "building"` (smart home
/ building automation — the natural HA / Matter category, vs
`network` which is more about transport bridges).
7 config[] entries mirror our CLI surface:
sensing_url, mqtt_host, mqtt_port, privacy_mode,
mdns_hostname, mdns_ipv4, no_mdns
Two post-build fields left as `<FILL_IN_...>` markers:
sha256 (paste from the workflow artifact's .sha256)
binary_size (wc -c < the binary)
Schema validated: all 10 required keys present, parses as JSON.
2. `cog/RELEASE-CHECKLIST.md` — one-page mechanical playbook with
four explicit "🔑 USER ACTION" gates. Each gate names exactly
what the user (or org admin) has to do that the pipeline cannot:
a) provision GCP_CREDENTIALS + HAS_GCP_CREDENTIALS org var
b) provision COGNITUM_OWNER_SIGNING_KEY GH secret
c) gcloud auth login (only if uploading locally)
d) PR app-registry.json into cognitum-one
Plus pre-release test gate, tag-push command, post-release
verification curl, and a rollback procedure using GCS object
versioning (per ADR-100 §"GCS misconfiguration risks").
Stop-condition check (cron's predicate: "ALL local-side publishing
scaffolding is complete and the only remaining work requires user
action"):
✅ cog/manifest.template.json
✅ cog/Makefile (build / sign / upload / verify / clean)
✅ cog/README.md
✅ cog/app-registry-entry.json (this commit)
✅ cog/RELEASE-CHECKLIST.md (this commit)
✅ .github/workflows/cog-ha-matter-release.yml (3 jobs, gated)
✅ dist/ handling (gitignored, created by make)
🔑 4 user-action gates explicitly enumerated in the checklist
The cron should STOP after this iter — the local-side scaffolding
is complete and the remaining work is the four named credential
gates that the pipeline cannot self-serve.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
3833929dcb |
cog-ha-matter (ADR-116 P8): CI release workflow + fix inherited filename bug
New `.github/workflows/cog-ha-matter-release.yml`:
* Triggers on `cog-ha-matter-v*` tag-push + manual dispatch
* Three jobs: build-x86_64, build-arm, publish-gcs
* x86_64: native ubuntu-latest cargo build
* arm: aarch64-unknown-linux-gnu via apt-installed gcc-aarch64-linux-gnu
linker (no `cross` dep needed — keeps workflow self-contained)
* Each build job runs make build-{arch} + make sign-{arch} +
gated Ed25519 sign step (skipped when COGNITUM_OWNER_SIGNING_KEY
secret is unset — workflow still produces unsigned artifacts so
we get build coverage now and signing later without re-merging)
* publish-gcs job gated on `vars.HAS_GCP_CREDENTIALS == 'true'`
so the workflow is safe to merge before credentials land —
no-op until the org admin sets the variable
* Uploads binary + sha256 + (optional) sig to
`gs://cognitum-apps/cogs/{arch}/cog-ha-matter-{arch}`
* Prints the app-registry.json snippet for the cognitum-one PR
(so the publish step's output is the exact JSON the user pastes)
Fixed a bug inherited from cog-pose-estimation's Makefile: the
precedent produces `dist/cog-cog-pose-estimation-arm` (double
`cog-` prefix because CRATE name already starts with `cog-`) but
the manifest URL has single prefix `cog-pose-estimation-arm`. The
upload path doesn't match the binary_url — a latent bug in the
pose cog's pipeline.
My copy now produces `dist/cog-ha-matter-arm` matching the
manifest URL `cog-ha-matter-{{ARCH}}`. Changed: Makefile (build /
sign / upload / verify / clean targets), workflow (artifact names
+ gsutil paths), README (local dry-run instructions). The
cog-pose-estimation precedent is unchanged — separate fix if/when
the user wants to align it.
What this iter does NOT do (P8 remaining):
* provision GCP_CREDENTIALS / COGNITUM_OWNER_SIGNING_KEY secrets
(user action — needs org admin access)
* actually run the workflow (needs a `cog-ha-matter-v0.1.0` tag
push, or workflow_dispatch from the Actions tab)
* append to app-registry.json in cognitum-one (separate repo PR)
Next iter: tag a v0.0.1-dev (so the workflow runs once + we see
any build-time errors on real CI runners) OR scaffold the
app-registry.json patch payload as a check-in doc.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
1e469aa336 |
cog-ha-matter (ADR-116 P8): scaffold cog/ publishing layout
Mirrors v2/crates/cog-pose-estimation/cog/ so the Seed runtime
treats cog-ha-matter identically — `cognitum cog install ha-matter`
behaves like `cognitum cog install pose-estimation`.
Files:
* cog/manifest.template.json — 9-field manifest with {{VERSION}}
+ {{ARCH}} slots, hand-edited by the Makefile signer
* cog/Makefile — same target set as cog-pose-estimation:
build / build-arm / build-x86_64
sign / sign-arm / sign-x86_64 (Ed25519 step is TODO,
blocked on COGNITUM_OWNER_SIGNING_KEY provisioning —
same blocker as cog-pose-estimation)
upload / upload-arm / upload-x86_64
manifest (delegates to `cargo run -- --print-manifest`)
release (= build + sign + upload + manifest)
verify (sha256sum vs sidecar)
clean
Adds `mkdir -p dist` to build steps so the gitignored dist/
folder is created on first build.
* cog/README.md — what this cog does, layout map, local dry-run
instructions, gcloud auth requirements, the JSON snippet to
paste into app-registry.json (in the separate cognitum-one
repo, not this one)
Local dist/ is intentionally not committed: top-level .gitignore
matches `dist/` globally, the Makefile creates it on demand.
What this commit does NOT do (P8 remaining):
* cross-compile build (needs `rustup target add
aarch64-unknown-linux-gnu x86_64-unknown-linux-gnu` + linker)
* sign the binaries (COGNITUM_OWNER_SIGNING_KEY not provisioned)
* gsutil cp to gs://cognitum-apps/ (needs user's gcloud auth)
* append to app-registry.json (lives in cognitum-one repo —
separate PR there)
Next iter: a CI workflow that runs `make build sign verify` on
tag-push, so the local-side pipeline is fully exercised even
without the production credentials.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
d4f0e12073 |
cog-ha-matter (ADR-116): P4 ✅ — mDNS wired into main, broker deferred
Two landings that flip P4 to shipped:
1. main.rs now actually registers the mDNS responder. New CLI:
--mdns-hostname (default: cog-ha-matter.local.)
--mdns-ipv4 (default: 127.0.0.1)
--no-mdns (skip for restrictive CI / multi-instance)
Responder boots after the publisher; failure logs WARN + falls
back to manual HA config instead of killing the cog. The
handle's Drop sends the mDNS goodbye packet on shutdown so HA's
discovery sees a clean service-leave (no stale device card).
2. Embedded rumqttd broker DEFERRED to v0.7 per dossier §8 ranking.
The dossier's prioritised v1 scope is:
1. --privacy-mode audit-only
2. cog manifest + Ed25519 signing + store listing
3. local SONA fine-tuning loop
4. HACS gold-tier integration
5. Matter Bridge (v0.8)
Embedded broker is not in that list. Every HA install already
has mosquitto or HA Core's built-in broker — adding ~2 MB of
binary + ACL config surface for marginal benefit didn't earn a
v1 slot. Documented as row 6 of §4 v1 scope table with explicit
v0.7 target.
P4 row updated to ✅: mDNS half complete (record-builder +
ServiceInfo + live responder + main.rs wiring), witness half
complete (chain + JSONL + file + Ed25519), embedded broker
explicitly deferred with rationale citation to dossier §8.
Stop-condition check:
* dossier has "Recommended scope" section ✅ (§8, folded into
ADR §4)
* P2 (cog scaffold) ✅
* P3 (MQTT publisher wrap) ✅
* P4 (Seed-native enhancements) ✅
Cron's stop predicate evaluates: P2-P4 shipped AND dossier has
the recommended-scope section → STOP. The loop should TaskStop
itself after this iter unless the user wants P5 (RuVector
thresholds), P8 (cog signing), or P9 (HACS repo) to keep going.
64/64 tests green.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
07b792715f |
cog-ha-matter (ADR-116 P4): live mDNS responder + handle
Closes the mDNS half of P4. `runtime::start_mdns_responder` binds
multicast via `mdns_sd::ServiceDaemon::new`, builds the
ServiceInfo from `MdnsService::to_service_info` (iter 9), and
registers — returning a typed handle that owns both daemon and
fullname.
Handle shape:
pub struct MdnsResponderHandle {
daemon: ServiceDaemon,
fullname: String,
}
impl MdnsResponderHandle {
pub fn fullname(&self) -> &str;
pub fn shutdown(self) -> Result<(), mdns_sd::Error>;
}
impl Drop for MdnsResponderHandle { /* best-effort */ }
Why explicit `shutdown` + best-effort `Drop`: a clean shutdown
sends a goodbye packet so HA's discovery integration sees the
service leave (good UX — no stale device card). `Drop` is the
fallback for panics / process termination but swallows errors
since panicking-in-Drop would mask the real failure.
1 new live-I/O test:
* mdns_responder_fullname_concatenates_instance_and_service_type
— actually binds multicast on the loopback adapter, registers,
asserts the fullname contains `_ruview-ha._tcp`, then
shutdown()s. Confirmed working on Windows; CI environments
where multicast bind is filtered will hit the gracefully-
skipping early return rather than failing the suite.
64/64 cog tests green (63 → 64).
ADR-116 P4: mDNS half ✅ (record-builder + ServiceInfo + live
responder), witness half ✅ (chain + JSONL + file + Ed25519).
Last piece is the embedded rumqttd broker so external mosquitto
becomes optional.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
34eced880f |
cog-ha-matter (ADR-116 P4): MdnsService -> mdns-sd ServiceInfo bridge
Pure conversion from our wire-format `MdnsService` to the
`mdns_sd::ServiceInfo` shape the responder daemon consumes. No
socket binding, no daemon registration yet — that lands next iter
as a `runtime::spawn_mdns_responder(info)` JoinHandle returning
helper, same shape as `runtime::spawn_publisher`.
* `MdnsService::to_service_info(hostname, ipv4) ->
Result<ServiceInfo, mdns_sd::Error>`
* `mdns-sd = "0.11"` added — aligned with the workspace pin from
wifi-densepose-desktop so the lockfile doesn't fork dalek-like
surfaces.
3 new tests:
* to_service_info_carries_service_type_and_port — locks that
`_ruview-ha._tcp` (with or without mdns-sd's trailing-dot
normalisation) and the control port round-trip through the
conversion
* to_service_info_propagates_txt_records — every locked TXT
key from iter 4 (cog_id, mqtt_port, privacy, proto, node_id,
cog_version) reachable via `get_property_val_str` on the
converted ServiceInfo
* to_service_info_does_not_silently_drop_caller_hostname —
locks the caller-side responsibility for the .local. suffix.
mdns-sd 0.11 accepts bare hostnames (verified empirically by
initial test expecting it to reject — it didn't), so the
wrapper layer must do the trailing-dot dance. Documenting
that via a named test catches future bumps where the lib
starts mutating the value.
63/63 cog tests green (60 → 63).
ADR-116 P4 now ⁶⁄₇: ✅ mDNS record-builder, ✅ chain, ✅ JSONL, ✅
file persistence, ✅ Ed25519 signing, ✅ ServiceInfo conversion;
⏳ daemon register + embedded broker.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
bb154d4e78 |
cog-ha-matter (ADR-116 P4): Ed25519 signing layer for witness chain
Closes the cryptographic-attestation gap in ADR-116 §2.2: every
witness event can now be signed by the Seed's Ed25519 key, with
verify available to any auditor holding the public key.
Module shape (`src/witness_signing.rs`, kept separate from
`witness::` so the hash chain stays usable without dalek linked
in — important for the wasm32 audit-verifier variant we'll ship
later):
* sign_event(event, &SigningKey) -> Signature
* verify_signature(event, &Signature, &VerifyingKey)
-> Result<(), SignatureVerifyError>
* signature_to_hex / signature_from_hex (128-char lowercase,
matches the witness hex convention)
* SignatureVerifyError::Invalid
* SignatureParseError::{Length, Hex}
Key design point: signature covers the SAME canonical bytes
witness::hash_event hashes. That means:
1. A signed event commits to the entire event content (kind,
payload, timestamp, seq, prev_hash) — no field can be
retroactively changed without invalidating both the hash AND
the signature.
2. The signature implicitly commits to the event's *chain
position* via prev_hash — splicing a signed event into a
different chain breaks verification.
Adds `ed25519-dalek = "2.1"` to cog-ha-matter (already in
workspace via ruv-neural, version kept aligned).
9 new tests:
* sign_and_verify_round_trip
* verify_rejects_signature_under_wrong_key
* verify_rejects_tampered_event (mutate payload after sign)
* verify_rejects_event_with_wrong_prev_hash (splice attack)
* signature_hex_round_trip
* signature_from_hex_rejects_wrong_length
* signature_from_hex_rejects_non_hex
* signature_is_deterministic_for_same_event_and_key
(locks Ed25519's determinism — catches future accidental
swap to a randomized scheme)
* different_events_produce_different_signatures
60/60 cog tests green (51 → 60). Key management is intentionally
out of scope here — the cog runtime reads the Seed's key from the
Cognitum control plane's secure store (separate concern).
ADR-116 P4 now ⁵⁄₆: ✅ mDNS record, ✅ chain, ✅ JSONL, ✅ file
persistence, ✅ Ed25519 signing; ⏳ responder + embedded broker.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
1f5b7b48c9 |
cog-ha-matter (ADR-116 P4): witness file persistence + chain-level verify
Closes the witness audit-bundle surface. The hash-chain primitive
+ JSONL serializer from earlier iters only handled one event at a
time; this lands the file-stream surface that operations actually
need:
* `WitnessChain::write_jsonl(&mut impl Write) -> io::Result<()>`
— streams every event as one line + `\n`, empty chain writes
zero bytes
* `WitnessChain::read_jsonl(impl BufRead) -> Result<WitnessChain,
WitnessReadError>` — parses event-by-event AND runs chain-level
`verify()` on the loaded chain, catching reordered or replayed
prefixes that per-event hashing alone misses
Critical security property: `read_jsonl` calls `WitnessChain::verify`
on the loaded chain BEFORE returning Ok. A forged bundle assembled
from two valid chains pasted together would slip past the
per-event hash check (each event's `this_hash` is internally
consistent) but the cross-event `prev_hash` linkage detects the
seam. Test `read_jsonl_chain_verify_catches_reordered_events`
locks this — swap two events in a 2-event bundle, see Verify error.
Error surface (new `WitnessReadError` enum):
* `Io { line_no, msg }` — read failure mid-stream
* `Parse { line_no, source }` — per-event from_jsonl_line failure
* `Verify { source }` — chain-level verify failure
`line_no` is 1-indexed so an auditor sees the same number their
text editor shows. Blank lines tolerated for hand-edited bundles.
7 new tests:
* empty chain writes zero bytes
* write→read round-trips a 3-event chain
* exactly N newlines for N events; trailing newline present
* blank lines / leading newline tolerated
* parse error surfaces with correct line_no
* reordered events caught by chain-level verify
* no-trailing-newline still loads the final event
51/51 cog tests green (44 → 51).
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
a3478ea3b5 |
cog-ha-matter (ADR-116 P4): witness JSONL persistence
Third P4 sub-unit: serialize/parse for the witness hash chain so
audit bundles can be written to disk and replayed.
Wire shape (one record per line, alphabetical field order locked):
{"kind":"...","payload_hex":"...","prev_hash":"...","seq":N,
"this_hash":"...","timestamp_unix_s":N}
Why alphabetical field order: auditors archive whole bundles and
hash them. A rebuild that reordered fields would silently
invalidate every archival hash — locking the order is what makes
the JSONL stable across compiler / serde-json upgrades.
Why hex everywhere: human-greppable, monospace-friendly, no base64
ambiguity, no Vec<u8> JSON-array ugliness. Same convention as
ADR-101's `binary_sha256`.
Critically, `from_jsonl_line` RE-VERIFIES `this_hash` against
the canonical bytes derived from the parsed fields. A tampered
bundle fires `WitnessParseError::HashMismatch` BEFORE the event
loads — the parser is itself an auditor.
New surfaces:
* `WitnessHash::from_hex` (with structured length/parse errors)
* `WitnessEvent::to_jsonl_line`, `from_jsonl_line`
* `WitnessParseError` enum: Json | MissingField | WrongType |
HashLength | HashHex | PayloadHex | PayloadLength | HashMismatch
* private `hex_encode` / `hex_decode` helpers (no `hex` crate dep)
10 new tests:
* jsonl round-trip preserves all fields
* jsonl line has no embedded \n / \r (one record per line)
* jsonl field order is alphabetical (byte-stable archival)
* parser rejects tampered payload via HashMismatch
* parser rejects non-hex characters in hash
* parser rejects missing field
* hex encode/decode round-trip across empty / single byte / 0xff /
UTF-8 / arbitrary bytes
* hex decode rejects odd-length input
* WitnessHash::from_hex round-trip
* WitnessHash::from_hex rejects wrong length
44/44 cog tests green (34 → 44).
ADR-116 P4 row enumerates 4 sub-units now: ✅ mDNS record-builder,
✅ witness chain primitive, ✅ witness JSONL persistence,
⏳ responder + embedded broker + Ed25519 signing.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
fe913b0ea7 |
cog-ha-matter (ADR-116 P4): pure witness hash-chain primitive
Second P4 unit: an append-only SHA-256 hash chain for tamper-evident
audit logging. ADR-116 §2.2 promised this for healthcare /
education / shared-housing deployments — this lands the primitive
with no key dependency so the next iter can layer Ed25519 signing
on top without touching the chain itself.
Module shape:
* `WitnessHash([u8; 32])` newtype + `WitnessHash::GENESIS` sentinel
* `WitnessEvent { seq, prev_hash, ts, kind, payload, this_hash }`
— once committed, every field is immutable
* `WitnessChain` — `append`, `tip`, `verify`, `events`
* `canonical_bytes` — length-prefixed serialization that prevents
the classic concatenation forgery
(`abc|def` ≠ `ab|cdef`)
* `WitnessVerifyError` — auditor-friendly error with `at: usize`
on every variant (SeqGap, PrevHashMismatch, HashMismatch)
13 new tests covering both happy path and active tampering:
* genesis hash all-zeros
* empty chain tip is genesis
* canonical bytes length-prefixed (anti-forgery)
* canonical bytes start with prev_hash (wire-format lock)
* append links to prev_hash
* seq monotonic from 0
* verify passes on clean chain
* verify catches tampered payload (fires HashMismatch)
* verify catches broken prev_hash link
* verify catches seq gap
* hash hex is 64 lowercase chars
* first event prev_hash == GENESIS (auditor anchor)
* different payloads → different hashes
Hash-chain over Merkle is the right tradeoff for the cog's event
rate (a few/min steady, dozens during a fall) — linear scan is
fine and we save the Merkle complexity for a future tier when
chains span days.
34/34 cog tests green (21 → 34).
ADR-116 P4 row updated to enumerate the three P4 sub-units shipped /
pending: (a) mDNS record-builder ✅, (b) witness hash-chain ✅, (c)
responder + embedded broker + Ed25519 signing pending.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
35722529bf |
cog-ha-matter (ADR-116 P4): pure mDNS service-record builder
Opens P4 with the smallest extractable unit: a pure builder that
produces the wire-format `MdnsService` the responder will publish
next iter. Splitting the record-builder from the responder lets
us:
* lock the TXT-record surface with named unit tests so drift
between the cog and the HA-side YAML auto-discovery binding
fires a test instead of silently breaking deployments,
* swap the responder library (mdns-sd / zeroconf / pnet) without
touching content,
* include the advertisement in `--print-manifest` for Seed
integration tests that can't boot tokio.
TXT surface (sorted, RFC 6763):
| cog_id | "ha-matter" |
| cog_version | CARGO_PKG_VERSION |
| node_id | identity.node_id |
| mqtt_port | u16 stringified |
| privacy | "1" | "0" |
| proto | "ruview-ha/1" |
9 new tests:
* service_type locked to `_ruview-ha._tcp`
* instance_name carries node_id
* control_port advertises the *control plane*, not MQTT
* privacy flag is "1"/"0" (HA config flow reads it byte-stable)
* proto version locked to ruview-ha/1 (bump is deliberate)
* cog_id in TXT matches crate constant
* txt_records sorted for byte-stable mDNS responses
* **PII leak guard**: TXT must NOT carry hr_bpm, br_bpm, pose_*,
keypoint, ssid, lat, lon, mac, rssi — broadcasts in cleartext
so a future "let's add hr_bpm for convenience" patch fires
here, not in a privacy incident.
* required-keys lock — adding is fine, removing/renaming breaks
every deployed Seed.
21/21 cog tests green (12 → 21).
ADR-116 P4 flipped pending → in progress, with the responder /
embedded broker / witness chain enumerated as the remaining P4
sub-units.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
c9f005c360 |
cog-ha-matter (ADR-116 P3): wire publisher::spawn into main.rs
P3 closes the publisher wiring loop. `main.rs` now:
1. builds `PublisherInputs` from CLI args via the pure helper
extracted last iter,
2. opens a `broadcast::channel::<VitalsSnapshot>(256)`,
3. calls `runtime::spawn_publisher(inputs, rx)` — a thin
wrapper around ADR-115's `publisher::spawn` that owns the
`Arc<MqttConfig>` wrap,
4. holds the tx side so the channel stays open until P3.5
wires the sensing-server bridge,
5. awaits Ctrl-C or unexpected publisher exit (logged at WARN).
Two new tests:
* `spawn_publisher_returns_live_handle_without_broker` — proves
the wiring compiles and the rumqttc event loop survives an
unreachable broker (it retries internally; we abort the handle
inside 100 ms). Catches breakage from a future refactor that
accidentally pre-validates host reachability.
* `default_state_channel_capacity_is_reasonable` — locks the
`DEFAULT_STATE_CHANNEL_CAPACITY = 256` default; a regression to
e.g. 1 would surface here instead of as a dropped frame in
production under bursty multi-Seed federation.
12/12 cog-ha-matter tests green (10 → 12).
ADR-116 phase table: P3 flipped from "in progress" to ✅ wiring done,
with the P3.5 follow-up (sensing-server `/v1/snapshot` WS bridge)
explicitly named.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
5723f505b7 |
cog-ha-matter (ADR-116 P3): extract pure publisher-input builder
Adds `runtime::build_publisher_inputs(host, port, privacy, identity)` —
the side-effect-free helper that turns the cog's CLI surface into the
`(MqttConfig, OwnedDiscoveryBuilder)` pair ADR-115's `publisher::spawn`
consumes. Keeps the tokio runtime wiring out of the pure unit so the
mDNS responder + Seed control plane (P4) can build the same inputs
from different sources without going through clap.
8 new tests lock the wire-format invariants:
* host/port round-trip into MqttConfig
* privacy_mode propagation (P1 dossier item 7, FDA Jan 2026)
* discovery_prefix defaults to "homeassistant"
* discovery carries node_id + sw_version + friendly_name
* via_device advertises COG_ID (ADR-101/102 device-registry shape)
* client_id includes node_id (lesson from ADR-115 iter 45-48 session
takeover post-mortem — two publishers sharing a client_id loop)
* tls defaults to Off for v1 LAN-only (lock against silent enablement)
* default_identity carries CARGO_PKG_VERSION + PID for uniqueness
Plus the existing 2 manifest tests → 10/10 green
(`cargo test -p cog-ha-matter --no-default-features --lib`).
Also lands the deep-researcher dossier (`docs/research/ADR-116-ha-...`)
that the ADR §3+§4 reference — it was produced last iter but only the
ADR was committed; this puts the source-of-truth into the tree so the
ADR's "8 sections, 30+ citations" claim is actually verifiable.
P3 status in the ADR phase table flipped from "pending" to "in progress"
with the helper named; next iter tokio::spawns publisher::run(...) in
main.rs and registers the mDNS responder.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
56265023dc |
feat(cog-ha-matter): P2 scaffold + ADR-116 P1 research-dossier fold-in
cron iter 1. Three things landed atomically because they cross-cite:
P1 — research dossier complete
Deep-researcher agent (a4dd35950ffd) shipped
docs/research/ADR-116-ha-matter-cog-research.md: 8 sections,
30+ citations across Matter / HACS / cog arch / local-AI /
federation / competitors / regulatory / v1 scope. Key
findings folded into ADR-116 §3 and §4:
- Matter device class: OccupancySensor (0x0107) +
RFSensing feature on cluster 0x0406 (1.4 rev 5)
- ESP32-C6 Thread Border Router: one Kconfig flag away
(CONFIG_OPENTHREAD_BORDER_ROUTER=y)
- HACS quality tier: target Gold (repairs + diagnostics +
reconfiguration), start from hacs.integration_blueprint
- CSA cert: ~$30-42k/yr — skip for v1, "Works with HA"
positioning instead
- Cog RAM/CPU: 128 MB / 15% on the Seed; 10 KB INT8
semantic-primitive classifier fits without PSRAM
- SONA: <100 µs/query confirmed by ruvllm-esp32 v0.3.3
- FDA Jan 2026 wellness guidance covers HR / sleep / activity
anomaly when marketed as "anomaly notification" not "diagnosis"
- Competitor moat: Aqara FP300 / TOMMY / ESPectre all lack
HR + BR + pose + semantic + witness simultaneously
P2 — cog crate scaffold compiles
v2/crates/cog-ha-matter/ created with cog-pose-estimation as
precedent shape (ADR-101). Files:
- Cargo.toml: depends on wifi-densepose-sensing-server with
--features mqtt + wifi-densepose-hardware for the ADR-110
SyncPacket bridge.
- src/lib.rs: COG_ID = "ha-matter", MDNS_SERVICE_TYPE
"_ruview-ha._tcp", DEFAULT_CONTROL_PORT 9180.
- src/manifest.rs: typed CogManifest (8 fields) mirroring
cog-pose-estimation's manifest.template.json. Round-trip
test locks the JSON wire shape; id-constant test guards
against rename drift.
- src/main.rs: clap CLI with --sensing-url / --mqtt-host /
--mqtt-port / --privacy-mode / --print-manifest. The
--print-manifest flag emits the build-time template with
{{VERSION}} / {{ARCH}} placeholders for the signer.
- v2/Cargo.toml: cog-ha-matter added as workspace member.
Verification:
cargo check -p cog-ha-matter --no-default-features → green
cargo test -p cog-ha-matter --no-default-features --lib
→ 2/2 manifest tests pass
ADR-116 §3 + §4 + §5 (phases) updated to mark P1+P2 ✅ done and
seat the recommended v1 scope (privacy-mode audit-only → cog
signing → SONA loop → HACS gold → Matter Bridge as v0.8) ranked
by build cost × user impact per the dossier.
P3 (next iter): wrap the existing ADR-115 MQTT publisher as the
cog's main loop. The scaffold returns SUCCESS immediately today.
Co-Authored-By: claude-flow <ruv@ruv.net>
|
||
|
|
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 |
||
|
|
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. |
||
|
|
004a63e82d |
fix(security): audit — fix RUSTSEC vulns, clippy warnings, dead code (#769)
- Upgrade openssl to 0.10.78 (CVE-2026-41676), jsonwebtoken to 9.4 - Suppress unmaintained-only/no-CVE advisories in .cargo/audit.toml with per-entry rationale - Fix all `cargo clippy --all-targets -- -D warnings` errors across 35 crates: derivable_impls, needless_range_loop, map_or→is_some_and/ is_none_or, await_holding_lock (drop MutexGuard before .await), ptr_arg (&mut Vec→&mut [T]), useless_conversion, approximate_constant (2.718→E, 3.14→PI), field_reassign_with_default, manual_inspect, useless_vec, lines_filter_map_ok, print_literal, dead_code - Apply `cargo fmt --all` - Pre-existing test failure in wifi-densepose-signal (test_estimate_occupancy_noise_only) is not introduced by this PR |
||
|
|
1906876541 |
fix: upgrade openssl to 0.10.78 (CVE-2026-41676) (#751)
* fix: CVE-2026-41676 security vulnerability Automated dependency upgrade by OrbisAI Security * fix: upgrade openssl to 0.10.78 (CVE-2026-41676) rust-openssl provides OpenSSL bindings for the Rust programming langua Resolves CVE-2026-41676 |
||
|
|
a85d4e31e4 |
research(sota): kick off SOTA research loop + first R5 saliency measurement (#702)
Sets up docs/research/sota-2026-05-22/ as the autonomous-research output dir, with PROGRESS.md as the canonical 15-vector research agenda spanning spatial intelligence, RF features, RSSI-only, and exotic/long-horizon verticals. Cron d6e5c473 (*/10 * * * *) picks threads from this file and self-terminates at 2026-05-22 08:00 ET. First concrete contribution this tick — R5 subcarrier saliency: * examples/research-sota/r5_subcarrier_saliency.py: pure-numpy port of the count cog's Conv1d encoder + count head, computes per- subcarrier input×gradient saliency via central-difference. 128 samples × 56 subcarriers × 2 forward passes/subcarrier ≈ ~3 s on CPU, no GPU or framework dependency. * docs/research/sota-2026-05-22/R5-subcarrier-saliency.md: research note with motivation, method, novelty argument, and the first measured ranking. Top-8 subcarriers for cog-person-count v0.0.2: [41, 52, 30, 31, 10, 35, 2, 38]. Max/mean ratio 2.85x. * v2/crates/cog-person-count/cog/artifacts/saliency.json: machine- readable per-subcarrier saliency + top-K lists, so future-tick experiments (retrain at K=8/16/32) consume it without re-running. Key insight from the first measurement: top-8 saliency is *band- spread* (indices span 2-52), not concentrated. This directly raises R8's (RSSI-only) feasibility ceiling, because RSSI is a band- aggregate — it retains the integral of a band-spread signal. First- order estimate: RSSI-only should hit ~60% of full-CSI accuracy for the count task. R7 (adversarial defence) inherits a concrete defender- priority list: corroborate these 8 subcarriers across nodes. This commit is the first of many short, focused contributions over the next ~12 hours. PROGRESS.md is the canonical pointer for the next tick to pick up the next thread. |
||
|
|
b3a5012dbd |
feat(cog-person-count): v0.0.2 — K-fold + label-smoothing + temperature-calibrated (#699)
* chore: stage v0.0.2 artifacts + temperature scalar for build pipeline
Stages count_v1.{safetensors,onnx,temperature,train_results.json}
ahead of the build/sign/upload step. This commit is a momentary
side-effect — the next commit will refresh the per-arch manifests
with the new binary SHAs once ruvultra finishes the cross-build.
The .temperature file holds the calibration scalar from LBFGS over the
held-out conf logits. The Rust cog will read it post-load and divide
conf_logits by it before sigmoid, exactly matching the Python eval.
* feat(cog-person-count): v0.0.2 — K-fold validated, label smoothing + early stop + temp scale
The v0.0.1 "65.1% but class-1=0%" result was an unlucky temporal split
that let a degenerate "always predict 0" classifier hit eval acc =
class-0 fraction. 5-fold stratified random CV proved the architecture
actually learns ~57.1% class-1 accuracy under fair splits — a real,
modestly useful signal.
v0.0.2 ships a retrained model that:
* **Splits randomly (seed=42) 80/20** instead of temporally — eliminates
the trailing-window-class-imbalance cheat.
* **Class-balanced sampler** (multinomial with replacement, weighted by
inverse class frequency) — per-batch expected counts are equal
regardless of dataset distribution.
* **Label smoothing 0.1** on the cross-entropy — reduces confidence
saturation that drove v0.0.1's all-or-nothing predictions.
* **Early stopping** with patience=20 — stops at epoch 29 instead of
overfitting through 400.
* **Temperature scaling** of the conf head — LBFGS fits a scalar T on
held-out conf logits; ships as a count_v1.temperature sidecar so the
Rust cog can divide conf_logits by T before sigmoid.
Numbers on the same data:
| Metric | v0.0.1 | v0.0.2 | K-fold (5x100) |
|------------------|--------|--------|----------------|
| Overall acc | 65.1% | 62.3% | 62.2% ± 1.9% |
| Class 0 acc | 100% | 86.2% | 67.4% |
| Class 1 acc | 0% | 34.3% | 57.1% ✓ |
| MAE | 0.349 | 0.377 | 0.378 |
| Spearman | 0.023 | 0.013 | 0.160 |
Class-1 accuracy 0 → 34.3% is the headline win. Net acc moves slightly
because we stopped cheating on class 0. K-fold's 57% says there's
headroom remaining; reaching it needs more independent splits (== more
data), not more training tricks.
Confidence calibration didn't move. Temperature scaling alone can't fix
a confidence head trained against a noisy argmax==truth indicator over
a 62%-accurate classifier — the head's training signal is the issue,
not its post-hoc transform. The honest fix is multi-room data (#645),
not another calibration knob.
Live on cognitum-v0 at /var/lib/cognitum/apps/person-count/ — health
reports candle-cpu backend, count = 1 (was 0 in v0.0.1) on synthetic
zero input.
Files changed:
* scripts/train-count.py — adds --k-fold (no sklearn dep, hand-rolled
stratified splits with deterministic shuffle) and --v2 paths.
* v2/.../cog/artifacts/count_v1.safetensors (392 KB, new sha
32996433…) + count_v1.onnx (16 KB) + count_v1.temperature (0.9262
scalar) + count_train_results.json (full epoch trace).
* v2/.../cog/artifacts/manifests/{arm,x86_64}/manifest.json bumped to
version 0.0.2 with the new weights_sha256 + caveats.
* docs/benchmarks/person-count-cog.md — appends a v0.0.2 section
with the K-fold diagnostic table and honest-read paragraph.
GCS:
gs://cognitum-apps/cogs/arm/cog-person-count-count_v1.safetensors
refreshed (binaries unchanged — load weights via mmap at runtime).
|
||
|
|
e6a5df36eb |
chore(cog-person-count): refresh GCS manifests after run-wiring rebuild (#698)
The arm + x86_64 manifests committed in #696 referenced the binaries built before #697 wired the `run` subcommand. Rebuilt + re-signed + re-uploaded to GCS, and re-deployed to cognitum-v0: arm sha 15c2fbac…7728ea5 (3,807,456 B, up from 2,168,816 — added Tokio runtime) x86_64 sha 051614ce…cc8388b3 (4,502,960 B, up from 2,615,528) Both re-signed Ed25519 with COGNITUM_OWNER_SIGNING_KEY. Manifests now match the binaries published at gs://cognitum-apps/cogs/{arm, x86_64}/cog-person-count-* and the binary installed at /var/lib/cognitum/apps/person-count/ on cognitum-v0. |
||
|
|
5c914e63c7 |
feat(cog-person-count): wire run subcommand — v0.0.1 fully functional (#697)
Phase 4 of ADR-103. Adds the long-running polling loop so the cog's
fourth verb (`run`) does real work, completing the ADR-100 runtime
contract end-to-end:
cog-person-count version → "person-count 0.3.0"
cog-person-count manifest → JSON skeleton
cog-person-count health → loads weights + 1-shot infer + emit
cog-person-count run --config → long-running per-frame emit ← THIS
What ships:
* src/runtime.rs (new) — `run_loop` polls sensing_url every poll_ms,
slides a [56, 20] CSI window, runs InferenceEngine::infer, emits
publisher::person_count events. Same shape as
cog-pose-estimation::runtime — fetch_frame extracts amplitudes
from `snapshot.nodes[0].amplitude[]`, fails open on connect errors
with a WARN log rather than crashing.
* src/lib.rs — registers the runtime module.
* src/main.rs — cmd_run now loads RunConfig from a JSON file, builds
the InferenceEngine (with weights if cfg.model_path is set,
otherwise auto-discover), emits a run.started event, and hands off
to the Tokio multi-thread runtime's block_on(run_loop). Single-node
fusion is a no-op for N=1 today; v0.2.0 will append predictions
from sibling nodes and call fusion::fuse_confidence_weighted before
emit.
Verified locally:
cargo check -p cog-person-count --no-default-features → clean
cargo test -p cog-person-count → 15/15 pass (no regressions)
cargo build -p cog-person-count --release → 2.36 MB unchanged
./cog-person-count run --config bad-config.json:
line 1: {"event":"run.started","fields":{"cog":"person-count",
"sensing_url":"http://127.0.0.1:9999/...",poll_ms:100,
"model_path":"(auto-discover)"}}
line 2: WARN sensing-server fetch failed
error=Connection Failed: Connect error: actively refused
(loop alive — exits cleanly on SIGTERM, no crash, no NaN)
Also adds a "Relationship to the in-process score_to_person_count
heuristic" section to cog/README.md explaining the dual-emitter
design (sensing-server keeps emitting the PR #491 slot heuristic;
the cog runs out-of-process and emits person.count events from the
learned model). Operators choose by installing the cog or not — no
sensing-server rebuild required.
ADR-103 §"Migration" status:
1. Land ADR + scaffold ........... done (#693, #694)
2. Train count_v1 ................ done (#695)
3. Cross-compile + sign + GCS .... done (#696)
4. Server-side wiring ............ done — out-of-process design
means no rewire needed; this
cog is the wiring.
5. v0.2.0 multi-room + LoRA ...... data-bound (#645)
|
||
|
|
a5e99670f8 |
feat(cog-person-count): release v0.0.1 — signed binaries on GCS, live on cognitum-v0 (#696)
Phase 3 of ADR-103. Cross-compiled aarch64 + x86_64 on ruvultra, signed
with COGNITUM_OWNER_SIGNING_KEY (Ed25519), uploaded to GCS, and live-
installed on the cognitum-v0 Pi 5 alongside cog-pose-estimation.
Real-hardware bench on cognitum-v0:
./cog-person-count-arm health
→ backend=candle-cpu, count=0, confidence=0.49, p95=[0,7]
30 sequential health invocations: 0.276 s → 9.2 ms/invocation cold
Compares to cog-pose-estimation's 8.4 ms — count cog is ~10% slower
because the dual-head (count softmax + confidence sigmoid) does ~2x
the work after the shared encoder.
GCS release artifacts (publicly downloadable, SHA-verified):
arm/cog-person-count-arm 2,168,816 B
sha: 36bc0bb0...0d47b507b3c3
sig: R/00xdzHriyr/2r...JK+a6k71NDg== (Ed25519)
x86_64/cog-person-count-x86_64 2,615,528 B
sha: 76cdd1ec...3923 7392b01db
sig: QB+8cnGSMQmu...ZtTNIQ2rDg== (Ed25519)
arm/cog-person-count-count_v1.safetensors 392,088 B
sha: dacb0551...e6e04ff56d15c3a65a9ff
Live install at /var/lib/cognitum/apps/person-count/ on cognitum-v0
matches the layout of every other installed cog (anomaly-detect,
seizure-detect, pose-estimation): cog-person-count-arm binary,
count_v1.safetensors weights, manifest.json, config.json.
Adds:
* v2/.../cog/artifacts/manifests/{arm,x86_64}/manifest.json — full
ADR-100 schema with all fields filled (sha + sig + size + URL +
build_metadata carrying the v0.0.1 honest training caveats).
* docs/benchmarks/person-count-cog.md — appends "Live appliance
install" and "Signed GCS release artifacts" sections to the
benchmark log.
Honest v0.0.1 caveat still applies (class-1 accuracy 0% on the held-
out tail of the single-session training data) — same data-bound
limit as pose_v1. The shipped artifact is the *vehicle*; production-
quality accuracy follows from multi-room paired data per ADR-103's
v0.2.0 plan + #645.
|
||
|
|
6b4994e105 |
feat(cog-person-count): train count_v1.safetensors — honest v0.0.1 (ADR-103) (#695)
Phase 2 of ADR-103: trained count head on the existing 1,077 paired samples (the same data that produced pose_v1 yesterday). Honest result: 65.1% eval accuracy / 100% within ±1 / MAE 0.349 on the held-out time-window. Per-class: 100% on "empty room" / 0% on "1 person". The model overfit by epoch 100 (train_acc → 1.0, eval_loss climbed 0.67 → 7.8) and the "best" checkpoint is the snapshot that happened to predict the eval window's class distribution (140/215 = 65.1%, matches eval_acc exactly). Confidence head Spearman = 0.023 ⇒ uncalibrated. Same data-bound failure mode as pose_v1 (#645), bounded by single-session training data; same fix path (multi-room). What v0.0.1 still validates end-to-end: * PyTorch → safetensors → Candle Rust loads cleanly on first try. `cog-person-count health` reports `backend: candle-cpu` and emits real per-frame predictions instead of the stub backend's hard-coded {1 person, 0 confidence}. Architecture parity between train-count.py and src/inference.rs::CountNet is bit-exact. * ONNX export bit-clean (16 KB, opset 18, dynamic batch axis). * Training wall time: 5.6 s for 400 epochs on RTX 5080. * Binary size unchanged (2.36 MB stripped), model loads via mmap at runtime. This commit ships: * scripts/align-ground-truth.js: extended to emit n_persons_mode + n_persons_max per window so the training pipeline has count labels. Backwards-compatible (additive fields). * scripts/train-count.py: new — mirrors CountNet architecture exactly, loads paired.jsonl, trains 400 epochs with CE+BCE+Brier loss, exports safetensors + ONNX + per-epoch JSON. * v2/.../cog/artifacts/{count_v1.safetensors,count_v1.onnx, count_train_results.json}: the trained artifacts. * v2/.../cog/README.md: Status table updated with the v0.0.1 numbers + an Honest Caveat section explaining the data-bound result. * docs/benchmarks/person-count-cog.md: new — full v0.0.1 benchmark log mirroring the format docs/benchmarks/pose-estimation-cog.md established. Includes comparison to ADR-103 v0.1.0 acceptance gates and per-class breakdown. Still pending: * `run` subcommand wiring (long-running polling loop, same as pose) * Cross-compile + sign + GCS upload (mirror of pose cog pipeline) * Live install on cognitum-v0 * v0.2.0: re-train on multi-room data, LoRA per-room adapters, Stoer-Wagner min-cut clip in fusion stage |
||
|
|
6959a42312 |
feat(cog-person-count): v0.0.1 scaffold + tests + fusion math + bench (ADR-103) (#694)
First implementation PR for ADR-103. Same incremental shape that
ADR-101 used: scaffold the cog crate, ship a stub-backend release
that satisfies the runtime contract + 15 tests + measured cold-start,
then follow up with the trained count_v1.safetensors in a separate PR.
What ships:
* v2/crates/cog-person-count/ — new workspace member.
- Cargo.toml: candle-core/candle-nn 0.9 (cpu default, cuda feature
opt-in), safetensors, ureq, sha2 — same dep shape as the pose cog
but minus wifi-densepose-train (this cog has no training-side
consumer, so the dep tree is materially smaller → 2.36 MB
binary vs the pose cog's 4.5 MB).
- src/inference.rs: CountNet (Conv1d 56→64→128→128 encoder + count
head Linear(128→64→8)+softmax + confidence head
Linear(128→32→1)+sigmoid). Stub backend returns
`{1-person, 0-confidence}` honestly when no safetensors present.
- src/fusion.rs: fuse_confidence_weighted() — Bayesian product of
per-node distributions with confidence-weighted log-sum, plus
fuse_with_mincut_clip() hook for the v0.2.0 Stoer-Wagner
upper-bound (`ruvector-mincut` dep lands when min-cut graph
builder is ready). Confidences floored at 1e-3 and probs floored
at 1e-9 before logs — no NaN propagation.
- src/publisher.rs: emits {count, confidence, count_p95_low,
count_p95_high, n_nodes, probs} per ADR-103 §"Output".
- src/main.rs: full ADR-100 four-verb CLI (version|manifest|health
|run). The `run` subcommand explicitly returns "wiring pending
v0.0.1" so the in-process library API is the v0.0.1-clean
integration path.
- tests/smoke.rs (8 tests) + fusion::tests (7 tests, in-lib) — 15
total, all green. Cover stub-backend behaviour, wrong-shape
rejection, fusion math (empty / single / agreement / high-conf
override / normalisation), p95-range correctness, and min-cut
clip semantics.
- cog/{manifest.template.json, config.schema.json, README.md} +
cog/artifacts/ placeholder dir.
* v2/Cargo.toml: registers the new workspace member.
Verified locally:
cargo check -p cog-person-count --no-default-features → clean
cargo test -p cog-person-count --no-default-features → 8/8 pass
cargo test -p cog-person-count --lib → 7/7 pass
cargo build -p cog-person-count --release → 2.36 MB binary
./cog-person-count version → "person-count 0.3.0"
./cog-person-count manifest → JSON skeleton
./cog-person-count health → backend:stub,
count:1, conf:0,
p95:[1,1]
Cold-start: 30 sequential `health` invocations → 53.3 ms/invocation
(vs cog-pose-estimation's 76.2 ms — smaller dep tree)
cog/README.md adds:
* Security section — six-row threat table covering safetensor mmap
trust, non-finite outputs, sensing fetch failures, fusion
divide-by-zero / log-of-zero, min-cut degenerate cases, and stdout
spoofing.
* Performance / optimization section — binary size, release profile
(already opt-level=3 / lto=fat / codegen-units=1 / strip=true at
workspace level), cold-start comparison table, projected warm-path
latency budget.
Still pending (separate PRs, ADR-103 §"Migration"):
* Train count_v1.safetensors on the existing 1,077 paired samples
with `n_persons` labels (Candle on RTX 5080, same script that
produced pose_v1.safetensors yesterday).
* `run` subcommand wiring (long-running polling loop, same shape as
cog-pose-estimation::runtime).
* Cross-compile + sign + GCS upload (mirror of cog-pose-estimation
release pipeline).
* Server-side `csi.rs::score_to_person_count` call-site rewire to
consume this cog when installed; falls back to PR #491's heuristic
when not.
|
||
|
|
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 |
||
|
|
4b1a835107 |
docs: repoint #640 references to #645 (original deleted, replaced) (#646)
Issue #640 (PCK gap follow-up) was deleted upstream after the cog v0.0.1 PRs landed today. Re-opened as #645 with the same context plus the new measured v0.0.1 numbers (PCK@20 3.0%, PCK@50 18.5%, MPJPE 0.093). This patch updates the three files in main that still pointed at the dead #640 to point at #645 instead — ADR-101, the cog README, and the benchmark log. |
||
|
|
fcb6f4bf12 |
feat(cog-pose-estimation): x86_64 release v0.0.1 — parallel to arm (#643)
Adds the x86_64-unknown-linux-gnu binary uploaded to
gs://cognitum-apps/cogs/x86_64/, signed with the same Ed25519
COGNITUM_OWNER_SIGNING_KEY as the arm release. Together with the
already-shipped arm artifact, the cog now ships natively for both
target architectures the Cognitum fleet supports.
x86_64 release:
sha256: a434739a24415b34e1aff50e5e1c3c32e568db96af473bbb3e5ecc9b95fe71fa
signature: pNNuxhgM18PztN8BSZdfw5oAShG2pV3na5T/q2QdlJWX/5FJgo4QTiUCbcTAxI2Uiva8VURSOlRzMU3xoQPqCQ==
size: 4,548,856 bytes
cold-start: 5.4 ms / invocation on ruvultra (RTX 5080, NVMe)
Reorganizes manifests under cog/artifacts/manifests/{arm,x86_64}/
so each arch carries its own manifest with the matching binary_sha256
and signature — same layout the release pipeline will use for the
future hailo8 / hailo10 variants.
Updates docs/benchmarks/pose-estimation-cog.md with the cross-arch
cold-start table:
Windows (x86_64) 76.2 ms
ruvultra (x86_64) 5.4 ms <- this release
Pi 5 (aarch64) 8.4 ms
Verified via anonymous GCS download + SHA round-trip — identical to
local build.
Hailo HEF remains the only pending arch, still blocked on Hailo SDK
provisioning to a self-hosted runner.
|
||
|
|
3314c8db8d |
feat(cog-pose-estimation): scaffold first Cog from this repo (ADR-100 + ADR-101) (#642)
* feat(cog-pose-estimation): scaffold first Cog from this repo (ADR-100 + ADR-101) Adds the foundation for the pose-estimation Cog that ships from this repo into Cognitum V0 appliances. Companion ADR-225 + crate land in cognitum-one/v0-appliance. ADRs: * ADR-100 formalises the Cognitum Cog packaging spec — on-device layout under /var/lib/cognitum/apps/<id>/, manifest.json schema (incl. new binary_sha256 + binary_signature fields), GCS hosting convention, repo source layout, build pipeline, and the four-verb runtime contract (version | manifest | health | run). Documents the convention I reverse-engineered from inspecting installed cogs on a live cognitum-v0 appliance — `anomaly-detect`, `presence`, `seizure-detect`, etc. * ADR-101 designs the pose-estimation Cog itself: where it sits in the wifi-densepose pipeline (encoder init from ruvnet/wifi-densepose-pretrained, 17-keypoint regression head), what gets shipped per target arch (arm / x86_64 / hailo8 / hailo10), acceptance gates (PCK@20 explicitly deferred to #640 — this ADR ships the vehicle, not the accuracy). Crate v2/crates/cog-pose-estimation/: * Cargo.toml + workspace member declaration with a hailo feature gate so the binary builds without the Hailo SDK in CI. * main.rs implements the four-verb CLI exactly per ADR-100. * config.rs / manifest.rs / publisher.rs / inference.rs / runtime.rs — small modules, each <100 lines. * publisher.rs emits ADR-100 structured JSON events. * inference.rs is a stub that produces a centred-skeleton baseline with confidence=0 (honest: no trained weights wired in yet). * runtime.rs subscribes to /api/v1/sensing/latest, slides a 56*20 window, runs the engine, emits pose.frame events. * cog/manifest.template.json + cog/config.schema.json define the release artifact + runtime config schemas. * cog/Makefile holds build / sign / upload targets. * tests/smoke.rs covers manifest roundtrip + engine I/O surface. Verified locally: * cargo check -p cog-pose-estimation: clean. * cargo test -p cog-pose-estimation: 4/4 pass. * ./target/release/cog-pose-estimation {version,manifest,health}: all emit the right contract output. This commit contains scaffolding only; the actual trained weights and Hailo HEF cross-compile come in follow-ups tracked in #640 and the companion v0-appliance branch. * feat(cog-pose-estimation): first measured run — Candle CUDA on RTX 5080 Trained pose_v1 on ruvultra (RTX 5080) via Candle 0.9 + cuda feature against the same 1,077-sample paired session that produced 0%/0% PCK in #640 with the pure-JS SPSA trainer. First real numbers: PCK@20 = 3.0% (up from 0.0%) PCK@50 = 18.5% (up from 0.0%) MPJPE = 0.093 (down from 0.66, ~7x improvement) 400 epochs in 2.1 s wall time, full-batch, ~5 ms/epoch. Loss curve 0.181 -> 0.014 over the run, eval 0.010. Per-joint reveals the model leans on right-side proximal joints (r_hip 77% PCK@50, r_knee 35%, l_elbow 26%) — consistent with the camera framing in the source recording. Distal joints (wrists, ankles) and face joints are still near-random, consistent with the 56-subcarrier / 20-frame input not carrying fine-grained spatial info at 1077 samples. This commit: * Adds v2/crates/cog-pose-estimation/cog/artifacts/{pose_v1.safetensors, train_results.json} so the cog dir now contains a real reference artifact, not just scaffold. * Updates cog/README.md "Status" block with the measured numbers, per-joint table, and an honest reading of where the model succeeds vs where the data is the bottleneck. * Adds docs/benchmarks/pose-estimation-cog.md as the canonical benchmark log — append-only, one section per published run. * Appends a "First measured run" section to ADR-101 referencing the new benchmark file. Still pending in the follow-up: * Wire pose_v1.safetensors into src/inference.rs (replace stub). * ONNX export (Candle lacks a writer — needs external conversion). * Hailo HEF cross-compile + cluster deploy. The data-bound gap to PCK@20 >= 35% is tracked in #640. * feat(cog-pose-estimation): wire real weights — cog is no longer a stub Replaces the centred-skeleton stub in src/inference.rs with a real Candle-based loader that reads cog/artifacts/pose_v1.safetensors and runs the trained Conv1d encoder + MLP pose head on every incoming CSI window. What changes: * src/inference.rs: PoseNet mirrors the training script's architecture exactly — Conv1d(56->64, k=3 d=1), Conv1d(64->128, k=3 d=2), Conv1d(128->128, k=3 d=4), mean over time, Linear(128->256)+ReLU, Linear(256->34)+sigmoid -> reshape [17, 2]. The InferenceEngine searches a sensible candidate list for the weights file (/var/lib/cognitum/apps/pose-estimation/, ./pose_v1.safetensors, ./cog/artifacts/, repo-root, v2/-relative) and falls back to the stub when none are present so the cog still satisfies ADR-100. * Cargo.toml: adds candle-core 0.9 + candle-nn 0.9 (no-default-features, CPU build by default) + safetensors 0.4. New `cuda` feature opt-in for GPU inference on hosts that have it. Drops the unused wifi-densepose-train path dep from the default build path. * src/main.rs + src/publisher.rs: health.ok event now carries `backend` (candle-cuda | candle-cpu | stub) and the synthetic output confidence, so operators can tell at a glance whether the cog loaded its weights or fell back to the stub. * tests/smoke.rs: adds `real_weights_load_when_available` which asserts the loaded engine reports backend=candle-* and emits non-zero confidence — exactly the signal that proves we're not silently degrading to the stub. Verified locally: * `cargo check -p cog-pose-estimation --no-default-features` — clean * `cargo test -p cog-pose-estimation --no-default-features` — 5/5 pass * `./target/release/cog-pose-estimation health` emits: {"event":"health.ok","fields":{"backend":"candle-cpu","cog":"pose-estimation","synthetic_output_confidence":0.185}} — 0.185 is the published PCK@50 from cog/artifacts/train_results.json, emitted by the real Candle inference path (would be 0.0 if it had fallen back to the stub). The cog now runs the trained pose_v1 model end-to-end. Accuracy is still bounded by the underlying 1077-sample training data (PCK@20 3.0%, PCK@50 18.5% per docs/benchmarks/pose-estimation-cog.md) — that gap is data-bound and tracked in #640. ONNX export + Hailo HEF cross-compile remain follow-ups. * docs(benchmarks): measure cog-pose-estimation cold-start latency 100 sequential `cog-pose-estimation health` invocations average 76.2 ms each on a Windows x86_64 host using the `candle-cpu` backend. Each invocation re-loads pose_v1.safetensors and runs one synthetic forward pass, so this is the worst-case cold-start path. Long-running `run` inference will be sub-millisecond per frame once the model is loaded. Updates the benchmarks doc accordingly. * feat(cog-pose-estimation): ONNX export — pose_v1.onnx + scripts/export-onnx.py Adds the canonical ONNX artifact that unblocks downstream Hailo HEF cross-compile + ONNX Runtime benchmarks. Generated on ruvultra (torch 2.12.0 + CUDA), 12,059 bytes, opset 18, dynamic batch axis. * scripts/export-onnx.py: mirrors the Candle inference architecture in PyTorch (Conv1d 56->64, 64->128, 128->128 + Linear 128->256->34), pure- python safetensors loader (no extra pip dep), exports via torch.onnx.export, then verifies via onnx.checker.check_model and numerical parity against the torch reference. * Verified parity vs torch: max |torch - onnx| = 8.94e-8 (1e-5 threshold). Effectively bit-perfect. * v2/crates/cog-pose-estimation/cog/artifacts/pose_v1.onnx — the artifact itself, 12 KB. * docs/benchmarks/pose-estimation-cog.md — adds an ONNX export section with the verification numbers. Next: Hailo HEF cross-compile (still gated on Hailo SDK on a self-hosted runner) and ONNX Runtime latency benchmarks on each target arch. * feat(cog-pose-estimation): release v0.0.1 — signed aarch64 binary on GCS End-to-end deploy: cross-compiled to aarch64-unknown-linux-gnu on ruvultra, ran via qemu-aarch64-static, then smoke-tested on a real cognitum-v0 Pi 5. Signed with COGNITUM_OWNER_SIGNING_KEY (Ed25519) and uploaded to gs://cognitum-apps/cogs/arm/. Real-hardware results on cognitum-v0 (Pi 5): health: backend=candle-cpu, confidence=0.185, real weights loaded 30x sequential `health`: 0.251 s total -> 8.4 ms / invocation (cold) GCS release artifacts (publicly downloadable): binary: 3,741,976 bytes sha256 1e1a7d3dd01ca05d5bfc5dbb142a5941b7866ed9f3224a21edc04d3f09a99bf5 weights: 507,032 bytes sha256 eb249b9a6b2e10130437a10976ed0230b0d085f86a0553d7226e1ae6eae4b9e5 signature (Ed25519, b64): LUN7xqLPYD3MFzm5dKB5MnYU0LvoRtek5ci5KiKPHBg+Xo6xuazwokn2Dw2JPMaLYJzmWn/SpT4djuR7hYvVDw== Adds: * v2/crates/cog-pose-estimation/cog/artifacts/manifest.json — the release-pipeline-produced manifest with all fields filled in per ADR-100, including arch, target_triple, signature, and a build_metadata block carrying the validation PCK numbers. * docs/benchmarks/pose-estimation-cog.md — new sections covering the real Pi 5 smoke (8.4 ms cold-start) and the signed GCS release artifacts. Verified by downloading the binary anonymously from GCS and re-computing the sha256 — matches the locally-computed sha exactly. Signature decoded to the expected 64-byte Ed25519 length. Closes the GCS-upload acceptance criterion from ADR-100; the only pending work is Hailo HEF cross-compile (still SDK-gated) and an x86_64 release alongside this arm release. * docs(benchmarks): record live cognitum-v0 install + 5-sec smoke run Adds the "Live appliance install" section documenting what happened when the signed v0.0.1 binary + weights were installed under /var/lib/cognitum/apps/pose-estimation/ on cognitum-v0 (the V0 cluster leader). * Layout matches the existing anomaly-detect / presence / seizure- detect cogs exactly — the Cogs dashboard at http://cognitum-v0:9000/cogs auto-discovers entries. * `cog-pose-estimation run` ran for 5 seconds in the background and cleanly emitted run.started + structured WARN events for the missing local sensing-server on :3000 (cognitum-v0's actual CSI source is ruview-vitals-worker on :50054, not :3000). No crashes, no NaN, no leaks. * Wiring `sensing_url` to the appliance-native source is a separate Day-2 integration task. |
||
|
|
c00f45e296 |
fix(sensing): finish #611 NaN-panic audit — 7 more sites missed by #613 (#624)
#613 fixed adaptive_classifier.rs:94 (the IQR sort) and called the audit done, but the grep used `partial_cmp(b).unwrap()` as a literal and missed seven additional production sites that use comparator variants: adaptive_classifier.rs:205 AdaptiveModel::classify() argmax over softmax probs — same per-frame hot path as #611. NaN flows through normalise → logits → softmax and still reaches this site even after the IQR fix. adaptive_classifier.rs:480 train() argmax (training accuracy loop) adaptive_classifier.rs:500 train() per-class argmax main.rs:2446, 2449 count_persons_mincut variance source/sink select csi.rs:602, 605 count_persons_mincut variance source/sink select (duplicate of main.rs logic in csi.rs) For the variance-select sites, note that the *outer* `unwrap_or((0, &0))` only catches an empty iterator — it cannot rescue a panic raised inside the comparator. A single NaN in `variances[]` still aborts the process. Same fix as #613: swap `.unwrap()` for `.unwrap_or(std::cmp::Ordering::Equal)` inside the comparator closure. Pure behavioural change, no API surface. Re-audit of the remaining `partial_cmp(...).unwrap()` matches in v2/: they are all inside `#[cfg(test)]` / `#[test]` blocks (spectrogram.rs:269, depth.rs:234, connectivity.rs:477, vital_signs.rs:737) where inputs are controlled and panic-on-NaN is acceptable. |
||
|
|
e964eaf14f | fix(deps): bump ndarray 0.15→0.17 and ndarray-npy 0.8→0.10 (closes #626) (#627) | ||
|
|
79cc2d7b22 |
Merge #491: feat(sensing-server): adaptive person count — RollingP95 + dedup_factor runtime API
Integrating @schwarztim's PR #491 into main on their behalf — their fork has fallen too far behind for a clean rebase (the PR's commit graph dropped silently during `git rebase origin/main`), so applying as a merge from the fork head to preserve the diff cleanly. What this lands: - `RollingP95` adaptive normaliser for the person-count feature scaling. Streaming P95 over a 600-sample / ~30 s sliding window. Cold-start (<60 samples) falls back to the legacy denominators (variance/300, motion_band_power/250, spectral_power/500) so day-0 behaviour is preserved on every deployment. - `RuntimeConfig` struct + `load_runtime_config` / `save_runtime_config` persisted to `data/config.json`. Exposes `dedup_factor` via REST so multi-node deployments can tune cluster-deduplication without a rebuild, including an auto-tune endpoint that derives optimal dedup from a known person count (calibration mode). - `compute_person_score()` now takes &AppStateInner alongside &FeatureInfo so the adaptive denominators are reachable. All 3 call sites updated. - New `AppStateInner` fields: `p95_variance`, `p95_motion_band_power`, `p95_spectral_power`, `dedup_factor`, `data_dir`. Closes #491. Directly addresses: - #499 (double skeletons, multi-node) — the slot-clustering problem this PR's adaptive normaliser was designed to fix - #519 Bug 1 (ghost person detection on edge-tier 1 & 2 multi-node) - #496 (person count over-reporting on single-room single-person) Verified locally: - cargo check -p wifi-densepose-sensing-server --no-default-features: 1.0s - cargo test -p wifi-densepose-sensing-server --no-default-features --lib: 233/233 passed in 25.0s Co-authored-by: @schwarztim Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
b2e2e6d6fd |
fix(sensing-server): WS broadcast emits effective_source() not hardcoded "esp32" (closes #618) (#621)
Reported by @ArnonEnbar with a complete reproduction. broadcast_tick_task() re-emits the cached `latest_update` every tick so pose WS clients keep getting data even when ESP32 pauses between frames. The `source` field of that cached update was set to "esp32" at the moment a fresh ESP32 frame was last decoded (main.rs:3885, :4136). After the ESP32 loses power or network, no fresh frame is decoded — the cached `latest_update` is still re-broadcast every tick with the stale source: "esp32" baked in. UI's "Sensing" tab keeps showing "LIVE — ESP32 HARDWARE Connected" with frozen vitals/features/ classification re-broadcast indefinitely. REST `/health` correctly reports source: "esp32:offline" (via effective_source(), which checks last_esp32_frame elapsed time against ESP32_OFFLINE_TIMEOUT=5s) — but the WS broadcast path was the one consumer that didn't call it. Fix: clone the cached update per tick, overwrite source with s.effective_source(), then serialize and broadcast. UI now switches to "esp32:offline" on the same 5s budget as the REST surface. cargo build -p wifi-densepose-sensing-server --no-default-features: 17s, no errors (1 pre-existing unused-import warning unchanged). |
||
|
|
72bbd256e7 |
fix(security): path-traversal guard on 5 sensing-server endpoints (closes #615) (#616)
Reported by @bannned-bit. Five endpoints in
v2/crates/wifi-densepose-sensing-server embedded user-controlled
identifiers in format!() paths with no sanitization:
recording.rs POST /api/v1/recording/start (session_name)
recording.rs GET /api/v1/recording/download/:id (id)
recording.rs DELETE /api/v1/recording/delete/:id (id)
model_manager.rs POST /api/v1/models/load (model_id)
training_api.rs load_recording_frames (dataset_ids[])
Each unauthenticated caller could:
- READ arbitrary files via ../../etc/passwd, ../../.env, etc.
- WRITE attacker-controlled JSONL via recording/start
- LOAD attacker-controlled .rvf model files
- DELETE arbitrary files the server process can touch
New `path_safety` module exports `safe_id(&str) -> Result<&str, PathSafetyError>`
that enforces the rejection envelope BEFORE any user input reaches a
format!() that builds a path:
- Allowed character set: [A-Za-z0-9._-]
- Reject leading '.' (rules out '.', '..', '.env', hidden files)
- Reject empty strings
- Reject anything > 64 bytes
- Reject all whitespace, path separators, null bytes, non-ASCII
Applied at all 5 sites. Errors return 400 Bad Request (download) /
status:"error" JSON (others) — not panics.
9 unit tests in path_safety::tests cover:
- accepts simple alphanumeric / hyphen / underscore / dot
- rejects empty, leading dot, path separators ('/', '\'),
null byte, whitespace, shell specials, non-ASCII (including
fullwidth slash U+FF0F), too-long, boundary at MAX_ID_LEN
test result: ok. 9 passed; 0 failed
cargo build -p wifi-densepose-sensing-server --no-default-features: 33s
Fix-marker RuView#615 in scripts/fix-markers.json prevents removing the
guard at any of the 5 call sites. CHANGELOG entry under [Unreleased] /
Security documents the patched endpoints and the rejection envelope.
Severity: critical per reporter — five remotely-reachable paths to read,
write, or delete arbitrary files. Hot per-request paths, not edge cases.
|
||
|
|
3bd70f7910 |
fix(sensing): adaptive_classifier sorts with unwrap_or(Equal) — NaN panic (closes #611) (#613)
Reported by @bannned-bit. v2/crates/wifi-densepose-sensing-server/src/
adaptive_classifier.rs:94 did:
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
f64::partial_cmp returns None on NaN, so `.unwrap()` panics. CSI data
from real ESP32 hardware can produce NaN (silent DSP div-by-zero,
empty buffer, etc.), and this code path runs on every frame in the
classify() hot path — a single NaN frame kills the entire sensing
server process.
Fix swaps for unwrap_or(Ordering::Equal), matching the pattern the
same file already uses at lines 149-150 and 155 (those sites were
already NaN-safe; this site was an oversight).
Scoped audit: greped the v2/ tree for `partial_cmp(b).unwrap()`. The
other 3 hits are in #[cfg(test)] blocks (spectrogram.rs:269,
depth.rs:234, connectivity.rs:477) where panic-on-NaN is acceptable
because test inputs are controlled. Only adaptive_classifier.rs:94
was a production-path crash.
Severity: critical per reporter — runtime panic on real-world data.
Patch: 1-line behavioural change + comment.
|
||
|
|
1b155ad027 |
chore: remove empty stub crates wifi-densepose-{api,db,config} (closes #578) (#608)
Each of these crates was a single-line doc-comment placeholder: v2/crates/wifi-densepose-api/src/lib.rs: //! WiFi-DensePose REST API (stub) v2/crates/wifi-densepose-db/src/lib.rs: //! WiFi-DensePose database layer (stub) v2/crates/wifi-densepose-config/src/lib.rs: //! WiFi-DensePose configuration (stub) with empty [dependencies] in their Cargo.toml and zero references from any source file or Cargo.toml in the workspace (verified by `grep -rln wifi-densepose-api/-db/-config` across `v2/`). They were reserved early for an envisioned REST/database/config split that never materialised. The functionality these would have provided is covered today by: - REST/WS: wifi-densepose-sensing-server (Axum) - Config: per-crate config + CLI args in sensing-server and desktop - DB: no persistent state; system is real-time Removal prevents `cargo` from listing dead crates, shipping empty published artifacts to crates.io, or wasting reviewer attention. If any of these names is needed in the future, reintroduce them with a real implementation. Per the issue reporter (@bannned-bit / Matad0r) #578 explicitly listed "OR be removed from workspace members until implementation starts" as an acceptable resolution. Updated: - `v2/Cargo.toml`: drop the three members (with inline comment explaining why) - `v2/Cargo.lock`: regenerated by cargo check - `CLAUDE.md`: drop the three rows from the crate table and the publishing order list - `CHANGELOG.md`: add an `[Unreleased] / Removed` entry Verified: - `cd v2 && cargo check --workspace --no-default-features` -> finished in 48s, no errors (warnings unchanged) |
||
|
|
0310b1fa9a |
chore(deps): bump @tauri-apps/plugin-dialog (#462)
Bumps [@tauri-apps/plugin-dialog](https://github.com/tauri-apps/plugins-workspace) from 2.6.0 to 2.7.0. - [Release notes](https://github.com/tauri-apps/plugins-workspace/releases) - [Commits](https://github.com/tauri-apps/plugins-workspace/compare/log-v2.6.0...log-v2.7.0) --- updated-dependencies: - dependency-name: "@tauri-apps/plugin-dialog" dependency-version: 2.7.0 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> |
||
|
|
4ecc053a27 |
chore(deps-dev): bump typescript in /v2/crates/wifi-densepose-desktop/ui (#456)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.3. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v5.9.3...v6.0.3) --- updated-dependencies: - dependency-name: typescript dependency-version: 6.0.3 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
4d45add824 |
chore(deps): bump react-dom and @types/react-dom (#451)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) and [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom). These dependencies needed to be updated together. Updates `react-dom` from 18.3.1 to 19.2.5 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-dom) Updates `@types/react-dom` from 18.3.7 to 19.2.3 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom) --- updated-dependencies: - dependency-name: react-dom dependency-version: 19.2.5 dependency-type: direct:production update-type: version-update:semver-major - dependency-name: "@types/react-dom" dependency-version: 19.2.3 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
a80617ee84 |
chore(deps): bump console from 0.15.11 to 0.16.3 in /v2 (#471)
Bumps [console](https://github.com/console-rs/console) from 0.15.11 to 0.16.3. - [Release notes](https://github.com/console-rs/console/releases) - [Changelog](https://github.com/console-rs/console/blob/main/CHANGELOG.md) - [Commits](https://github.com/console-rs/console/compare/0.15.11...0.16.3) --- updated-dependencies: - dependency-name: console dependency-version: 0.16.3 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> |
||
|
|
afc86c6fc4 |
chore(deps): bump thiserror from 1.0.69 to 2.0.18 in /v2 (#469)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.69 to 2.0.18. - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/1.0.69...2.0.18) --- updated-dependencies: - dependency-name: thiserror dependency-version: 2.0.18 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
e6710e8988 |
chore(deps): bump ndarray-linalg from 0.16.0 to 0.18.1 in /v2 (#477)
Bumps [ndarray-linalg](https://github.com/rust-ndarray/ndarray-linalg) from 0.16.0 to 0.18.1. - [Release notes](https://github.com/rust-ndarray/ndarray-linalg/releases) - [Commits](https://github.com/rust-ndarray/ndarray-linalg/compare/ndarray-linalg-v0.16.0...ndarray-linalg-v0.18.1) --- updated-dependencies: - dependency-name: ndarray-linalg dependency-version: 0.18.1 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> |
||
|
|
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> |
||
|
|
bdb4484259 |
chore(deps): bump tch from 0.14.0 to 0.24.0 in /v2 (#482)
Bumps [tch](https://github.com/LaurentMazare/tch-rs) from 0.14.0 to 0.24.0. - [Release notes](https://github.com/LaurentMazare/tch-rs/releases) - [Changelog](https://github.com/LaurentMazare/tch-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/LaurentMazare/tch-rs/commits) --- updated-dependencies: - dependency-name: tch dependency-version: 0.24.0 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> |
||
|
|
ba370c7b08 |
chore(deps): bump tabled from 0.15.0 to 0.20.0 in /v2 (#481)
Bumps [tabled](https://github.com/zhiburt/tabled) from 0.15.0 to 0.20.0. - [Changelog](https://github.com/zhiburt/tabled/blob/master/CHANGELOG.md) - [Commits](https://github.com/zhiburt/tabled/commits) --- updated-dependencies: - dependency-name: tabled dependency-version: 0.20.0 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> |
||
|
|
3fdd310f89 |
chore(deps): bump tauri-plugin-dialog from 2.6.0 to 2.7.1 in /v2 (#480)
Bumps [tauri-plugin-dialog](https://github.com/tauri-apps/plugins-workspace) from 2.6.0 to 2.7.1. - [Release notes](https://github.com/tauri-apps/plugins-workspace/releases) - [Commits](https://github.com/tauri-apps/plugins-workspace/compare/log-v2.6.0...log-v2.7.1) --- updated-dependencies: - dependency-name: tauri-plugin-dialog dependency-version: 2.7.0 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> |
||
|
|
98e7eeda42 |
chore(deps): bump ruvector-core from 2.0.5 to 2.2.0 in /v2 (#479)
Bumps [ruvector-core](https://github.com/ruvnet/ruvector) from 2.0.5 to 2.2.0. - [Release notes](https://github.com/ruvnet/ruvector/releases) - [Changelog](https://github.com/ruvnet/RuVector/blob/main/CHANGELOG.md) - [Commits](https://github.com/ruvnet/ruvector/compare/v2.0.5...v2.2.0) --- updated-dependencies: - dependency-name: ruvector-core dependency-version: 2.2.0 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> |