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:
-
docs/adr/ADR-117-pip-wifi-densepose-modernization.md (644 lines)
— Plan to bring the 2025-publishedwifi-denseposePyPI 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. -
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 withextension-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-corefrom the existing v2/ workspace via relative
path. -
python/pyproject.toml— maturin>=1.7 build backend with
python-source = "python"andmodule-name = "wifi_densepose._native"
so the compiled module loads as an internal underscore-private
submodule of the user-facingwifi_denseposepackage. 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 ahello()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 somypy --strict
in user code treats the wheel as fully typed (real stubs land in P2). -
python/tests/test_smoke.py— 6 P1 acceptance tests:- package imports without error
- version string is PEP 440-compliant
__rust_version__is reachable from Python (the diagnostic
surface ADR-117 §5.2 promised)__build_features__listsp1-scaffoldmarkerwifi_densepose.hello()returns "ok" (FFI round-trip)wifi_densepose._nativeis 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-nameconfig self-contained and to avoid forcing every
cargo test --workspaceinvocation 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:
-
python/Cargo.tomlremoved*.workspace = trueinheritance —
the python/ crate is intentionally outside the v2/ workspace
(ADR-117 §5.2) so it needs every[package]field local. -
python/pyproject.tomlpython-source = "python"was wrong
because pyproject.toml lives at python/ — maturin was looking for
python/python/. Changed topython-source = "."so the
wifi_densepose/package directory sibling-to-pyproject is found. -
python/src/lib.rs#[pymodule] fn wifi_densepose_native→
#[pymodule] #[pyo3(name = "_native")] fn wifi_densepose_native.
PyO3 generatesPyInit__nativefrom the pyo3-name attribute, which
must match themodule-namein pyproject.toml's [tool.maturin]
block ("wifi_densepose._native"). Without this attribute the wheel
builds butimport wifi_densepose._nativefails 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
-
Confidenceis NOT bound as a separate class. Users hate
wrapping a float in a constructor. Python-side, confidence is just
afloat in [0.0, 1.0]; the binding validates on construction
(ValueErrorfor out-of-range, matching the Rust core error). -
KeypointTypeis 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). -
Keypoint.__init__keyword-onlyzso 2D users don't have to
writeNoneand 3D users get a clear named arg:
Keypoint(KeypointType.LeftWrist, 0.2, 0.4, 0.8, z=0.1). -
Keypointis#[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-trippython/src/lib.rs—mod bindings { pub mod keypoint; }+
bindings::keypoint::register(m)?call from#[pymodule]python/wifi_densepose/__init__.py— re-exportsKeypointand
KeypointTypeat the package rootpython/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 addinghashto 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-bindingsfeature marker landed
- 17-element COCO ordering of
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
-
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]. -
PoseEstimate.id and .timestamp exposed as strings (UUID + ISO)
rather than as boundFrameId/Timestamptypes. Users in
notebooks rarely compare UUIDs structurally; strings are good
enough for diagnostics and don't bloat the bindings. -
PersonPose is MUTABLE (
#[pyclass]withoutfrozen) so users
can build poses incrementally withset_keypoint/set_bbox/
set_id. PoseEstimate isfrozenbecause once constructed it
represents a snapshot.
Three PyO3 0.22 gotchas surfaced this iteration
-
#[pymethods]getters are NOT accessible from other Rust modules
— need a separateimpl PyKeypoint { pub(crate) fn inner(&self) -> &Keypoint { ... } }block for cross-module use. -
PyDict::new(py)was removed in PyO3 0.21 → 0.22 in favour of
PyDict::new_bound(py). (Confusing becauseBound<'py, PyDict>
is the return type either way.) -
dict.set_item(K, V)requires both K and V to impl
ToPyObject.#[pyclass]types implIntoPy<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:
- 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 - Works on stock 802.11ac+ hardware — no Nexmon patch, no ESP32
monitor mode, no firmware drift. Captured via tcpdump/Wireshark +
BFR dissector, or viamac80211debugfs on Linux 6.10+ - 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 snapshotBfldReport(frozen) — aggregator over a 60-s scan windowBfldKindenum —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-bfldworkspace 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-signallean. - §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 aVendorenum 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.cdissector - 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*-piptags 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.0sdist+wheel using setuptools
backend (NOT maturin — tombstone is pure Python, no Rust). src/wifi_densepose/__init__.pyraises ImportError with the
migration URL on import. Verified locally: 2.7 KB wheel,
pip installthenimport wifi_denseposeraises ImportError
withpip install wifi-densepose==2.0.0hint + repo URL.- 5 unit tests (
tests/test_tombstone.py) lock the file content
down: mustraise ImportError, must contain v2 install hint
and migration URL, must NOT contain anydef/class/import
beyond the bareraise— 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-piptag → publishes tombstone (or via workflow_dispatch
withtarget: v1-99-tombstone)v2.X.Y-piptag → 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 thePYPI_API_TOKENGitHub Actions secret. Both publish jobs
(v2 wheels + tombstone) passpassword: ${{ secrets.PYPI_API_TOKEN }}
topypa/gh-action-pypi-publish@release/v1. Workflow comments now
document the GCP → GH secret-refresh command. - Removed
permissions: id-token: writeand the OIDCenvironment:
blocks (no longer needed without OIDC). - Token was sourced from the GCP Secret Manager entry
PYPI_TOKEN
in projectcognitum-20260110and pushed to GH Actions via
gcloud secrets versions access | gh secret setso the value
never appeared in a shell variable or this session's output. - Rewrote
python/README.mdfrom 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 thematurin developsource-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@v5with 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):
-
wifi-densepose==1.99.0 — tombstone (raises ImportError with migration URL)
https://pypi.org/project/wifi-densepose/1.99.0/ -
wifi-densepose==2.0.0a1 — PyO3 wheel (win_amd64 cp310-abi3) + sdist
https://pypi.org/project/wifi-densepose/2.0.0a1/ -
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__sofrom ruview import BreathingExtractor
is equivalent tofrom wifi_densepose import BreathingExtractor.
Also re-exports__version__,__rust_version__,
__rust_build_tag__,__build_features__. Aliases theclient
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 literalPYPI_TOKENsubstring. Moved the refresh runbook
to docs/integrations/pypi-release.md (with the BOM-stripping
trstep 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 containdef/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__.pyat 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