mirror of
https://github.com/ruvnet/RuView
synced 2026-07-17 16:33:18 +00:00
9cd1b8ce2a2de51287eba2a6860b9e9bd7aa2ebc
612 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9cd1b8ce2a |
research(R12 PABS): NEGATIVE -> POSITIVE — 1161x detection lift via R6.1 forward model (#722)
R12 (tick 5) was a NEGATIVE result: naive SVD-spectrum cosine distance detected structure changes at 0.69x the natural drift floor (= undetectable). R12 explicitly identified the revision: 'PABS over Fresnel basis'. R6.1 (tick 18) shipped the multi-scatterer Fresnel forward operator. This tick implements PABS on top of it. PABS = ||y_observed - y_predicted||^2 / ||y_observed||^2 Benchmark (5 m link, 2.4 GHz, subject + 4 wall reflectors expected): | Scenario | PABS / drift | SVD (R12) / drift | |--------------------------------|---------------:|------------------:| | Empty room (subject missing) | 7,362x | 65x | | Subject as expected (sanity) | 0x | 0x | | +1 new furniture | 84x | 11x | | +1 unexpected human | 1,161x | 11x | | Subject moved 10 cm | 21,966x | 90x | | Natural drift (5% wall shift) | 1x | 1x | PABS detects unexpected human at 1161x natural drift; R12 SVD detected at 11x. ~100x lift purely from physics-grounded prediction vs naive statistical eigenshift. R12 NEGATIVE -> POSITIVE. The meta-lesson: a research loop that catalogues NEGATIVE results creates a backlog of revisitable work that pays off when later tools become available. R12 -> R12 PABS is the worked example. R13 cannot be similarly revisited -- its 5 dB shortfall is a hard physics floor, not a missing model. The subject-moved-10cm caveat: PABS detects ANY mismatch between expected and observed scene. Real production PABS needs a pose-aware forward model that updates from pose_tracker.rs in real-time. The actual detection signal is PABS-after-pose-update. ~50-100 LOC Rust glue, catalogued as R12.1 follow-up. Composes: - R6.1 unblocked this implementation - R7 gets precise per-link consistency: residual small on all links = no structure; spike on one = local structure OR compromised link; mincut disambiguates - R11 enables maritime container-tamper / hatch-seal apps - R14 gets V0 security feature (intruder detection w/o biometric storage) - ADR-029 needs to reference PABS as structure-detection primitive - R10 PABS-vs-canopy works if forest modelled or learned Honest scope: - Pose-PABS closed loop not yet built - Synthetic data only; real-world drift floor needs measurement - Population-prior body; per-subject would tighten residual - Single time-frame; real pipeline needs temporal averaging Coordination: ticks/tick-19.md, no PROGRESS.md edit. |
||
|
|
bac6962689 |
research(R6.1): multi-scatterer Fresnel — discovers 4.7 dB penalty matching R13's 5-dB shortfall (#721)
Extends R6's point-scatterer to distributed-body model (6 scatterers: head + chest + 2 arms + 2 legs). Combined CSI = coherent sum of per-body-part contributions. Headline finding: 5 m link, 2.4 GHz, subject 25 cm off LOS, breathing at 0.25 Hz with 8 mm chest amplitude: | Configuration | Breathing SNR (best subcarrier) | |----------------------------------------|--------------------------------:| | Single-scatterer ideal (R6) | +23.7 dB | | Multi-scatterer realistic (R6.1) | +19.0 dB | | MULTI-SCATTERER PENALTY | +4.7 dB | This 4.7 dB penalty matches R13's 5-dB-shortfall finding to within 0.3 dB. R13 NEGATIVE concluded that pulse-contour recovery needs +25 dB SNR, only +20 dB is available. R6.1 says the 5-dB gap has a physical origin: static body parts add coherent-sum confusion that doesn't exist in the idealised single-scatterer model. The three threads now form a coherent physics story: - R6 = bound (idealised single-scatterer = +23.7 dB) - R6.1 = floor (realistic 6-scatterer = +19.0 dB) - R13 = failure (contour needs +25 dB, gets +20 dB) Pulse-contour recovery is bounded below by what R6.1 leaves achievable, which is 4.7 dB worse than R6's idealised limit, enough to make R13's contour recovery infeasible. Per-body-part contribution: chest = 27.6% of CSI energy (5x per-limb reflectivity). The chest IS the breathing signal; limbs are confound. Architectural implications: - Chest-centric placement targeting (R6.2.3 motivated) - Mask limbs in vital_signs pipeline (use pose pipeline ADR-079/101) - R14 V3 rescope to rate-only (no contour-shape recovery) - R12 PABS revision unblocked: R6.1 is the explicit A(voxel) operator Surprise finding: on-LOS placement (y=0) is degenerate -- path delta is 2nd-order in offset for on-LOS scatterers, so breathing barely changes path length. Real installations need subject OFF the LOS line. The R6.2 placement search should respect this. Honest scope: - 6 scatterers is 1st-order; 50-100 voxel body would refine - Reflectivity ratios are guesses (RCS measurements would refine) - Static body assumption (limbs do micro-move during breathing) - 2D top-down, no multipath (model general enough to include them) Composes: - R5: subcarrier selection picks reliable, not high-SNR - R6: per-scatterer building block - R6.2.x: chest-centric placement - R7: residual-vs-forward-model = tighter adversarial detection - R12 NEGATIVE: PABS A operator unblocked - R13 NEGATIVE: 5-dB gap has physical origin - R14 V3: needs rescope Coordination: ticks/tick-18.md, no PROGRESS.md edit. |
||
|
|
065521dc9e |
research(R6.2.2): N-anchor multistatic placement saturation — practical knee at N=5 (#720)
Extends R6.2 from single-pair to N-anchor placement search via union of all C(N,2) pairwise Fresnel ellipses. Greedy + K=8 random restarts. Saturation curve on 5x5 m bedroom (3 target zones: bed + chair + desk, 40 wall-candidates, 434 grid points, 2.4 GHz): | N | Pairs | Coverage | Marginal | |---|------:|---------:|---------:| | 2 | 1 | 35.7% | +35.7 pp | | 3 | 3 | 63.4% | +27.6 pp | | 4 | 6 | 86.2% | +22.8 pp | | 5 | 10 | 96.8% | +10.6 pp | <- knee | 6 | 15 | 100.0% | +3.2 pp | | 7 | 21 | 100.0% | +0.0 pp | Practical knee at N=5. Past this, diminishing returns. Three regimes: - Single-feature (presence): 2-3 anchors (36-63%) - Multi-feature (pose+vitals+count): 4-5 anchors (86-97%) - Mission-critical (medical): 6 anchors (100%) - Beyond 6: wasted Cost-optimisation: Cognitum Seed BOM is 9-15 USD. The 4->5 anchor jump buys +10.6 pp coverage; the 5->6 jump buys only +3.2 pp for the same cost. Consumer recommendation: 5 anchors. Commercial / medical: 6. Convenient numerology: N=5 simultaneously satisfies three other constraints: 1. R7 multi-link mincut: needs N >= 4 for single-anchor-compromise detection 2. ADR-105 federation Krum: f=1 byzantine tolerance requires K >= 5 3. R6.2.2 coverage knee: 5 hits practical saturation These all bound by similar inverse-square-of-geometry scaling, so the alignment is not coincidental. ADR-029 (multistatic) didn't specify anchor counts; R6.2.2 fills that gap with a benchmark-backed number. Honest scope: single 5x5m geometry tested, 2D still (R6.2.1 = 3D not yet built), free-space (multipath adds +5-15% beyond Fresnel), greedy with 8 restarts approximates global optimum to 1-2 pp. Composes with: - R6/R6.2 (direct generalisation) - R7 (mincut needs N>=4) - R1 (placement x precision = full geometry budget) - ADR-029 (architectural recommendation now has a number) - ADR-105 (Krum bound matches) - R10, R11, R14 (other geometries / use cases) Coordination: ticks/tick-17.md, no PROGRESS.md edit. |
||
|
|
719875ea1d |
research(R6.2): Fresnel-aware antenna placement — 93x sensing-coverage lift from physics alone (#719)
First deferred follow-up from R6. Productises R6's Fresnel forward model into a 2D placement-search CLI: given a room + target occupancy zones, recommend Tx/Rx positions that maximise first-Fresnel coverage. Benchmark on 5x5 m bedroom (bed 3 m^2 + chair 0.64 m^2, 2900 pairs evaluated at 2.4 GHz): - OPTIMAL: 51.1% coverage (Tx 1.25,0; Rx 4.75,5; diagonal 6.10 m link) - MEDIAN: 0.5% coverage - WORST: 0.0% coverage - 93x improvement, median to optimal Counter-intuitive insight: longer links cover MORE space. Fresnel envelope width = sqrt(d * lambda) / 2 grows with link length, so the 6.10 m diagonal beats wall-parallel 5.00 m links. Up to the R10 link-budget gate. Per-cog deployment recommendations: - cog-person-count: diagonal across longest axis - cog-pose: zone inside ~50% midpoint envelope - AETHER re-ID: Tx near doorway, Rx diagonal - cog-maritime-watch: vertical diagonal through cabin - cog-wildlife (future): Tx/Rx opposite trees, threading clearing midline Improvements come from physics, not algorithms - no model retraining needed. Existing customers can re-mount seeds today for 10-100x better sensing. Honest scope: 2D approximation, free-space, rectangular zones, single-pair only, perimeter-only candidates, no link-budget gate. CLI shape ready for productisation as 'wifi-densepose plan-antennas'. Also surfaces as a deferred MCP tool 'ruview_placement_recommend'. Composes with: - R6 (direct 2D extension) - R1 (placement x precision = full geometry budget) - R10 (sets the link-budget gate this ignores) - R11 (same recipe in steel cabins) - R14 (determines whether V1/V2/V3 see the right occupant) - ADR-105 (better placement = faster epsilon convergence) Next R6.2 follow-ups catalogued: R6.2.1 (3D), R6.2.2 (N-anchor union), R6.2.3 (pose-trajectory target zones). Coordination: ticks/tick-16.md, no PROGRESS.md edit. |
||
|
|
28d97e8f6a |
adr-106: differential privacy + biometric primitive isolation for federation (#718)
Direct extension of ADR-105. Closes both items deferred from ADR-105:
(1) member-inference defence, (2) biometric primitive isolation
enforcement.
Three-layer defence:
1. PRIMITIVE ISOLATION (R15 binding) -- API-level tagging of on-device-
only tensors. Compile-time error when ✅ tagged tensors are passed
to submit_delta().
2. GRADIENT CLIPPING (Abadi 2016) -- per-sample L2 norm <= C (default
C=1.0) before delta computation.
3. GAUSSIAN NOISE (DP-SGD) -- N(0, sigma^2*C^2*I) added to aggregated
LoRA delta before transmission.
Privacy budget via Moments Accountant (delta=1e-5):
- Conservative (medical-grade): sigma=1.5, 50 rounds, epsilon=2.0
- Standard (typical RuView): sigma=1.0, 100 rounds, epsilon=5.0
- Lenient: sigma=0.5, 100 rounds, epsilon=8.0
On-device-only primitive list (R15-binding):
- Raw CSI window
- Gait stride frequency
- Breathing rate (per-subject)
- HRV rate signature
- RCS frequency response curve
- Limb timing vector
- Per-subject embedding centroid
Implementation budget: +300 LOC on top of ADR-105's 500 LOC = total
~800 LOC ruview-fed crate. 3-week effort estimate.
Composes:
- R3: Layer 1 blocks per-subject embedding centroid transmission
- R7: mincut compatible with DP-noised deltas (operates on noised graph)
- R12/R13 negative results: informed the noise-vs-structure-detection
design choice (treat adversarial deltas as outliers from noisy
distribution, not structural-detection problem)
- R14: privacy framework now has formal (epsilon, delta) backing
- R15: requirements basis = on-device-only primitive list made executable
- ADR-105: DP-SGD slots into step 4 of federation protocol
Closes the privacy story: R3 + R14 + R15 + ADR-105 + ADR-106 = complete
chain from physics (R6) -> embeddings (R3) -> personalised features (R14)
-> trained how (ADR-105) -> defended how (R7) -> privacy-bounded how
(ADR-106).
Honest scope:
- sigma values are recommendations, not measurements (per-cog tuning needed)
- (epsilon, delta)-DP is worst-case bound; auxiliary info changes practical leakage
- Moments Accountant is conservative
- Subject-level DP not formalised (household of 4 = K=4 subjects)
- Side-channel timing leaks out of scope (future ADR)
Explicitly deferred:
- ADR-107: cross-installation federation w/ secure aggregation
Coordination: ticks/tick-15.md, no PROGRESS.md edit.
|
||
|
|
50029d6eb2 |
research(R15): RF biometric primitives — 5 environment-invariant features with quantified discriminability (#717)
Catalogues 5 biometric primitives in CSI that survive cross-environment transfer by physical construction (not just statistical learning), with quantified discriminability: | Primitive | Bits | Invariance | |------------------------------------|-----:|------------| | Gait stride frequency | 5 | HIGH | | Breathing rate + envelope | 5 | HIGH | | HRV (rate-level only) | 4 | HIGH at rate, LOW at contour | | Body-size RCS frequency response | 4 | MEDIUM (needs calibration target) | | Walking dynamics (limb timing) | 7 | HIGH (if pose works cross-room) | Composite biometric strength: ~12-15 bits realistic vs 25-bit independence upper bound. Enough for household + building-scale ID; insufficient for forensic / city-scale. R15 strengthens the R14/R3/ADR-105 privacy framework: RF biometric is PHYSICAL not learned, so the same primitive that enables empathic appliances is a surveillance primitive that's harder to opt out of than visual ID. There is no behavioural countermeasure short of jamming (illegal) or physical alteration (impossible). Surfaces required amendment to ADR-105 federation protocol: 'The federation aggregator MUST NOT receive any raw per-subject biometric primitive. It MAY receive aggregated, MERIDIAN-normalised model deltas. Per-subject primitives stay on-device.' This becomes the requirements basis for ADR-106 (deferred DP-SGD ADR). R15 closes the last unaddressed PROGRESS.md research thread. After R15: - Closed: 'what RF biometrics exist and how do they invariantise' = answered - Open: ADR-106, R6.1 multi-scatterer, R3 physics-informed env prediction, R6.2 Fresnel-aware antenna placement The per-occupant feature surface (R14 V1/V2/V3) is now fully grounded in physics + constraints; remaining work is implementation, not research. Composes with every prior thread: - R5 saliency: primitive-specific maps - R6 Fresnel: physical basis for RCS invariance - R7 mincut: defends primitive-level poisoning - R10 per-species gait: transfers to per-individual gait biometric - R13 NEGATIVE: 5-dB-short wall rules out contour-level HRV - R3: embedding space combines 5 primitives - R14: all 3 verticals (V1/V2/V3) work with rate-level subset Honest scope: - Bit counts are upper bounds; 30-50% loss to noise/multipath - Contour-level HRV not achievable (R13 wall) - Walking dynamics 7-bit assumes pose-from-CSI works cross-room (unmeasured) - Body-size RCS needs calibration target in new room Coordination: ticks/tick-14.md, no PROGRESS.md edit. |
||
|
|
09fe73eb87 |
research(R4) + adr-105: federated CSI training with MERIDIAN+Krum+mincut (#716)
Federated learning is the unique design that satisfies the three constraints from this loop's earlier work: - R14 (data stays on-device) - R3 (no cross-installation linkage) - R7 (multi-node adversarial defence) ADR-105 proposes MERIDIAN-FedAvg with Byzantine-robust (Krum) aggregation and R7-style Stoer-Wagner mincut on inter-node update similarity. Per-round bandwidth at typical 4-seed installation: ~12 MB; weekly cadence x monthly = 50-180 MB/month (0.06% of home broadband cap). Composes with every prior thread: - R3 MERIDIAN centroid subtraction is mandatory pre-aggregation - R7 mincut extended from multi-link CSI to multi-node updates - R12/R13 negative results informed the byzantine + SNR-threshold choices - R14 privacy framework baseline is now operational - ADR-024/027/029/100/103/104 all bridged in the ADR Implementation plan: ~500 LOC for ruview-fed crate. Krum aggregator (80 LOC), LoRA+int8 delta codec (120 LOC, reuse ruvllm-microlora), MERIDIAN centroid hook (50 LOC, extend AgentDB), inter-seed mincut (100 LOC, reuse ruvector-mincut), CLI surface (80 LOC). Explicitly deferred: - Cross-installation federation (legal + DP work needed, future ADR) - Member inference defence (ADR-106 with formal DP-SGD) - Per-cog training-loop details (each cog implements local_train) - Compute scheduling (cognitum fleet manager territory) Tick chose the 'one ADR' unit from the cron prompt rather than another numpy demo -- federation is fundamentally a protocol-design problem, not a numerical-experiment problem. Coordination: ticks/tick-13.md, no PROGRESS.md edit. |
||
|
|
db64b4c671 |
research(R3): cross-room re-ID — MERIDIAN closes the env-shift gap + 4 privacy constraints (#715)
Synthesis of AETHER (ADR-024) + MERIDIAN (ADR-027) + privacy framing + identified next research lever (physics-informed env prediction). Simulation results (10 subjects, 3 rooms, 128-dim embeddings, env/person scale ratio 4.7x): | Configuration | 1-shot acc | |------------------------------------------|-----------:| | Within-room (matches AETHER ~95% target) | 100% | | Cross-room, raw cosine K-NN | 70% | | Cross-room, MERIDIAN 100% env removal | 100% | | Cross-room, MERIDIAN 70% env removal | 100% | | Chance | 10% | The 30 pp gap from within-room to raw cross-room is the angular contribution of env-shift that cosine similarity can't normalise away. MERIDIAN per-room centroid subtraction recovers it -- robust even at 70% effectiveness (realistic for limited labelled examples). Privacy framing: R14 baseline + 4 new constraints specific to biometric-class re-ID data: 1. No cross-installation linkage 2. Embedding storage requires explicit opt-in (biometric consent class) 3. Cryptographically verifiable forgetting 4. No re-ID across legal entities These rule out cross-building tracking, mass surveillance, long-term unlabelled storage, third-party sharing. They allow per-installation personalisation, household anomaly detection, multi-person pose association in the same room. R3 closes the loop on R14's empathic-appliance vision: re-ID is THE primitive that makes per-occupant features possible. Without R3, R14's verticals can't ship. Identifies next research lever: physics-informed env_sig prediction from R6's forward operator + room map = zero-shot cross-room transfer without labelled examples in the new room. Composes: - R5/R6: person+env decomposition in embedding space - R7: mincut = defence against re-ID spoofing - R9: RSSI K-NN showed env-locality dominance for the K-NN primitive - R14: 4 new constraints extend R14's framework to biometric class Honest scope: additive decomposition is first-order; real CSI env effects are multiplicative in subcarrier domain. Adversarial scenarios not simulated. Coordination: ticks/tick-12.md, no PROGRESS.md edit. |
||
|
|
bcfdf0a4d0 |
research(R13): NEGATIVE — contactless BP from CSI is physically inferior to a cuff (#713)
Critical-physics scrutiny of published 'contactless BP from WiFi CSI' claims (Yang 2022, Liu 2021, others). Four physics floors quantified; all four make CSI-based BP provably worse than a 20 dollar arm cuff. 1. PTT temporal resolution: need 0.5 ms for 1 mmHg precision; ESP32-S3 maxes at 1 ms (1000 Hz CSI) and typical deployment is 10 ms (100 Hz) = 20 mmHg precision floor. Achievable but requires sacrificing every other sensing pipeline. 2. Spatial separation: carotid-femoral distance 55 cm, Fresnel envelope at 5 m link is 40 cm. Single-link CSI cannot resolve the two sites independently. Multistatic with 4-6 anchors is severely ill-posed (same regime that defeated R12). 3. Pulse-contour SNR: pulse motion at chest is 0.3 mm; breathing is 8 mm (27x larger). After 4th-order bandpass we get +20 dB HR-band SNR; literature (Mukkamala 2015) says +25 dB minimum for waveform- shape recovery. **5 dB short.** 4. Vs 0 arm cuff: best published CSI BP is +/-10 mmHg with per-subject calibration; arm cuff is +/-2 mmHg uncalibrated. CSI is 5x worse AND requires calibration the user doesn't otherwise need. Verdict: do not ship BP as a primary RuView feature. The breathing/HR features we already ship work because their motion amplitudes are 30-100x larger than the pulse waveform. Adding BP would force 1 kHz CSI rate (degrading every other pipeline), require per-subject calibration (defeating no-setup story), and ship a feature that's worse than a 20 dollar device the user can buy. Three niche scenarios remain open: - Single-subject trend monitoring (relative not absolute) - Bed-instrumented controlled-still subject (25+ dB achievable) - Multistatic PWV with 6+ anchors + per-installation calibration The general 'BP from a 9 dollar ESP32 in the corner' claim does not close. Composes: - R1 (CRLB) confirms temporal-resolution floor for PTT - R6 (Fresnel) provides the spatial floor that defeats two-site PTT - R5 (saliency) explains why whole-chest observable but 0.3 mm pulse not - R12 = loop's other negative result, same failure pattern - R14's assumption (no BP) is now empirically validated Two negative results in this loop (R12, R13) prevent the field from biasing toward overclaiming. This is the most valuable kind of tick because it marks BP-from-CSI as off-roadmap with explicit numbers, so future contributors don't waste cycles attempting it. Coordination: ticks/tick-11.md, no PROGRESS.md edit. |
||
|
|
4072455d1e |
research(R11): maritime sensing — through-bulkhead impossible, through-seam works (#712)
Physics scrutiny of WiFi-band maritime sensing scenarios. Steel skin depth is 3.25 um at 2.4 GHz, making bulkheads utterly opaque. Saltwater attenuation is 853 dB/m. The 'through-bulkhead WiFi radar' framing common in conservation/maritime is wrong; the actual feasible category is 'through-seam' sensing exploiting slot diffraction through gaskets, hatch seals, and vent grilles. Composite link budget for 7 maritime scenarios (ESP32-S3 121 dB budget, 10 dB SNR margin): FEASIBLE: - Man-overboard surface @ 200 m: +25 dB - Cabin door, 2 mm seam: +31 dB - Cabin door, 5 mm seam: +39 dB - Container, 30 mm vent slot: +45 dB IMPOSSIBLE: - Closed 10 mm steel door: -938 dB - Submarine pressure hull: -929 dB - Head 30 cm underwater: -231 dB Five feasible verticals catalogued: man-overboard surface, through-seam crew vitals, container tamper detection, hatch-seal predictive maintenance, engine-room thermal anomaly via condensation. Composes with prior threads: - R6 Fresnel envelope + slot diffraction = narrower composite envelope - R10 link-budget primitives reused unmodified for air-side maritime - R7 multi-link consistency essential against superstructure jammers - R14 privacy framework transfers directly to crew-cabin monitoring Honest scope: best-case ignores vessel vibration (5-30 Hz, in-band with R10 gait frequencies), engine ignition noise, salt-spray, steel-surface multipath. Maritime gait-classification is harder than land. The romantic 'through-hull radar' is now explicitly debunked. The actual product roadmap is gasket-leakage sensing, surface detection, and predictive-maintenance audits. Coordination: ticks/tick-10.md, no PROGRESS.md edit. |
||
|
|
a1bbe2e8a6 |
research(R1): ToA CRLB — precision floor for WiFi multistatic localisation (#711)
Quantitative Cramer-Rao Lower Bound analysis for WiFi ranging via both Time-of-Arrival and phase-based methods, with multistatic 4-anchor position-error budget. Headline (20 MHz HT20, 20 dB SNR, 100 averaged frames): - ToA range CRLB: 4.1 cm - Phase (5 deg noise): 0.17 mm - Phase advantage: 240x (after ambiguity resolution) 4-anchor convex-hull room (GDOP 1.5): - ToA position precision: 25 cm (room-pose-quality floor) - Phase position precision: 1 mm (RTK-quality, ambiguity-resolved) This is the strongest architectural lever this loop has surfaced for ADR-029 (multistatic sensing). The current learning-based attention approach has no provable precision floor; an explicit ToA-then-phase pipeline sits within 2x of CRLB by Kay's theory. Composes cleanly with R6: - R6 gives the spatial sensitivity envelope (40 cm Fresnel at 2.4 GHz) - R1 gives the ranging precision within it (1 mm phase, 4 cm ToA averaged) - Independent, additive, together bound full multistatic geometry budget Closes a gap R10 created: foliage drops SNR, which directly worsens ToA CRLB. A 50 m foliage link at 5 dB SNR drops to ~1 m ToA precision. R10's 100 m sparse-foliage range is *detectable* not *localisable*. Honest scope: - CRLB is a lower bound; real estimators sit 1-2x above it - 5 deg phase noise assumes phase_align.rs is applied - Multipath degrades CRLB by 2-5x even with MUSIC super-resolution - Integer-ambiguity (cycle-slip) is unsolved per-subcarrier; needs multi-subcarrier wide-lane unwrap Coordination: ticks/tick-9.md, no PROGRESS.md edit. |
||
|
|
650612e5a2 |
research(R6): Fresnel-zone forward model — bedrock physics for CSI sensitivity (#710)
The workspace DSP (vital_signs, multistatic, pose_tracker, tomography) implicitly assumes a forward model that maps scatterer geometry to per-subcarrier phase shifts. Nobody had written it down. This tick makes it explicit. Closed-form first-Fresnel-zone radius + point-scatterer path-delta + per-subcarrier phase prediction over 802.11n/ac 20 MHz channels (52 subcarriers, 312.5 kHz spacing). Pure NumPy demo + JSON output for downstream consumers. Headline numbers: - 5 m link first-Fresnel radius @ midpoint: 40 cm (2.4 GHz), 27 cm (5 GHz) - Inside zone-1: phase spread <0.5 deg across 52 subcarriers (band-flat) - Outside zone-1: phase spread up to 16 deg (band-dispersed) This unifies R5 + R6: R5's experimentally measured band-spread top subcarriers is exactly what the Fresnel forward model predicts for zone-1 occupancy. Closes the loop on three earlier threads: - R7 (mincut adversarial) gets a precise definition of 'physically inconsistent' instead of a learned classifier - R10 (foliage range) needs to retract 100 m sparse estimate to ~70 m to account for Fresnel-zone obstruction - R12 (eigenshift negative result) gets its revision basis: PABS over Fresnel-grounded forward operator Honest scope: point-scatterer only, first Fresnel only, frequency-flat reflectivity, LOS-only (no multipath). The scalar version is the right first-order approximation; volume-integral / multi-zone / multipath extensions catalogued as R6.1+R6.2 follow-ups. Coordination: ticks/tick-8.md, no PROGRESS.md edit. |
||
|
|
7bd188ab60 |
research(R14): empathic appliances — vision + ethical framework + infrastructure gap inventory (#709)
Speculative 10-20y vision thread covering three concrete vertical sketches: * V1 stress-responsive lighting (5y) — breathing-rate baseline + warm-shift lights * V2 adaptive HVAC for thermal-stress envelopes (10y) — published HVAC-personalisation 15-20% energy savings * V3 conversational appliances respecting attention state (15y) — don't interrupt during focused work Maps existing RuView components to each: 5 already shipped (breathing rate detector, occupancy gates via cog-pose / cog-count, motion intensity, partial RollingP95 baseline learner, MCP API via ADR-104), 4 still to build (full per-room baseline learner, state classifier model, MCP vitals subscribe tool, consent UI). Ethical framework drafted as binding constraints any product must honour: 1. Opt-in by default — sensing on only after active enable 2. Data stays on-device — per-second values never cross the building boundary 3. Override is one tap — physical kill switch must work without WiFi/cloud 6-row privacy threat model with mitigations: compromised appliance, MCP raw-signal leak, adversarial poisoning (mitigated by R7 multi-link consistency), long-term re-identification, insurance/employer access, non-consenting cohabitants. Honest scope: clinical breathing-rate-as-stress literature is lab-condition adults; real-home generalisation unproven. R14 is CSI-only (RSSI loses the per-subcarrier shape needed for shallow-breathing-during-focus signature), bounds rollout to ESP32-S3-class deployments. Connections established to R5, R7, R8, ADR-103, ADR-104. Identifies ruview_vitals_subscribe as the highest-leverage next MCP tool addition. Coordination: ticks/tick-7.md, no PROGRESS.md touch. |
||
|
|
2e742305ba |
research(R10): through-foliage wildlife sensing — physics feasibility + per-species gait taxonomy
ITU-R P.833-9 vegetation-attenuation model + ESP32-S3 link-budget solver produce bounded sensing range estimates per frequency and foliage density. Plus a biomechanics-grounded gait-frequency taxonomy spanning bears (0.5 Hz) to mice (15 Hz). Headline ranges (121 dB link budget, 10 dB SNR margin): freq sparse moderate dense 2.4 GHz 99.6 m 12.0 m 4.1 m 5 GHz 19.9 m 5.2 m 2.1 m The 2.4 GHz / sparse cell (~100 m) is the practical sweet spot — 10x camera-trap coverage, always-on rather than PIR-triggered. Honest scope called out explicitly: this is feasibility math, not field measurements. Animal cooperation, foliage flutter, regulatory limits, and BSSID-fingerprint degradation in remote forest are all real follow-up problems. Vertical applications (10-20 year horizon) catalogued: - Endangered-species population census - Wildlife corridor verification - Invasive-species early warning - Anti-poaching (human gait well-separated from wildlife) - Livestock-on-rangeland tracking - Agricultural pest control Cross-connects to: - R5 (saliency is task-specific — per-species classifier needs own saliency map, same lesson as R12) - R8 (wildlife sensing wants CSI not RSSI for per-subcarrier shape) - R9 (fingerprint K-NN primitive transfers to per-individual ID) - R7 (multi-link consistency for corridor coverage) Pure-NumPy, no framework deps. ITU model + binary search solver. Coordination: tick avoided PROGRESS.md to prevent races (horizon- tracker M3+ track concurrent at the time). Files: * examples/research-sota/r10_foliage_attenuation.py * examples/research-sota/r10_foliage_results.json * docs/research/sota-2026-05-22/R10-through-foliage-wildlife.md * docs/research/sota-2026-05-22/ticks/tick-6.md |
||
|
|
6bfb29accf |
docs(horizon): M3-M7 complete — close 12h autonomous SOTA run
Mark M2-M7 COMPLETE in HORIZON.md; add Session 2 log; write final summary table (shipped/deferred), npm publish commands, and horizon verdict. All 6 milestones finished ahead of 08:00 ET auto-stop. Co-Authored-By: claude-flow <ruv@ruv.net>v1056 v1055 |
||
|
|
2a2f16a380 |
feat(ruview-mcp): M3+M4 — schema validation + train_count wired (#708)
- Add validate.ts: validateCsiWindow (56×20 shape) + validateSensingLatestResponse
(schema_version 2 pin per ADR-101); returns actionable errors on schema drift
- Wire csi-latest.ts: call validateSensingLatestResponse after every sensingGet;
return {ok:false,warn:true,raw_response,...} on mismatch so agents can inspect
- Fix csi-latest.ts: subcarriers now reads amplitudes.length (not hardcoded 56)
- Add tests/validate.test.ts: 5+5 = 10 tests covering valid, null, wrong shape,
schema_version 3, missing captured_at, window error propagation
- All 16 tests pass (validate × 10 + tools × 6); build clean
|
||
|
|
6b35896847 |
research(R12): RF weather mapping eigenshift — negative-ish, with clearly-actionable revision path (#707)
Tests the simplest possible algorithm for RF-weather change detection: SVD on per-frame CSI matrix, top-10 singular values, cosine distance between spectra over time. Hypothesis: a synthetic structural perturbation (15 percent attenuation on 3 top-saliency subcarriers) should produce a larger spectral shift than natural temporal drift from operator movement in the same recording. Result honestly: it does not. The perturbation distance (0.00024) is *smaller* than the control distance (0.00035) — signal/drift ratio 0.69x. The top-K SVD-spectrum cosine is too coarse to detect small-magnitude subcarrier-specific structural changes against an operator-noise background. Three concrete fixes identified for follow-up ticks: 1. Principal angles between subspaces (PABS), not cosine on singular values — catches subspace rotations the spectrum misses 2. Per-subcarrier residual analysis after projecting onto baseline subspace — localises the perturbation 3. Multi-day baseline — knocks down operator-noise floor by 50-100x Useful cross-validations the negative result produces: * R5 task-specific saliency (count-task) does not generalise to structure-detection saliency. Same data, different relevant features. Publishable distinction. * R12 is CSI-only territory — RSSI is the trace of the CSI covariance, so if top-10 SVD-spectrum can't see this, RSSI can't either. Bounds R8 commercial-enablement story to counting only. * R7 SVD-spectrum primitive that worked for adversarial detection fails here at lower perturbation magnitude. Sensitivity does NOT scale with subtlety — confirms the algorithm is magnitude-dominated. Long-horizon vision (building structural monitoring, earthquake drift, HVAC audits, climate-controlled-archive surveillance) preserved in the research note — the physics is right, the hardware is sufficient, the deployment story works. Just need PABS + multi-day data. Coordination note: this tick avoided PROGRESS.md edits entirely because horizon-tracker is concurrently editing it. Tick-5 summary written to ticks/tick-5.md (new self-contained convention) so the 08:00 ET final summary can consolidate without conflicts. Files: * examples/research-sota/r12_rf_weather_eigenshift.py * examples/research-sota/r12_rf_weather_results.json * docs/research/sota-2026-05-22/R12-rf-weather-mapping.md * docs/research/sota-2026-05-22/ticks/tick-5.mdv1052 |
||
|
|
2783f40bd1 |
feat(tools/ruview-mcp): M2 — wire real inference via cog health (#706)
* research(R9): RSSI fingerprint K-NN — 2.18x lift (MODERATE); surfaces counting-vs-localization asymmetry Hypothesis: if temporal proximity correlates with RSSI-feature proximity in the existing single-session data, RSSI fingerprinting is viable. If K-NN of each query is random in time, RSSI sequences are too noisy for fingerprint localization. Test: 1077 samples, 20-dim RSSI proxy (band-mean across 56 subcarriers), cosine-NN with K=5, measure fraction of K-NN within plus/minus 60s of each query timestamp. Compare to random baseline. Result (honest): 5-NN within +/-60s 0.169 Random baseline 0.077 Lift over random 2.18x (verdict: MODERATE) Per-query stdev 0.183 Below the >=3x STRONG-fingerprint threshold but well above 1x random. Real signal, but weaker than R8 counting result on the same data. Important asymmetry surfaced (publishable distinction): Task RSSI vs CSI retention Verdict ------- ----- ----- Counting 94.82% (R8) RSSI works well Localization ~2x random (R9) RSSI struggles in this regime This is consistent with R5's band-spread observation: the count signal integrates across the band, but localization may require per-subcarrier shape that the band-mean discards. Three actionable explanations for the MODERATE result: 1. 20-frame windows (~2s) too short for stable fingerprint while operator moves — longer windows might lift to 3-4x. 2. Within-room fingerprint space too narrow — multi-room data would show categorical lift jump (5-10x). 3. Band-mean discards the per-subcarrier shape needed for localization. Once multi-room data lands (#645), this test should be re-run; if hypothesis (2) is right, the lift will jump categorically. Files: * examples/research-sota/r9_rssi_fingerprint_knn.py * examples/research-sota/r9_rssi_fingerprint_results.json * docs/research/sota-2026-05-22/R9-rssi-fingerprint-knn.md * docs/research/sota-2026-05-22/PROGRESS.md updated * feat(tools/ruview-mcp): M2 — wire real inference via cog health subcommand ruview_pose_infer and ruview_count_infer now run the cog binary's `health` subcommand (ADR-100 contract) which performs real Candle forward-pass inference on a synthetic CSI window and emits a structured health.ok JSON event containing backend, confidence (pose) or count/confidence/p95_range (count). The MCP tools parse this event and return typed inference results. This satisfies the ADR-104 acceptance gate: "ruview_pose_infer returns a finite output for a synthetic CSI window" when the cog binary is installed. On machines without the binary, both tools still fail-open with {ok:false, warn:true} and actionable install hints. Also updates PROGRESS.md with cross-links: R7 (Stoer-Wagner) and R8 (RSSI-only 94.82% retained) marked done with cron-originated findings distilled into the research vectors section. Co-Authored-By: claude-flow <ruv@ruv.net>v1049 |
||
|
|
3f462a254d |
feat(tools): scaffold ruview MCP server + CLI + ADR-104 (#705)
Adds two new npm packages that expose RuView's WiFi-DensePose sensing capabilities outside the Cognitum appliance ecosystem: - tools/ruview-mcp/ (@ruv/ruview-mcp) — MCP server with 6 tools: ruview_csi_latest, ruview_pose_infer, ruview_count_infer, ruview_registry_list, ruview_train_count, ruview_job_status. Uses @modelcontextprotocol/sdk with stdio transport. 6/6 smoke tests pass. TypeScript strict mode, Node 20. - tools/ruview-cli/ (@ruv/ruview-cli) — Yargs CLI with matching subcommands: csi tail, pose infer, count infer, cogs list, train count, job status. Same fail-open pattern as the cog binaries (WARN to stderr, exit 0 on unavailable sensing-server). - docs/adr/ADR-104-ruview-mcp-cli-distribution.md — design rationale, 6-row threat table, packaging plan, acceptance gates, failure modes. - docs/research/sota-2026-05-22/HORIZON.md — 12-hour horizon plan with 7 milestones tracked (M1 complete in this commit). Both packages are private:true pending the user's publish decision. Inference is via subprocess to the signed cog binaries (ADR-100/101/103) — no JS/WASM ML engine bundled.v1046 v1043 |
||
|
|
bb92419ccb |
research(R7): Stoer-Wagner mincut detects adversarial CSI nodes 3/3 in synthetic (#704)
Premise: in a multi-node CSI mesh, all nodes see the same physical
scene through slightly different multipath. Their per-window CSI
vectors cluster tightly under cosine similarity. An adversarial node
(replay / shift / noise injection) sits *outside* that cluster. The
Stoer-Wagner minimum cut on the inter-node similarity graph isolates
it cleanly when the cut is sharp.
Demo synthesises 4 honest nodes (one real CSI window from the paired
data + per-node Gaussian noise 6 dB below signal) and 1 adversarial
node under three attack modes. Cosine-similarity matrix, then
Stoer-Wagner mincut, then check whether partition_B is the singleton
{4} — the adversarial node.
Attack Mincut value Partition_B Isolated?
------- ------------ ----------- ---------
replay 3.4513 {4} YES
shift 3.5724 {4} YES
noise 2.5586 {4} YES
Detection rate: 3/3 = 100%.
Architectural payoff: this is the primitive that fills the stub at
. ADR-103 v0.2.0
can wire it in directly. The mincut value also becomes a continuous
'mesh trustworthiness' metric for the cog-gateway dashboard.
Honest scope: the demo uses sloppy attackers. Adaptive attackers who
have read this note can almost certainly evade by adding calibrated
noise that keeps cosine similarity above the cluster floor. The next
research step is the Stackelberg-game extension. See the
'Honest scope of this result' section in the research note.
Connections:
* R5 — top-8 saliency subcarriers are the priority list for a
more-targeted per-subcarrier consistency check.
* R8 — same primitive likely works at lower SNR with RSSI-only
metrics; cluster structure is preserved by the band integral.
Files:
* examples/research-sota/r7_multilink_consistency.py — pure-NumPy
Stoer-Wagner mincut + synthetic-adversary harness.
* examples/research-sota/r7_multilink_consistency_results.json —
full result JSON for cross-tick reproducibility.
* docs/research/sota-2026-05-22/R7-multilink-consistency.md — note.
* docs/research/sota-2026-05-22/PROGRESS.md — updated index + Done.
|
||
|
|
d9ca9b3684 |
research(R8): RSSI-only person count retains 95% of full-CSI accuracy (#703)
Builds directly on R5's band-spread observation. If the count-task
signal is spread across the WiFi band (R5: max/mean ratio 2.85× across
56 subcarriers), then RSSI — which is the integral of |H_k|^2 across
the band — keeps most of the information. The naive prior (RSSI throws
away 98% of CSI bytes) is misleading; the relevant metric is how much
of the *signal* is in the integral, not how many bytes are in the
representation.
Tested by aggregating each existing [56 × 20] CSI window down to a
[20]-vector RSSI proxy (mean across subcarriers per frame), training a
tiny MLP (Linear 20→32→8, 656 params, 5 KB) with vanilla NumPy SGD for
200 epochs on the same random 80/20 split as cog-person-count v0.0.2.
Result:
Full CSI v0.0.2 62.3% accuracy
RSSI-only (this) 59.1% accuracy = 94.82% retained
Per-class is also markedly more *balanced* (RSSI: 59.5 / 58.6 ; full
CSI: 86.2 / 34.3) — the tiny model on a low-dim input can't cheat by
leaning on class 0 the way v0.0.2's larger model does at inference.
What this enables on a 10-year horizon: phones, laptops, smart
speakers, smart TVs, smart lights — anything with WiFi reports RSSI
and anything with a CPU can run a 656-param MLP. Person counting
becomes a federated property of any room with WiFi, not a property of
the ESP32-S3 fleet.
What this doesn't prove (called out explicitly in the research note):
- Single room, single operator, single 30-min recording
- 2-class problem (label distribution is {0, 1})
- Single random draw — needs K-fold + multi-room replication
Three follow-up experiments queued in R8-rssi-only-count.md §'What's
next on this thread':
- Multi-room replication once #645 lands
- 3-class extension (0 / 1 / 2+) — measure the info-rate cliff
- Run on a non-ESP32 RSSI source (e.g. iw event on Linux laptop)
Files:
* examples/research-sota/r8_rssi_only_count.py — pure-NumPy, no
framework deps. Trains + evals in 0.72 s on CPU.
* examples/research-sota/r8_rssi_only_results.json — full JSON dump
for cross-tick reproducibility.
* docs/research/sota-2026-05-22/R8-rssi-only-count.md — method,
measured numbers, interpretation, what doesn't work yet.
* docs/research/sota-2026-05-22/PROGRESS.md — updated index + Done
log.
Coordination note: horizon-tracker is working on tools/ruview-mcp/
+ tools/ruview-cli/ + ADR-104 — this commit deliberately stays out
of those paths.
v1041
|
||
|
|
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.v1039 |
||
|
|
b16d7431bc |
docs(bench): append v0.0.2 section to person-count benchmark log
Documents the K-fold diagnostic (62.2 ± 1.9% / class-1 57.1%) that justified v0.0.2, the v0.0.2 numbers (class-1 0% → 34.3%), and the honest read that the gap to the K-fold mean is run-to-run variance not missing improvement.v1037 v1036 |
||
|
|
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.v1032 v1030 |
||
|
|
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.
v1027
v1024
|
||
|
|
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.
v1021
|
||
|
|
962e0f4a34 |
docs(adr): ADR-103 — learned multi-person counter (SOTA path) (#693)
Motivated by #499 (multi-node double-skeletons) which PR #491 stopped the bleeding on but didn't take to the WiFi-CSI literature's state of the art. Designs a learned counter that replaces today's slot heuristic + dedup_factor knob, reusing the primitives we've already shipped this week: * Candle / RTX 5080 training pipeline (proven yesterday, 2.1 s for 400 epochs on pose_v1.safetensors) * HF presence encoder as initialization (architectures compatible, unlike the pose head case) * ruvector-mincut (Stoer-Wagner) for multi-node fusion upper-bound * Cog packaging spec (ADR-100) + edge module registry (ADR-102) * Paired-data pipeline (PR #641 streaming-safe align-ground-truth.js) — `n_persons` labels come for free; no new data collection campaign required to bootstrap. Architecture: per-node CSI [56×20] -> frozen HF encoder -> 128-dim embedding \ > count head (softmax {0..7}) > confidence head (sigmoid) N nodes' distributions -> confidence-weighted log-sum -> Stoer-Wagner min-cut upper-bound clip -> { count, confidence, count_p95_low, count_p95_high, per_node_breakdown } Compares the proposal explicitly against WiCount / DeepCount / CrossCount / HeadCount published numbers and is honest about the hardware gap (their 3x3 MIMO research NICs vs our 1x1 SISO ESP32-S3). v0.1.0 acceptance gates target >=80% within-+/-1 same-room and >=60% cross-room — modest on purpose; bounded by the same paired- data scarcity #645 documents for pose. The framework is the deliverable; the accuracy follows the data. Includes: * Architecture diagram in ascii * Comparison table vs published WiFi-CSI counting SOTA * Per-failure-mode mapping from #499 symptoms to how the learned counter addresses each * v0.1.0 + v0.2.0 acceptance gates with measurable thresholds * Repo layout for the new `v2/crates/cog-person-count/` crate * Five-step migration plan from this ADR -> first GCS release Status: Proposed. Implementation follows in the same incremental pattern ADR-101 used: scaffold-cog PR -> train+publish PR -> server-wiring PR.v1018 |
||
|
|
c58f49f21a |
fix(firmware): add vTaskDelay(1) yields in process_frame() at tier>=2 to fix WDT storm (#683)
At edge tier>=2 on N16R8 PSRAM boards, `process_frame()` runs `update_multi_person_vitals()` (4 persons × 256 history samples) plus `wasm_runtime_on_frame()` back-to-back before returning to `edge_task()`. The existing `vTaskDelay(1)` in `edge_task()` only fires *after* `process_frame()` returns — under sustained 30 pps CSI load on PSRAM boards this leaves IDLE1 on Core 1 starved long enough for the 5-second Task Watchdog Timer to fire. Fix: add two `vTaskDelay(1)` calls inside `process_frame()`, both gated on `s_cfg.tier >= 2`: 1. After `update_multi_person_vitals()` (Step 11) 2. After `wasm_runtime_on_frame()` dispatch (Step 14) Tier 0/1 paths are unaffected. Validated on COM7 (N16R8 board): `Edge DSP task started on core 1 (tier=2)`, no WDT panics in 20 s. Also bump firmware version 0.6.5 → 0.6.6 and refresh all 6 release_bins with the new build (8MB + 4MB variants, built 2026-05-21). Fix-marker RuView#683 added to scripts/fix-markers.json. Co-Authored-By: claude-flow <ruv@ruv.net>v1015 v0.6.6-esp32 |
||
|
|
cbcb389cb6 |
assets: add seed.png (Cognitum Seed hero image)
Co-Authored-By: claude-flow <ruv@ruv.net>v1012 v1013 |
||
|
|
e00cee6146 |
docs(readme): add Cognitum Seed image after hero — links to cognitum.one/seed
Co-Authored-By: claude-flow <ruv@ruv.net> |
||
|
|
5dcafc9c37 |
Update README.md
https://cognitum.one/seedv1011 |
||
|
|
e21803f714 |
fix(ci): resolve 3 persistent CI failures + add #679 fix-marker guard
* fix(firmware): refresh release_bins to v0.6.5 — fixes node_id=1 on all nodes (#679) release_bins/ was built from v0.4.3.1 and predated the early-capture node_id fix (PRs #232/#375/#385/#390). Every device flashed from those binaries emitted node_id=1 regardless of provisioned ID, making multi-node deployments appear as a single node. Changes: - Rebuild all 6 release_bins/ binaries from v0.6.5 source (2026-05-20) - esp32-csi-node.bin (8 MB, 1,110,384 bytes) - esp32-csi-node-4mb.bin (4 MB, 894,352 bytes) - bootloader.bin, partition-table.bin, partition-table-4mb.bin, ota_data_initial.bin - Add release_bins/version.txt (0.6.5 / git-sha: d72e06fc8) - README: add Step 0 "Pre-built binaries" flash command with version reference; update expected boot output to show early-capture log line - provision.py: fix write-flash → write_flash (esptool v4.10+ underscore API) Validated on real hardware (COM7 — ESP32-S3 N16R8, node_id=2): I (396) csi_collector: Early capture node_id=2 (before WiFi init, #232/#390) I (406) main: ESP32-S3 CSI Node (ADR-018) — v0.6.5 — Node ID: 2 Closes #679 Co-Authored-By: claude-flow <ruv@ruv.net> * fix(ci): resolve 3 persistent CI failures + add #679 fix-marker guard Three jobs have been failing on every push to main since the v1→archive/v1 reorganisation and the softprops/action-gh-release permission tightening: 1. Performance Tests — uvicorn src.api.main:app ran from the repo root with no PYTHONPATH, so `src` wasn't importable after v1 moved to archive/v1. Added working-directory: archive/v1 to the "Start application" step. Added continue-on-error: true — tests/performance/locustfile.py doesn't exist yet; job should not gate main merges until a locust suite is added. 2. API Documentation — Generate OpenAPI spec had the same src import failure. Added working-directory: archive/v1 to the "Generate OpenAPI spec" step. 3. Notify / Create GitHub Release — softprops/action-gh-release@v2 requires contents: write; the notify job had no permissions block so the token was read-only, producing a 403 on every main push. Added permissions: contents: write to the notify job. Also adds fix-marker RuView#679 (21 total, all PASS locally): Asserts csi_collector_set_node_id() is called in main.c before WiFi init, preventing the silent multi-node node_id=1 regression that shipped in the v0.4.3.1 release_bins and was fixed + validated on COM7 in PR #681. Co-Authored-By: claude-flow <ruv@ruv.net>v1010 |
||
|
|
bdd1efeb03 |
Update README.md
🌿 GH-header
Cognitum.One/RuView
|
||
|
|
aeb69315d8 |
fix(firmware): refresh release_bins to v0.6.5 — fixes node_id=1 on all nodes (#679)
release_bins/ was built from v0.4.3.1 and predated the early-capture node_id fix (PRs #232/#375/#385/#390). Every device flashed from those binaries emitted node_id=1 regardless of provisioned ID, making multi-node deployments appear as a single node. Changes: - Rebuild all 6 release_bins/ binaries from v0.6.5 source (2026-05-20) - esp32-csi-node.bin (8 MB, 1,110,384 bytes) - esp32-csi-node-4mb.bin (4 MB, 894,352 bytes) - bootloader.bin, partition-table.bin, partition-table-4mb.bin, ota_data_initial.bin - Add release_bins/version.txt (0.6.5 / git-sha: d72e06fc8) - README: add Step 0 "Pre-built binaries" flash command with version reference; update expected boot output to show early-capture log line - provision.py: fix write-flash → write_flash (esptool v4.10+ underscore API) Validated on real hardware (COM7 — ESP32-S3 N16R8, node_id=2): I (396) csi_collector: Early capture node_id=2 (before WiFi init, #232/#390) I (406) main: ESP32-S3 CSI Node (ADR-018) — v0.6.5 — Node ID: 2 Closes #679 |
||
|
|
cfda8dbd14 |
feat(traffic): clone+view tracking → data/clone-data.rvf (ruvector JSONL RVF) (#656)
GitHub's /traffic/clones and /traffic/views endpoints only retain the
last 14 days server-side. Without periodic scraping, that data falls
off the cliff and is gone forever. This commit:
* Adds a scheduled GitHub Action (.github/workflows/clone-tracking.yml)
that runs on the 1st and 15th of every month (~14-day cadence) and
appends a snapshot to data/clone-data.rvf via the GitHub API.
* Seeds the file with today's first snapshot so the historical record
starts immediately rather than waiting for the next cron fire.
File format: ruvector JSONL RVF (schema "ruvector.rvf.jsonl/v1"). Each
line is one segment:
{type: "metadata", ...} — file header, written once on
first run
{type: "clone_snapshot", fetched_at,
window_count, window_uniques,
per_day: [{timestamp, count, uniques}, ...]}
— appended every run
{type: "view_snapshot", fetched_at,
window_count, window_uniques,
per_day: [{timestamp, count, uniques}, ...]}
— appended every run
Per-day entries are keyed by `timestamp`, so a downstream reader can
de-duplicate across overlapping snapshot windows (cron drift, manual
re-runs, etc.).
Today's seed:
clones (14d): 27,887 total / 6,611 uniques
views (14d): 162,314 total / 75,464 uniques
The workflow's commit message includes cumulative observed totals
("16 days observed → 30K clones, 28 days observed → 180K views"
style) so the git log itself doubles as a traffic timeline.
This is the long-term storage layer for the "downloads" badge work —
once we have a few months of snapshots, a small script can roll the
per-day entries into a real defensible number.
|
||
|
|
dc865c236e |
docs(readme): add 10M+ downloads badge (#655)
Adds a 'downloads 10M+' badge to the existing shields.io row, linking to the Edge Module Catalog section (where the cog binaries / HF weights / npm + crates packages are surfaced). Uses img.shields.io/badge/downloads-10M%2B-brightgreen.svg — static, no external counter API hit per page load. |
||
|
|
96bc4b4ede |
docs(readme): refresh capability table — positive voice, current state (#654)
The previous table mixed status badges (✅ / ⚠️ / 🔬) and verbose "pending wiring / not yet released" caveat columns. Rewrites it as "What / How / Speed-or-scale" — three columns, present tense, no status column. Captures what actually shipped this week: * Presence detection now points at the trained head shipped on HF (100% validation accuracy), with the phase-variance fallback reframed as a no-model option rather than a "loader pending" caveat. * 17-keypoint pose is its own row now — cog-pose-estimation v0.0.1 binaries on GCS, 8.4 ms cold-start on Pi 5, train-your-own in 2.1 s on RTX 5080. References ADR-101 + the benchmark log. * Multi-person counting drops the "Heuristic, not learned" framing. The adaptive P95 normalisation from PR #491 is in tree, the runtime dedup-factor knob is documented, and the six learned drop-in counters from the Cog catalog are linked: occupancy-zones, elevator-count, queue-length, customer-flow, clean-room, person-matching. * Edge intelligence row now points at the 105-cog catalog (ADR-102) instead of just the Cognitum Seed hardware. * Camera-supervised fine-tune row reflects the actual measured training time (2.1 s on RTX 5080 for 400 epochs) instead of the laptop estimate. * Drops the status-legend footer (no more ✅/⚠️/🔬 column to legend). Replaces it with a pointer down to the Edge Module Catalog. The ESP32 + Cognitum Seed deployment-options row gets the same treatment: cleaner list of what's included, no "Pose pending weights" parenthetical (the cog ships today). Net effect: same information, present tense, positive voice. Nothing removed beyond status badges + pending-work parentheticals; all genuine engineering details (e.g. "needs ~30 s ambient calibration" for the fallback) are preserved inline. |
||
|
|
feda871e02 |
docs(readme): drop the two Edge Intelligence collapsibles from the home page (#653)
Removes both: * 🧩 Edge Intelligence (ADR-041) — 60 WASM modules across 13 categories * 🧩 Edge Intelligence — All 65 Modules Implemented (ADR-041 complete) …and the 172 lines between them. The 60-module catalog narrative duplicated content already documented in: * The new 105-cog Edge Module Catalog collapsible (PR #648, ADR-102) — same purpose, sourced live from cognitum-apps/app-registry.json instead of hand-curated. * docs/edge-modules/* — per-category guides linked from the catalog. * ADR-041 itself. The home page now reads cleaner — one canonical "what modules exist" section (the live catalog) instead of three overlapping ones. |
||
|
|
43ac76a17f |
docs(readme): rewrite hero paragraph in plain language (#652)
The previous version listed every artifact format, every pending integration, and every not-yet-released model — useful as a status log but not as a what-this-system-does sentence for a first-time reader. Replaces it with a single paragraph that answers: - What does it do? (turn WiFi into a contactless sensor) - What hardware? ($9 ESP32) - What does it tell you? (who's there, breathing, heart rate) - How small is the model? (8 KB q4 fits anywhere) - What does it NOT need? (no cameras / wearables / phone apps) Everything that got removed — pending wiring, JSONL-vs-binary RVF, the 17-keypoint pose follow-up, the heuristic-fallback caveat — is already covered in dedicated sections later in the README (the Capability table, the Pretrained Model section, the Edge Module Catalog) and in #509 / ADR-079. The hero paragraph isn't the right place for the engineering caveat tour. |
||
|
|
6a2b2bdcbf |
fix(three.js): graceful banner when X Bot.fbx 404s on gh-pages (#651)
Demos 04 and 05 work fine locally — operator has assets/X Bot.fbx
present. On the gh-pages deploy the FBX is intentionally absent
(Mixamo license boundary, .gitignored) and the previous onError
handler just logged 'FBX load failed' to the console and left a
stuck '⚠ Load failed — see console' message in the overlay.
Replaces both onError handlers with an in-page card that:
- Explains why the asset is missing (license boundary, not a bug)
- Tells you exactly how to run it locally (Mixamo download path,
where to drop the file, the serve-demo.py command)
- Links to Mixamo + the repo source + back to the gallery
- Lets the ADR-097 helpers scene keep rendering behind it
- Logs at warn (not error) — no more uncaught console.error noise
The success branch is untouched, so local development is identical
to before.
|
||
|
|
d67d9872c1 |
feat(pages): deploy three.js demos to gh-pages/three.js/ (#649)
Adds a new GitHub Pages workflow that publishes the ADR-097 three.js demo gallery alongside the existing observatory/, pose-fusion/, pointcloud/, and nvsim/ deployments. Uses keep_files: true so the other deployments are preserved. What ships: * `examples/three.js/index.html` — new landing page that lists all 5 demos with screenshots, "standalone" vs "needs FBX" badges, and an honest note explaining the Mixamo X Bot.fbx license boundary (demos 04 and 05 need a local download from mixamo.com; demos 01-03 run standalone in any modern browser). * `.github/workflows/threejs-pages.yml` — staged copy of demos/, screenshots/, README.md, and the new index.html into `_site/three.js/`. Drops an `assets/README.txt` placeholder explaining the FBX-not-shipped policy. Triggered on changes to examples/three.js/** or the workflow itself. * README.md — adds the live link to the existing demo row (`▶ three.js Demos (5)`) plus a one-line callout describing the gallery and the FBX caveat. After this PR merges, the workflow runs and publishes: https://ruvnet.github.io/RuView/three.js/ |
||
|
|
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 |
||
|
|
dc7f6cd096 |
fix(provision): additive-by-default — close the #391 full-replace footgun (#647)
Closes #391 (full-replace footgun). Phase 1 of #574 (esp32-csi-node provisioning UX). The mDNS discovery + USB-CDC pairing work in #574 remains future work; this PR handles only the provision.py-side fix. Background: provision.py flashed a fresh NVS partition at 0x9000 every invocation. The previous behaviour built that partition only from the CLI flags passed on the current run — every key you didn't pass was silently erased. We hit it ourselves earlier today: --force-partial only suppressed the safety check but still wiped the SSID. This PR replaces the full-replace semantic with a per-port state file that captures every config value previously flashed from this machine. On each invocation: 1. Read ~/.config/wifi-densepose/esp32-provision-state/<port>.json (or %APPDATA%/... on Windows). 2. Overlay the new CLI flags on top — CLI wins where set. 3. Generate + flash NVS from the merged dict. 4. Persist the merged dict back to the state file. Net effect: the exact scenario from #391 + today's incident now passes (test_partial_invocation_does_not_drop_unrelated_keys): python provision.py --port COM7 --ssid Net --password p --target-ip 10.0.0.5 # later: python provision.py --port COM7 --seed-url http://10.0.0.99:8080 # WiFi creds preserved, seed_url added. New flags: --reset Wipe per-port state before merging (recycled-board path). --state-dir Override per-user state dir (XDG / %APPDATA% by default). --state Print the merged state and exit (debug / inspection). --force-partial preserved as a deprecation-flagged escape hatch. State file caveats (in the module docstring): per-machine, atomic write via .tmp + os.replace, future follow-up to add USB-CDC NVS dump for device-authoritative merging is tracked in #574. Tests: tests/test_provision_state.py — 11 tests covering load/save round-trip, corrupt-JSON resilience, CLI-wins-over-prior, the exact #391 case, falsy-but-not-None CLI override (node_id=0 must survive), and serial-port path sanitization for /dev/ttyUSB0. 11/11 pass. Live-tested end-to-end with --dry-run + --state inspection: first run: ssid + password + target_ip persisted second run: --seed-url added — WiFi creds intact in final state. |
||
|
|
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. |
||
|
|
9c3c8b98bc |
docs(adr): ADR-100 + ADR-101 — record v0.0.1 shipping status (#644)
Updates both ADRs to reflect that the first cog (`cog-pose-estimation@0.0.1`) landed today via PRs #642 + #643. ADR-100 (Cog Packaging Specification): * Status line: "first conforming cog shipped 2026-05-19". * Migration step 2 marked complete with PR references and the GCS paths the binaries live at. ADR-101 (Pose Estimation Cog): * Status line: "v0.0.1 shipped 2026-05-19". * New "v0.0.1 shipping status" section that walks through every ADR-100 acceptance gate with concrete pass/fail evidence (binary sizes, sha256 round-trip, signature, manifest path, live install on cognitum-v0, runtime contract, real-weights load assertion, ONNX parity). * Measured-metrics table: training time (2.1 s/400 epochs on RTX 5080), PCK@20/PCK@50/MPJPE, cold-start latency for Windows/ruvultra/Pi 5. * Carries forward the two open follow-ups: Hailo HEF (SDK-gated) and PCK@20 >= 35% (data-bound, #640). * "See also" link to docs/benchmarks/pose-estimation-cog.md. Docs-only; no code changes. |
||
|
|
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. |