发布

  • v1256 0bffe27288

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

    frostbyte_neo 发布于 2026-05-24 17:00:38 +00:00

    • 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 (fd0568caa), 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

    下载附件