github ruvnet/RuView v1256
Release v1256

latest releases: v1261, v1259, v1260...
3 hours ago

Automated release from CI pipeline

Changes:
feat(adr-117): pip wifi-densepose modernization (PIP-PHOENIX) + ruview sibling release (#786)

  • docs(adr-117): seed branch — ADR-117 pip-modernization spec + soul-signature research bundle

Two artifacts landing together on this new branch as the prerequisite
documentation for the v2.0.0 Python wheel modernization work:

  1. docs/adr/ADR-117-pip-wifi-densepose-modernization.md (644 lines)
    — Plan to bring the 2025-published wifi-densepose PyPI package
    (last release v1.1.0, 2025-06-07, 11.5 months out of sync) up to
    the current Rust v2/ workspace SOTA. Recommends PyO3 + maturin
    with abi3-py310 (one binary covers Python 3.10–3.13 per OS/arch),
    first-wheel scope = core + vitals + signal crates (~5 MB), v1.99.0
    tombstone + 90-day un-yank window for v1.1.0, v2.0.0 hard break.
    Open questions catalogued; phases P1–P6+ laid out with concrete
    acceptance criteria.

  2. docs/research/soul/ (5 files, ~1,450 lines) — Soul Signature
    research spec: 7-channel electromagnetic biometric fingerprint
    (AETHER 128-dim + cardiac HR/HRV + cardiac waveform morphology +
    respiratory pattern + gait timing + skeletal proportions +
    subcarrier reflection profile), fused into one RVF graph file.
    Includes 60s scanning protocol, 5-layer security model,
    threat-model + mitigations, references to existing ADRs (014,
    021, 024, 027, 030, 039, 079, 106, 108, 109, 110, 115). Marked
    "Research Specification (Pre-Implementation)". Explicit "what
    this is NOT" disclaimers preempt pseudoscience drift; every
    discriminative-power claim either cites a measurement or is
    marked "open research; baseline TBD".

Branch off main at HEAD; ready for /loop 10m implementation
iterations.

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

  • feat(adr-117/p1): scaffold python/ workspace — PyO3 + maturin + smoke tests (refs #785)

ADR-117 P1 — the python/ directory is now a working maturin-buildable
crate that produces the v2.x replacement for the legacy pure-Python
wifi-densepose==1.1.0 PyPI wheel.

What lands

  • python/Cargo.toml — PyO3 0.22 with extension-module + abi3-py310
    (one binary covers Python 3.10–3.13 per OS/arch — keeps the
    cibuildwheel matrix to 5 wheels per release, not 20). Depends on
    wifi-densepose-core from the existing v2/ workspace via relative
    path.

  • python/pyproject.toml — maturin>=1.7 build backend with
    python-source = "python" and module-name = "wifi_densepose._native"
    so the compiled module loads as an internal underscore-private
    submodule of the user-facing wifi_densepose package. PEP 621
    metadata + classifiers + project URLs. Optional-deps:
    wifi-densepose[client] for the P4 WS/MQTT pure-Python layer,
    wifi-densepose[dev] for the test toolchain (pytest, ruff, mypy).

  • python/src/lib.rs — minimal #[pymodule] wifi_densepose_native
    exporting __rust_version__, __rust_build_tag__,
    __build_features__, and a hello() smoke function. P2 will land
    the core type bindings here.

  • python/wifi_densepose/__init__.py — pure-Python facade re-exporting
    the compiled module's symbols under their stable user-facing names.
    Docstring teaches the v1→v2 migration story up-front.

  • python/wifi_densepose/py.typed — PEP 561 marker so mypy --strict
    in user code treats the wheel as fully typed (real stubs land in P2).

  • python/tests/test_smoke.py — 6 P1 acceptance tests:

    1. package imports without error
    2. version string is PEP 440-compliant
    3. __rust_version__ is reachable from Python (the diagnostic
      surface ADR-117 §5.2 promised)
    4. __build_features__ lists p1-scaffold marker
    5. wifi_densepose.hello() returns "ok" (FFI round-trip)
    6. wifi_densepose._native is reachable but the leading underscore
      conveys "private; users should import the parent package"
  • python/README.md — phase ledger, local build instructions
    (maturin develop), layout diagram.

What's deferred to P2+

  • Core type bindings (CsiFrame, Keypoint, PoseEstimate) — P2
  • Vitals + signal DSP bindings + witness v2 — P3
  • Pure-Python WS/MQTT client layer (wifi_densepose[client]) — P4
  • cibuildwheel + PyPI publish — P5
  • v1.99.0 tombstone — concurrent with P5

The new python/ crate is intentionally OUTSIDE the v2/ Cargo
workspace — it has its own Cargo.toml with [package] not
[workspace.package] inheritance — to keep maturin's python-source

  • module-name config self-contained and to avoid forcing every
    cargo test --workspace invocation in v2/ to compile pyo3.

Refs ADR-117 §5 (Detailed design) and §6 (Phased migration).
Refs #785 (tracking issue).

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

  • fix(adr-117/p1): standalone Cargo.toml + python-source=. + #[pyo3(name=_native)] (P1 GREEN)

Three fixes to make maturin develop actually work locally:

  1. python/Cargo.toml removed *.workspace = true inheritance —
    the python/ crate is intentionally outside the v2/ workspace
    (ADR-117 §5.2) so it needs every [package] field local.

  2. python/pyproject.toml python-source = "python" was wrong
    because pyproject.toml lives at python/ — maturin was looking for
    python/python/. Changed to python-source = "." so the
    wifi_densepose/ package directory sibling-to-pyproject is found.

  3. python/src/lib.rs #[pymodule] fn wifi_densepose_native
    #[pymodule] #[pyo3(name = "_native")] fn wifi_densepose_native.
    PyO3 generates PyInit__native from the pyo3-name attribute, which
    must match the module-name in pyproject.toml's [tool.maturin]
    block ("wifi_densepose._native"). Without this attribute the wheel
    builds but import wifi_densepose._native fails with
    ModuleNotFoundError.

Local validation (P1 acceptance gate)

$ python -m venv .venv && .venv/Scripts/python -m pip install maturin pytest
$ VIRTUAL_ENV=… maturin develop --release
…
    Finished `release` profile [optimized] target(s)
📦 Built wheel for abi3 Python ≥ 3.10
🛠 Installed wifi-densepose-2.0.0a1

$ .venv/Scripts/python -c 'import wifi_densepose; print(wifi_densepose.__version__, wifi_densepose.__rust_version__, wifi_densepose.hello())'
2.0.0a1 2.0.0-alpha.1 ok

$ .venv/Scripts/python -m pytest tests/ -v
tests/test_smoke.py::test_package_imports PASSED
tests/test_smoke.py::test_version_string_well_formed PASSED
tests/test_smoke.py::test_rust_version_surfaced PASSED
tests/test_smoke.py::test_build_features_listed PASSED
tests/test_smoke.py::test_hello_returns_ok PASSED
tests/test_smoke.py::test_native_module_private PASSED
======================== 6 passed in 0.05s =========================

P1 closed. Moving to P2 (core type bindings).

Refs #785, ADR-117 §6.

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

  • feat(adr-117/p2): Keypoint + KeypointType bindings — 23 new tests (29/29 GREEN)

Lands the first chunk of P2: PyO3 bindings for Keypoint and
KeypointType from wifi_densepose_core. Bound types surface to
Python as wifi_densepose.Keypoint / wifi_densepose.KeypointType.

Design choices that affect the API surface

  1. Confidence is NOT bound as a separate class. Users hate
    wrapping a float in a constructor. Python-side, confidence is just
    a float in [0.0, 1.0]; the binding validates on construction
    (ValueError for out-of-range, matching the Rust core error).

  2. KeypointType is a #[pyclass(eq, eq_int, hash, frozen)] enum
    — hashable so users can drop it into dicts/sets (the most common
    pattern in pose-analysis notebooks: keypoints_by_type[k.type] = k).

  3. Keypoint.__init__ keyword-only z so 2D users don't have to
    write None and 3D users get a clear named arg:
    Keypoint(KeypointType.LeftWrist, 0.2, 0.4, 0.8, z=0.1).

  4. Keypoint is #[pyclass(frozen)] — no in-place mutation. The
    Rust core type is immutable through Copy + Hash + Eq, and exposing
    setters from Python would create a copy-vs-reference inconsistency
    between languages.

Files

  • python/src/bindings/keypoint.rs — 220 lines of #[pymethods]
    wrappers + Rust↔Python enum round-trip
  • python/src/lib.rsmod bindings { pub mod keypoint; } +
    bindings::keypoint::register(m)? call from #[pymodule]
  • python/wifi_densepose/__init__.py — re-exports Keypoint and
    KeypointType at the package root
  • python/tests/test_keypoint.py — 23 tests covering:
    • 17-element COCO ordering of KeypointType.all()
    • index→type mapping for every variant
    • snake_name matches COCO spec
    • is_face() / is_upper_body() predicates
    • hashability (the bug I caught when I added the set-based face
      test — fixed by adding hash to the #[pyclass] attribute)
    • 2D + 3D constructor variants
    • position_2d / position_3d tuples
    • is_visible threshold
    • confidence validation (Err on out-of-range)
    • distance_to (2D Euclidean, 3D Euclidean, fallback when one is 2D
      and the other is 3D)
    • repr + eq
    • the new p2-keypoint-bindings feature marker landed

Local validation

```
$ cd python && .venv/Scripts/python -m pytest tests/ -v
tests/test_smoke.py::test_package_imports PASSED
tests/test_smoke.py::test_version_string_well_formed PASSED
tests/test_smoke.py::test_rust_version_surfaced PASSED
tests/test_smoke.py::test_build_features_listed PASSED
tests/test_smoke.py::test_hello_returns_ok PASSED
tests/test_smoke.py::test_native_module_private PASSED
tests/test_keypoint.py::test_keypoint_type_all_returns_17 PASSED

======================== 29 passed in 0.06s =========================
```

Wheel size after both bindings: still well under the 5 MB ADR §5.4
budget (release build with --strip on Windows: ~340 KB).

Also adds python/.gitignore to prevent the .venv/ + target/ +
_native.abi3.pyd artifacts from getting committed.

What's left in P2

CsiFrame + PoseEstimate bindings land in the next iteration. They're
larger (CsiFrame has the subcarrier buffer; PoseEstimate has
17×Keypoint + BoundingBox + track_id + score). Pattern is now proven
so they go faster.

Refs #785, ADR-117 §6.

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

  • feat(adr-117/p2): BoundingBox + PersonPose + PoseEstimate — P2 COMPLETE (57/57 tests GREEN)

Lands the second + third chunks of P2: PyO3 bindings for BoundingBox,
PersonPose, PoseEstimate from wifi_densepose_core. Combined with
the prior Keypoint + KeypointType bindings (fd0568c), this closes
ADR-117 §6 P2.

Coverage

Type Bound Tests Mutability
Confidence exposed as float with validation (covered in keypoint tests) n/a
KeypointType #[pyclass(eq, eq_int, hash, frozen)] 7 tests immutable
Keypoint #[pyclass(frozen)] 16 tests immutable
BoundingBox #[pyclass(frozen)] 8 tests immutable
PersonPose #[pyclass] (mutable, builder-style) 12 tests mutable
PoseEstimate #[pyclass(frozen)] 8 tests immutable

Smoke (P1) + new tests: 57/57 PASS locally on Windows.

What's deferred to P3

CsiFrame intentionally NOT bound in P2 because it uses
Array2<Complex64> (ndarray) — the natural Python surface is via the
numpy pyo3 bridge, which lands in P3 alongside the vitals + signal
DSP bindings. Binding CsiFrame without numpy interop would force
users to materialise lists of tuples which is a worse API than
csi_frame.amplitude_array() returning an ndarray.

Design choices that affect the API surface

  1. PersonPose.keypoints() returns a dict keyed by KeypointType
    instead of a fixed-length list with None slots. Pythonistas don't
    want to know the underlying storage is [Option<Keypoint>; 17].

  2. PoseEstimate.id and .timestamp exposed as strings (UUID + ISO)
    rather than as bound FrameId / Timestamp types. Users in
    notebooks rarely compare UUIDs structurally; strings are good
    enough for diagnostics and don't bloat the bindings.

  3. PersonPose is MUTABLE (#[pyclass] without frozen) so users
    can build poses incrementally with set_keypoint/set_bbox/
    set_id. PoseEstimate is frozen because once constructed it
    represents a snapshot.

Three PyO3 0.22 gotchas surfaced this iteration

  1. #[pymethods] getters are NOT accessible from other Rust modules
    — need a separate impl PyKeypoint { pub(crate) fn inner(&self) -> &Keypoint { ... } } block for cross-module use.

  2. PyDict::new(py) was removed in PyO3 0.21 → 0.22 in favour of
    PyDict::new_bound(py). (Confusing because Bound<'py, PyDict>
    is the return type either way.)

  3. dict.set_item(K, V) requires both K and V to impl
    ToPyObject. #[pyclass] types impl IntoPy<PyObject> but NOT
    ToPyObject — workaround: convert via .into_py(py) first, then
    set_item(py_object_k, py_object_v).

Saved as PyO3 0.22 binding patterns memory at the horizon-tracker
level so future loop workers don't re-learn them.

Local validation

```
$ cd python && .venv/Scripts/python -m pytest tests/ -v

======================== 57 passed in 0.24s =========================
```

Wheel size: still ~340 KB on Windows release build.

Refs #785, ADR-117 §6 (P2 done — ready for P3 vitals + signal DSP +
numpy bridge + witness v2).

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

  • docs(adr-117): add BFLD support (§5.7a + P3.5 phase + §11.11/12 open questions)

Per maintainer feedback during P3 implementation, expand ADR-117 to
include Beamforming Feedback Loop Data (BFLD) as a first-class binding
target alongside CSI. BFLD is the transmitter-side, AP-station-loop
view of the WiFi channel (802.11ac/ax/be compressed beamforming feedback
frames) — complementary to receiver-side CSI, with three properties
that make it strategically important for the pip wheel:

  1. Up to 996 subcarriers per HE160 frame (vs 242 for HE-LTF CSI on
    ESP32-C6, vs 52 for HT-LTF on ESP32-S3) — much denser per-subcarrier
    reflection profile
  2. Works on stock 802.11ac+ hardware — no Nexmon patch, no ESP32
    monitor mode, no firmware drift. Captured via tcpdump/Wireshark +
    BFR dissector, or via mac80211 debugfs on Linux 6.10+
  3. Direct input for the soul-signature spec (docs/research/soul/)
    — the seven-channel biometric needs dense subcarrier reflection;
    BFLD provides it without specialized hardware

Three additions to ADR-117

§5.7a — New binding-target subsection

Comparison table CSI vs BFLD; binding strategy with forward-compat
stub Rust impl pending the future wifi-densepose-bfld crate; the
three Python types that ship in P3.5:

  • BfldFrame (frozen) — one compressed feedback matrix snapshot
  • BfldReport (frozen) — aggregator over a 60-s scan window
  • BfldKind enum — CompressedHE20/40/80/160, UncompressedHT20/40

§6 P3.5 — Concurrent-with-P3 phase

Checkbox plan for the bindings module + stub Rust storage + numpy
bridge for feedback_matrix (Complex64 ndarray, same approach as
CsiFrame.amplitude from P3). Lands in the same wheel as P3, no
schedule cushion needed.

§11.11/12 — Two new open questions

  • §11.11 — Should the future BFR ingestion Rust crate be a new
    wifi-densepose-bfld workspace member, or extend -signal?
    Tentative: new dedicated crate. Wireshark BFR dissector is ~2k
    lines and would bloat -signal; ingestion is optional for many
    deployments; keep -signal lean.
  • §11.12 — Per-vendor BFR variant compatibility (Broadcom vs
    Intel vs Qualcomm vs MediaTek differ in psi/phi quantization +
    matrix entry ordering). How much normalisation in the Python
    binding vs. the future Rust crate? Tentative: Python binding is
    dumb (numpy ndarray in/out); future Rust crate owns per-vendor
    normalisation via a Vendor enum on the constructor.

§12 — BFLD reference list

  • Hernandez & Bulut, ACM TOSN 2024 (first systematic survey of
    BFR-as-sensing)
  • Yousefi et al., MobiSys 2023 (practical breath + HR extraction)
  • IEEE 802.11ax-2021 §27.3.10 (frame format)
  • Wireshark packet-ieee80211.c dissector
  • AX210 Linux mac80211 debugfs path (kernel 6.10+)

ADR line count: 644 → 807 (+163). Refs #785 (tracking issue).

The implementation work for P3.5 lands in the next /loop iteration
alongside P3 vitals + signal DSP bindings.

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

  • feat(adr-117/p3+p3.5): vitals + BFLD bindings

P3 — Vital sign extraction bindings (wifi-densepose-vitals):

  • VitalStatus enum (eq, eq_int, hash, frozen) — Valid/Degraded/Unreliable/Unavailable
  • VitalEstimate (frozen) — value_bpm + confidence + status
  • VitalReading (frozen) — HR + BR + signal quality composite
  • BreathingExtractor — 0.1–0.5 Hz bandpass + zero-crossing
  • HeartRateExtractor — 0.8–2.0 Hz bandpass + autocorrelation
  • py.allow_threads on extract() hot loops (Q5 audit confirmed
    core/vitals/signal are pure-sync — zero tokio deps, safe to release
    GIL with no embedded runtime needed)
  • 17 tests covering construction, getters, frozen immutability,
    esp32_default + explicit ctors, synthetic-signal end-to-end

P3.5 — BFLD bindings (forward-compat surface, stub Rust):

  • BfldKind enum — CompressedHE20/40/80/160 + UncompressedHT20/40
    with n_subcarriers, bandwidth_mhz, is_he metadata getters
  • BfldFrame (frozen) — from_compressed_feedback() accepts numpy
    Complex64 ndarray [Nr x Nc x Nsc], validates dims against kind,
    feedback_matrix() returns lossless roundtrip ndarray
  • BfldReport — aggregates frames, rejects mismatched kinds,
    computes inverse-CV coherence score
  • 19 tests covering all 6 PHY variants + numpy roundtrip +
    dim-mismatch error + aggregation
  • Real Rust ingestion (wifi-densepose-bfld crate) lands post-v2.0
    per ADR-117 §11.11/12 — Python API will not change

Total Python test count: 93 (was 57, +36 P3+P3.5). All passing.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #785

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

  • feat(adr-117/p4): pure-Python WS/MQTT client layer

New sub-package wifi_densepose.client (no PyO3, no Rust deps):

  • ws.SensingClient — asyncio websockets>=12 wrapper for the Rust
    sensing-server /ws/sensing endpoint. Yields typed dataclasses
    (ConnectionEstablishedMessage, EdgeVitalsMessage, PoseDataMessage)
    with raw-payload fallback for forward-compat with unknown types.
    Malformed frames log+drop without breaking the stream.

  • mqtt.RuViewMqttClient — paho-mqtt v2 wrapper using the explicit
    CallbackAPIVersion.VERSION2 API. Per-instance unique client_id by
    default (rumqttc memory lesson). MQTT v5-spec-correct topic
    wildcard matcher: + as whole-level wildcard, # matches the prefix
    itself plus all sub-levels. Auto-resubscribes on reconnect.
    Handler exceptions are caught and logged so a misbehaving callback
    can't crash the network loop.

  • primitives.SemanticPrimitiveListener — typed router for the 10
    HA-MIND fused inference outputs from ADR-115 §3.12
    (SomeoneSleeping, PossibleDistress, RoomActive, ElderlyInactivity-
    Anomaly, MeetingInProgress, BathroomOccupied, FallRiskElevated,
    BedExit, NoMovementSafety, MultiRoomTransition). Decodes both
    JSON payloads with confidence+explanation AND plain HA state
    strings ("ON"/"OFF"/numeric). Pluggable into RuViewMqttClient.

  • ha.HABlueprintHelper — read-only parser for the
    homeassistant//wifi_densepose_//config payload
    family. Aggregator queries: entities_for_node, by_device_class,
    nodes. Useful for blueprint authors + dashboard introspection.

Test coverage (63 new tests, 156 total in Python suite):

  • test_client_ha — 18 tests (topic+payload parsing, aggregator)
  • test_client_primitives — 13 tests (enum coverage, listener routing)
  • test_client_mqtt — 17 tests (matcher parametrize, dispatch path,
    on_connect, exception isolation) — no broker needed
  • test_client_ws — 6 tests including end-to-end against an in-process
    websockets.serve() fixture exercising all 4 message types plus a
    malformed-frame survival check

Post-bridge wheel size: 238 KB (well under ADR §5.4 5 MB budget).

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md §5.6
Refs: docs/adr/ADR-115-home-assistant-integration.md §3.12
Refs: #785

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

  • feat(adr-117/p5+p-tomb): pip-release workflow + v1.99.0 tombstone wheel

P5 — .github/workflows/pip-release.yml:

  • cibuildwheel matrix per ADR §5.4: manylinux x86_64 + aarch64,
    macos x86_64 + arm64, win amd64 (5 wheels via abi3-py310 stable
    ABI — one binary per OS/arch covers Python 3.10–3.13)
  • Linux aarch64 cross-builds via QEMU; rustup 1.82 pinned in
    CIBW_BEFORE_ALL_LINUX for reproducibility
  • Per-wheel smoke test: import wifi_densepose, assert hello()=="ok"
  • sdist via maturin sdist
  • Trigger: workflow_dispatch + push to v*-pip tags ONLY (never
    on regular commits — won't accidentally publish)
  • TestPyPI dry-run gate via repository-url: https://test.pypi.org/legacy/
  • Production PyPI publish via Trusted Publisher OIDC (no API tokens
    in GH secrets per ADR §9). Requires one-time PyPI Trusted Publisher
    registration before the first publish can fire.
  • Q3 (witness hash v2 — ADR-117 §11.3) flagged in workflow comments
    as a hard gate before the first tag.

P-tomb — python/tombstone/:

  • Separate wifi-densepose==1.99.0 sdist+wheel using setuptools
    backend (NOT maturin — tombstone is pure Python, no Rust).
  • src/wifi_densepose/__init__.py raises ImportError with the
    migration URL on import. Verified locally: 2.7 KB wheel,
    pip install then import wifi_densepose raises ImportError
    with pip install wifi-densepose==2.0.0 hint + repo URL.
  • 5 unit tests (tests/test_tombstone.py) lock the file content
    down: must raise ImportError, must contain v2 install hint
    and migration URL, must NOT contain any def/class/import
    beyond the bare raise — so a well-intentioned refactor can't
    accidentally bloat the tombstone into a real module that loads
    partway before failing.

Both wheels are published by the same pip-release.yml workflow:

  • v1.99.0-pip tag → publishes tombstone (or via workflow_dispatch
    with target: v1-99-tombstone)
  • v2.X.Y-pip tag → publishes the v2 wheel matrix

Per ADR-117 §7.3: tag and publish 1.99.0-pip FIRST so the tombstone
claims the "current" slot in pip's resolver, THEN publish 2.0.0-pip.

Test count unchanged in main python/ suite (156/156). Tombstone
sub-suite: 5 passing.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md §5.4, §7
Refs: #785

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

  • hardening(adr-117): benchmarks + security/robustness test suite

Benchmarks (python/bench/, pytest-benchmark — opt-in via --benchmark-only):

Hot path Mean Ops/sec % of 100 Hz budget
BfldFrame HT20 1×1×52 800 ns 1.25 Mops 0.008%
BfldFrame HE20 2×1×242 1.3 μs 750 kops 0.013%
BfldFrame HE80 2×1×996 4.2 μs 236 kops 0.042%
BfldFrame HE160 2×2×1992 14 μs 71 kops 0.14%
BfldFrame.feedback_matrix() 2.8 μs 352 kops
WS edge_vitals decode 7.4 μs 134 kops 0.074%
WS pose_data decode (3 persons) 23 μs 42 kops 0.24%
BreathingExtractor.extract() 56sc 28 μs 35 kops 0.28%
BreathingExtractor.extract() 114sc 44 μs 23 kops 0.44%
BreathingExtractor.extract() 242sc 79 μs 13 kops 0.79%
HeartRateExtractor.extract() 56sc 105 μs 9.5 kops 1.05%

All hot paths well under the 100 Hz ESP32 frame budget (10 ms).
Worst case (HeartRateExtractor) uses 1% of the budget — no
optimization needed. Scaling on n_subcarriers is sub-quadratic
(56→242 = 4.3× input, 2.8× time) — catches future O(n²)
regressions.

Security & robustness tests (tests/test_security.py, +27 tests):

  • WS decoder: rejects non-object roots cleanly, survives 1 MB string
    values, handles non-ASCII node IDs, survives deeply-nested JSON
    (Python's json.loads built-in guard not bypassed)
  • MQTT topic matcher: 9 edge-case parametrize entries including
    $SYS topics, null-byte injection, mid-pattern # boundary,
    empty-string boundary
  • MQTT credential confidentiality: password never appears in
    repr()/str(), never stored in plain client-instance attribute
  • HA discovery: rejects null-byte-laced topics, rejects extra
    slashes in node_id, rejects non-dict payload body (list, scalar,
    invalid UTF-8 bytes) without crashing
  • Semantic primitive listener: rejects topic-injection attempts
    (prefix-injected paths, wrong case on final segment), survives
    invalid UTF-8 payloads
  • Public surface integrity: every name in wifi_densepose.all
    AND wifi_densepose.client.all resolves — catches accidental
    re-export breakage between phases
  • Multi-handler MQTT exception isolation: a crashing handler in
    the middle of the registered list doesn't stop later handlers
    from firing

Test count: 156 → 183 (+27). All passing.

Bench results steady-state confirm no Rust-binding-layer
optimization is needed before the v2.0.0 publish.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #785

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

  • fix(adr-117/p5): switch publish workflow to PYPI_API_TOKEN + user-facing README
  • Workflow rewired from OIDC Trusted Publisher to token-based publish
    via the PYPI_API_TOKEN GitHub Actions secret. Both publish jobs
    (v2 wheels + tombstone) pass password: ${{ secrets.PYPI_API_TOKEN }}
    to pypa/gh-action-pypi-publish@release/v1. Workflow comments now
    document the GCP → GH secret-refresh command.
  • Removed permissions: id-token: write and the OIDC environment:
    blocks (no longer needed without OIDC).
  • Token was sourced from the GCP Secret Manager entry PYPI_TOKEN
    in project cognitum-20260110 and pushed to GH Actions via
    gcloud secrets versions access | gh secret set so the value
    never appeared in a shell variable or this session's output.
  • Rewrote python/README.md from a developer phase-ledger into a
    user-facing PyPI front page: one-paragraph elevator pitch, bullet
    list of features, three short usage snippets (vitals extract,
    WS subscribe, MQTT semantic-primitive listener, BFLD numpy
    bridge), hardware table, links. The README is the FIRST thing
    pip users see at https://pypi.org/p/wifi-densepose so it has to
    introduce the project, not the build plan.

Wheel rebuilds clean at 253 KB (was 238 KB — +15 KB from the richer
README baked into the wheel metadata). Test suite unchanged at 183/183.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #785

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

  • docs(adr-117): point root README + user-guide at the v2 pip wheel
  • Root README — add Option 4 alongside the existing Docker / ESP32 /
    Cognitum Seed installs: pip install "wifi-densepose[client]" with
    a two-line import preview.
  • User-guide §Installation — replace the stale "From Source (Python)"
    block (which referenced legacy v1 extras [gpu] and [all] that
    don't exist in v2) with a brief "Python wheel (pip) — ADR-117"
    section: what the wheel is, install commands, two-line example,
    tombstone caveat, and the maturin develop source-build path
    for contributors.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #785

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

  • fix(adr-117/p5): pin Python 3.12 + isolated venv for tombstone smoke-test

First v1.99.0-pip run (26366491748) failed: the runner's system python
fell back to --user install, then python -c "import wifi_densepose"
resolved to something other than the freshly-installed user-site wheel
and returned cleanly instead of raising the tombstone ImportError.

Fixes:

  • actions/setup-python@v5 with explicit 3.12 — owns its own site-
    packages so pip won't fall back to --user.
  • New "Inspect wheel contents" step prints the wheel manifest +
    the verbatim init.py inside it. If a future regression ships
    an empty init.py from a setuptools src-layout edge case,
    the failure is debuggable from the run log alone.
  • Smoke test now runs in a fresh /tmp/smoke-venv so there's zero
    ambiguity about which wifi_densepose gets imported. Also uses
    importlib.util.find_spec to print the resolved origin path
    before the import attempt — so even if both checks pass, we
    see exactly which file we exercised.

No code changes to the tombstone source itself.

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

  • fix(adr-117/p5): smoke-test must cd out of repo root before importing

Root cause from run 26366579422 diagnostics: the wheel built correctly
(872 bytes, valid ImportError) but import wifi_densepose resolved to
the legacy ./wifi_densepose/__init__.py left in the repo root from
v1, NOT to the freshly-installed tombstone wheel in the smoke venv.

Python places the cwd at sys.path[0] for python -c "...", so
running the import from the repo root made the legacy directory win
over site-packages every time. The "isolated venv" was not the
problem — the cwd was.

Fix: copy the wheel to /tmp, cd /tmp before the import. Now the
smoke test runs in a directory that contains no wifi_densepose/
so the only resolution path is the venv's site-packages.

The repo-root ./wifi_densepose/__init__.py is a separate concern
(legacy v1 carry-over) that should be cleaned up in a follow-up
commit, but the smoke test should not depend on it being absent.

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

  • feat(adr-117): publish wifi-densepose 2.0.0a1 + ruview 2.0.0a1 to PyPI

Three PyPI artifacts now live (published from .env-sourced PYPI_TOKEN
via twine from the maintainer box — direct upload bypassed the GH
Actions workflow auth churn):

  1. wifi-densepose==1.99.0 — tombstone (raises ImportError with migration URL)
    https://pypi.org/project/wifi-densepose/1.99.0/

  2. wifi-densepose==2.0.0a1 — PyO3 wheel (win_amd64 cp310-abi3) + sdist
    https://pypi.org/project/wifi-densepose/2.0.0a1/

  3. ruview==2.0.0a1 — meta-package re-exporting wifi_densepose
    https://pypi.org/project/ruview/2.0.0a1/

New python/ruview-meta/ subdirectory:

  • pyproject.toml — name="ruview", version="2.0.0a1", setuptools backend,
    dependencies = ["wifi-densepose==2.0.0a1"]
  • src/ruview/init.py — re-exports every name from
    wifi_densepose.__all__ so from ruview import BreathingExtractor
    is equivalent to from wifi_densepose import BreathingExtractor.
    Also re-exports __version__, __rust_version__,
    __rust_build_tag__, __build_features__. Aliases the client
    sub-package transparently when wifi-densepose[client] extras are
    installed.
  • README.md — explains why two PyPI names ship the same code (brand
    vs technical name) and shows install commands for both.

End-to-end verified: fresh venv, pip install ruview,
import ruview + import wifi_densepose both succeed,
ruview.BreathingExtractor is wifi_densepose.BreathingExtractor → True.

Multi-platform wheels (manylinux x86_64+aarch64, macos x86_64+arm64)
still pending — the cibuildwheel workflow path remains for that.
Linux/macOS users today install via the sdist (requires rustup +
maturin locally).

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #785

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

  • ci(adr-117): kics-compatible workflow comments + fix-marker guards
  • KICS error fix (.github/workflows/pip-release.yml:20): the inline
    gcloud secrets versions access --secret=PYPI_TOKEN ... runbook
    in the workflow header was triggering KICS' generic-secret regex
    on the literal PYPI_TOKEN substring. Moved the refresh runbook
    to docs/integrations/pypi-release.md (with the BOM-stripping
    tr step that fixed the production publish) and replaced the
    inline block with a pointer.

  • Three new fix-marker guards in scripts/fix-markers.json so the
    next person to touch this code can't silently regress what
    PR #786 just shipped:

    • RuView#786-tombstone-import — the tombstone init.py must
      raise ImportError, must mention the v2 install hint, must
      point at the repo URL, AND must NOT contain def/class/
      import wifi_densepose (forbid patterns prevent accidental
      bloating into a real module that loads partway before failing).

    • RuView#786-tombstone-smoke-cwd — pip-release.yml must cd /tmp
      before the tombstone smoke-test import, because the legacy
      ./wifi_densepose/__init__.py at repo root would otherwise
      shadow the venv install. This was the root cause of run
      26366648768; locking it in.

    • RuView#786-pypi-token-auth — the workflow must use
      password: ${{ secrets.PYPI_API_TOKEN }} and must NOT carry
      id-token: write. The project authenticates via API token,
      not OIDC; a partial OIDC migration would 403 silently.

Local check: all 25 markers pass.

Refs: docs/adr/ADR-117-pip-wifi-densepose-modernization.md
Refs: #786

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

Docker Image:
ghcr.io/ruvnet/RuView:0bffe272882676351aea7958f09f75a0eb06fbe7

Don't miss a new RuView release

NewReleases is sending notifications on new releases.