chore(repo): move v1/ → archive/v1/ + add archive/README.md (#430)

The Rust port at v2/ has been the primary codebase since the rename
in #427. The Python implementation at v1/ is no longer the active
target; the only load-bearing path is the deterministic proof bundle
at v1/data/proof/ (per ADR-011 / ADR-028 witness verification).

Move the whole Python tree into archive/v1/ and document the policy
in archive/README.md: no new features, bug fixes only when they affect
a still-load-bearing path (currently just the proof), CI continues to
verify the proof on every push and PR.

Path references updated in 26 files via path-pattern sed (only
matches v1/<known-child> patterns, never bare v1 or API URLs like
/api/v1/). Two double-prefix typos (archive/archive/v1/) caught and
hand-fixed in verify-pipeline.yml and ADR-011.

Validated:
- Python proof verify.py imports cleanly at archive/v1/data/proof/
  (numpy/scipy still required; CI installs requirements-lock.txt
  from archive/v1/ now)
- cargo test --workspace --no-default-features → 1,539 passed,
  0 failed, 8 ignored (unaffected by Python tree relocation)
- ESP32-S3 on COM7 untouched (no firmware paths changed)

After-merge: contributors should re-run any local `python v1/...`
commands as `python archive/v1/...` (CLAUDE.md and CHANGELOG already
updated).
This commit is contained in:
rUv
2026-04-25 23:07:52 -04:00
committed by GitHub
parent 74233cfb23
commit 81cc241b9e
183 changed files with 290 additions and 216 deletions
@@ -20,31 +20,31 @@ The following code paths produce fake data **in the default configuration** or a
| File | Line | Issue | Impact |
|------|------|-------|--------|
| `v1/src/core/csi_processor.py` | 390 | `doppler_shift = np.random.rand(10) # Placeholder` | **Real feature extractor returns random Doppler** - kills credibility of entire feature pipeline |
| `v1/src/hardware/csi_extractor.py` | 83-84 | `amplitude = np.random.rand(...)` in CSI extraction fallback | Random data silently substituted when parsing fails |
| `v1/src/hardware/csi_extractor.py` | 129-135 | `_parse_atheros()` returns `np.random.rand()` with comment "placeholder implementation" | Named as if it parses real data, actually random |
| `v1/src/hardware/router_interface.py` | 211-212 | `np.random.rand(3, 56)` in fallback path | Silent random fallback |
| `v1/src/services/pose_service.py` | 431 | `mock_csi = np.random.randn(64, 56, 3) # Mock CSI data` | Mock CSI in production code path |
| `v1/src/services/pose_service.py` | 293-356 | `_generate_mock_poses()` with `random.randint` throughout | Entire mock pose generator in service layer |
| `v1/src/services/pose_service.py` | 489-607 | Multiple `random.randint` for occupancy, historical data | Fake statistics that look real in API responses |
| `v1/src/api/dependencies.py` | 82, 408 | "return a mock user for development" | Auth bypass in default path |
| `archive/v1/src/core/csi_processor.py` | 390 | `doppler_shift = np.random.rand(10) # Placeholder` | **Real feature extractor returns random Doppler** - kills credibility of entire feature pipeline |
| `archive/v1/src/hardware/csi_extractor.py` | 83-84 | `amplitude = np.random.rand(...)` in CSI extraction fallback | Random data silently substituted when parsing fails |
| `archive/v1/src/hardware/csi_extractor.py` | 129-135 | `_parse_atheros()` returns `np.random.rand()` with comment "placeholder implementation" | Named as if it parses real data, actually random |
| `archive/v1/src/hardware/router_interface.py` | 211-212 | `np.random.rand(3, 56)` in fallback path | Silent random fallback |
| `archive/v1/src/services/pose_service.py` | 431 | `mock_csi = np.random.randn(64, 56, 3) # Mock CSI data` | Mock CSI in production code path |
| `archive/v1/src/services/pose_service.py` | 293-356 | `_generate_mock_poses()` with `random.randint` throughout | Entire mock pose generator in service layer |
| `archive/v1/src/services/pose_service.py` | 489-607 | Multiple `random.randint` for occupancy, historical data | Fake statistics that look real in API responses |
| `archive/v1/src/api/dependencies.py` | 82, 408 | "return a mock user for development" | Auth bypass in default path |
#### Moderate Severity (mock gated behind flags but confusing)
| File | Line | Issue |
|------|------|-------|
| `v1/src/config/settings.py` | 144-145 | `mock_hardware=False`, `mock_pose_data=False` defaults - correct, but mock infrastructure exists |
| `v1/src/core/router_interface.py` | 27-300 | 270+ lines of mock data generation infrastructure in production code |
| `v1/src/services/pose_service.py` | 84-88 | Silent conditional: `if not self.settings.mock_pose_data` with no logging of real-mode |
| `v1/src/services/hardware_service.py` | 72-375 | Interleaved mock/real paths throughout |
| `archive/v1/src/config/settings.py` | 144-145 | `mock_hardware=False`, `mock_pose_data=False` defaults - correct, but mock infrastructure exists |
| `archive/v1/src/core/router_interface.py` | 27-300 | 270+ lines of mock data generation infrastructure in production code |
| `archive/v1/src/services/pose_service.py` | 84-88 | Silent conditional: `if not self.settings.mock_pose_data` with no logging of real-mode |
| `archive/v1/src/services/hardware_service.py` | 72-375 | Interleaved mock/real paths throughout |
#### Low Severity (placeholders/TODOs)
| File | Line | Issue |
|------|------|-------|
| `v1/src/core/router_interface.py` | 198 | "Collect real CSI data from router (placeholder implementation)" |
| `v1/src/api/routers/health.py` | 170-171 | `uptime_seconds = 0.0 # TODO` |
| `v1/src/services/pose_service.py` | 739 | `"uptime_seconds": 0.0 # TODO` |
| `archive/v1/src/core/router_interface.py` | 198 | "Collect real CSI data from router (placeholder implementation)" |
| `archive/v1/src/api/routers/health.py` | 170-171 | `uptime_seconds = 0.0 # TODO` |
| `archive/v1/src/services/pose_service.py` | 739 | `"uptime_seconds": 0.0 # TODO` |
### Root Cause Analysis
@@ -119,7 +119,7 @@ def _parse_atheros(self, raw_data: bytes) -> CSIData:
**All mock code moves to a dedicated module. Default execution NEVER touches mock paths.**
```
v1/src/
archive/v1/src/
├── core/
│ ├── csi_processor.py # Real processing only
│ └── router_interface.py # Real hardware interface only
@@ -157,7 +157,7 @@ if MOCK_MODE:
A small real CSI capture file + one-command verification pipeline:
```
v1/data/proof/
archive/v1/data/proof/
├── README.md # How to verify
├── sample_csi_capture.bin # Real CSI data (1 second, ~50 KB)
├── sample_csi_capture_meta.json # Capture metadata (hardware, env)
@@ -172,7 +172,7 @@ v1/data/proof/
"""Verify WiFi-DensePose pipeline produces deterministic output from real CSI data.
Usage:
python v1/data/proof/verify.py
python archive/v1/data/proof/verify.py
Expected output:
PASS: Pipeline output matches expected hash
@@ -265,13 +265,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
WORKDIR /app
# Pinned requirements (not a reference to missing file)
COPY v1/requirements-lock.txt ./requirements.txt
COPY archive/v1/requirements-lock.txt ./requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY v1/ ./v1/
COPY archive/v1/ ./v1/
# Proof of reality: verify pipeline on build
RUN cd v1 && python data/proof/verify.py
RUN cd archive/v1 && python data/proof/verify.py
EXPOSE 8000
# Default: REAL mode (mock requires explicit opt-in)
@@ -281,7 +281,7 @@ CMD ["uvicorn", "v1.src.api.main:app", "--host", "0.0.0.0", "--port", "8000"]
**Key change**: `RUN python data/proof/verify.py` **during build** means the Docker image cannot be created unless the pipeline produces correct output from real CSI data.
**Requirements lockfile** (`v1/requirements-lock.txt`):
**Requirements lockfile** (`archive/v1/requirements-lock.txt`):
```
# Core (required)
fastapi==0.115.6
@@ -307,9 +307,9 @@ name: Verify Signal Pipeline
on:
push:
paths: ['v1/src/**', 'v1/data/proof/**']
paths: ['archive/v1/src/**', 'archive/v1/data/proof/**']
pull_request:
paths: ['v1/src/**']
paths: ['archive/v1/src/**']
jobs:
verify:
@@ -322,11 +322,11 @@ jobs:
- name: Install minimal deps
run: pip install numpy scipy pydantic pydantic-settings
- name: Verify pipeline determinism
run: python v1/data/proof/verify.py
run: python archive/v1/data/proof/verify.py
- name: Verify no random in production paths
run: |
# Fail if np.random appears in production code (not in testing/)
! grep -r "np\.random\.\(rand\|randn\|randint\)" v1/src/ \
! grep -r "np\.random\.\(rand\|randn\|randint\)" archive/v1/src/ \
--include="*.py" \
--exclude-dir=testing \
|| (echo "FAIL: np.random found in production code" && exit 1)
@@ -336,23 +336,23 @@ jobs:
| File | Action | Description |
|------|--------|-------------|
| `v1/src/core/csi_processor.py:390` | **Replace** | Real Doppler extraction from temporal CSI history |
| `v1/src/hardware/csi_extractor.py:83-84` | **Replace** | Hard error with descriptive message when parsing fails |
| `v1/src/hardware/csi_extractor.py:129-135` | **Replace** | Real Atheros CSI parser or hard error with hardware instructions |
| `v1/src/hardware/router_interface.py:198-212` | **Replace** | Hard error for unimplemented hardware, or real `iwconfig` + CSI tool integration |
| `v1/src/services/pose_service.py:293-356` | **Move** | Move `_generate_mock_poses()` to `v1/src/testing/mock_pose_generator.py` |
| `v1/src/services/pose_service.py:430-431` | **Remove** | Remove mock CSI generation from production path |
| `v1/src/services/pose_service.py:489-607` | **Replace** | Real statistics from database, or explicit "no data" response |
| `v1/src/core/router_interface.py:60-300` | **Move** | Move mock generator to `v1/src/testing/mock_csi_generator.py` |
| `v1/src/api/dependencies.py:82,408` | **Replace** | Real auth check or explicit dev-mode bypass with logging |
| `v1/data/proof/` | **Create** | Proof bundle (sample capture + expected hash + verify script) |
| `v1/requirements-lock.txt` | **Create** | Pinned minimal dependencies |
| `archive/v1/src/core/csi_processor.py:390` | **Replace** | Real Doppler extraction from temporal CSI history |
| `archive/v1/src/hardware/csi_extractor.py:83-84` | **Replace** | Hard error with descriptive message when parsing fails |
| `archive/v1/src/hardware/csi_extractor.py:129-135` | **Replace** | Real Atheros CSI parser or hard error with hardware instructions |
| `archive/v1/src/hardware/router_interface.py:198-212` | **Replace** | Hard error for unimplemented hardware, or real `iwconfig` + CSI tool integration |
| `archive/v1/src/services/pose_service.py:293-356` | **Move** | Move `_generate_mock_poses()` to `archive/v1/src/testing/mock_pose_generator.py` |
| `archive/v1/src/services/pose_service.py:430-431` | **Remove** | Remove mock CSI generation from production path |
| `archive/v1/src/services/pose_service.py:489-607` | **Replace** | Real statistics from database, or explicit "no data" response |
| `archive/v1/src/core/router_interface.py:60-300` | **Move** | Move mock generator to `archive/v1/src/testing/mock_csi_generator.py` |
| `archive/v1/src/api/dependencies.py:82,408` | **Replace** | Real auth check or explicit dev-mode bypass with logging |
| `archive/v1/data/proof/` | **Create** | Proof bundle (sample capture + expected hash + verify script) |
| `archive/v1/requirements-lock.txt` | **Create** | Pinned minimal dependencies |
| `.github/workflows/verify-pipeline.yml` | **Create** | CI verification |
### Hardware Documentation
```
v1/docs/hardware-setup.md (to be created)
archive/v1/docs/hardware-setup.md (to be created)
# Supported Hardware Matrix
@@ -368,17 +368,17 @@ v1/docs/hardware-setup.md (to be created)
2. Capture 10 seconds of empty-room baseline
3. Have one person walk through at normal pace
4. Capture 10 seconds during walk-through
5. Run calibration: `python v1/scripts/calibrate.py --baseline empty.dat --activity walk.dat`
5. Run calibration: `python archive/v1/scripts/calibrate.py --baseline empty.dat --activity walk.dat`
```
## Consequences
### Positive
- **"Clone, build, verify" in one command**: `docker build . && docker run --rm wifi-densepose python v1/data/proof/verify.py` produces a deterministic PASS
- **"Clone, build, verify" in one command**: `docker build . && docker run --rm wifi-densepose python archive/v1/data/proof/verify.py` produces a deterministic PASS
- **No silent fakes**: Random data never appears in production output
- **CI enforcement**: PRs that introduce `np.random` in production paths fail automatically
- **Credibility anchor**: SHA-256 verified output from real CSI capture is unchallengeable proof
- **Clear mock boundary**: Mock code exists only in `v1/src/testing/`, never imported by production modules
- **Clear mock boundary**: Mock code exists only in `archive/v1/src/testing/`, never imported by production modules
### Negative
- **Requires real CSI capture**: Someone must capture and commit a real CSI sample (one-time effort)
@@ -390,7 +390,7 @@ v1/docs/hardware-setup.md (to be created)
A stranger can:
1. `git clone` the repository
2. Run ONE command (`docker build .` or `python v1/data/proof/verify.py`)
2. Run ONE command (`docker build .` or `python archive/v1/data/proof/verify.py`)
3. See `PASS: Pipeline output matches expected hash` with a specific SHA-256
4. Confirm no `np.random` in any non-test file via CI badge
@@ -1,7 +1,7 @@
# ADR-013: Feature-Level Sensing on Commodity Gear (Option 3)
## Status
Accepted — Implemented (36/36 unit tests pass, see `v1/src/sensing/` and `v1/tests/unit/test_sensing.py`)
Accepted — Implemented (36/36 unit tests pass, see `archive/v1/src/sensing/` and `archive/v1/tests/unit/test_sensing.py`)
## Date
2026-02-28
@@ -323,7 +323,7 @@ class PresenceClassifier:
### Proof Bundle for Commodity Sensing
```
v1/data/proof/commodity/
archive/v1/data/proof/commodity/
├── rssi_capture_30sec.json # 30 seconds of RSSI from 3 receivers
├── rssi_capture_meta.json # Hardware: Intel AX200, Router: TP-Link AX1800
├── scenario.txt # "Person walks through room at t=10s, sits at t=20s"
@@ -375,7 +375,7 @@ class CommodityBackend(SensingBackend):
### Implementation Status
The full commodity sensing pipeline is implemented in `v1/src/sensing/`:
The full commodity sensing pipeline is implemented in `archive/v1/src/sensing/`:
| Module | File | Description |
|--------|------|-------------|
@@ -384,7 +384,7 @@ The full commodity sensing pipeline is implemented in `v1/src/sensing/`:
| Classifier | `classifier.py` | `PresenceClassifier` with ABSENT/PRESENT_STILL/ACTIVE levels, confidence scoring |
| Backend | `backend.py` | `CommodityBackend` wiring collector → extractor → classifier, reports PRESENCE + MOTION capabilities |
**Test coverage**: 36 tests in `v1/tests/unit/test_sensing.py` — all passing:
**Test coverage**: 36 tests in `archive/v1/tests/unit/test_sensing.py` — all passing:
- `TestRingBuffer` (4), `TestSimulatedCollector` (5), `TestFeatureExtractor` (8), `TestCusum` (4), `TestPresenceClassifier` (7), `TestCommodityBackend` (6), `TestBandPower` (2)
**Dependencies**: `numpy`, `scipy` (for FFT and spectral analysis)
+4 -4
View File
@@ -22,8 +22,8 @@ This ADR answers *how* to build it — the concrete development sequence, the sp
| Frame types | `wifi-densepose-hardware/src/csi_frame.rs` | Complete — `CsiFrame`, `CsiMetadata`, `SubcarrierData`, `to_amplitude_phase()` |
| Parse error types | `wifi-densepose-hardware/src/error.rs` | Complete — `ParseError` enum with 6 variants |
| Signal processing pipeline | `wifi-densepose-signal` crate | Complete — Hampel, Fresnel, BVP, Doppler, spectrogram |
| CSI extractor (Python) | `v1/src/hardware/csi_extractor.py` | Stub — `_read_raw_data()` raises `NotImplementedError` |
| Router interface (Python) | `v1/src/hardware/router_interface.py` | Stub — `_parse_csi_response()` raises `RouterConnectionError` |
| CSI extractor (Python) | `archive/v1/src/hardware/csi_extractor.py` | Stub — `_read_raw_data()` raises `NotImplementedError` |
| Router interface (Python) | `archive/v1/src/hardware/router_interface.py` | Stub — `_parse_csi_response()` raises `RouterConnectionError` |
**Not yet implemented:**
@@ -211,10 +211,10 @@ The bridge test: parse a known binary frame, convert to `CsiData`, assert `ampli
### Layer 4 — Python `_read_raw_data()` Real Implementation
Replace the `NotImplementedError` stub in `v1/src/hardware/csi_extractor.py` with a UDP socket reader. This allows the Python pipeline to receive real CSI from the aggregator while the Rust pipeline is being integrated.
Replace the `NotImplementedError` stub in `archive/v1/src/hardware/csi_extractor.py` with a UDP socket reader. This allows the Python pipeline to receive real CSI from the aggregator while the Rust pipeline is being integrated.
```python
# v1/src/hardware/csi_extractor.py
# archive/v1/src/hardware/csi_extractor.py
# Replace _read_raw_data() stub:
import socket as _socket
+2 -2
View File
@@ -34,7 +34,7 @@ Implement a **sensing-only UI mode** that:
- Breathing ring modulation when breathing-band power detected
- Side panel with RSSI sparkline, feature meters, and classification badge
4. **Python WebSocket bridge** (`v1/src/sensing/ws_server.py`) that:
4. **Python WebSocket bridge** (`archive/v1/src/sensing/ws_server.py`) that:
- Auto-detects ESP32 UDP CSI stream on port 5005 (ADR-018 binary frames)
- Falls back to `WindowsWifiCollector``SimulatedCollector`
- Runs `RssiFeatureExtractor``PresenceClassifier` pipeline
@@ -80,7 +80,7 @@ Windows WiFi RSSI ───┘ │ │
### Created
| File | Purpose |
|------|---------|
| `v1/src/sensing/ws_server.py` | Python asyncio WebSocket server with auto-detect collectors |
| `archive/v1/src/sensing/ws_server.py` | Python asyncio WebSocket server with auto-detect collectors |
| `ui/components/SensingTab.js` | Sensing tab UI with Three.js integration |
| `ui/components/gaussian-splats.js` | Custom GLSL Gaussian splat renderer |
| `ui/services/sensing.service.js` | WebSocket client with reconnect + simulation fallback |
@@ -40,8 +40,8 @@ Use the `wifi-densepose-nn` crate with `default-features = ["onnx"]` only. This
| Component | Rust Crate | Replaces Python |
|-----------|-----------|-----------------|
| CSI processing | `wifi-densepose-signal::csi_processor` | `v1/src/sensing/feature_extractor.py` |
| Motion detection | `wifi-densepose-signal::motion` | `v1/src/sensing/classifier.py` |
| CSI processing | `wifi-densepose-signal::csi_processor` | `archive/v1/src/sensing/feature_extractor.py` |
| Motion detection | `wifi-densepose-signal::motion` | `archive/v1/src/sensing/classifier.py` |
| BVP extraction | `wifi-densepose-signal::bvp` | N/A (new capability) |
| Fresnel geometry | `wifi-densepose-signal::fresnel` | N/A (new capability) |
| Subcarrier selection | `wifi-densepose-signal::subcarrier_selection` | N/A (new capability) |
+4 -4
View File
@@ -232,10 +232,10 @@ python scripts/provision.py --port COM7 \
| Component | File | Purpose |
|-----------|------|---------|
| Reference signal | `v1/data/proof/sample_csi_data.json` | 1,000 synthetic CSI frames, seed=42 |
| Generator | `v1/data/proof/generate_reference_signal.py` | Deterministic multipath model |
| Verifier | `v1/data/proof/verify.py` | SHA-256 hash comparison |
| Expected hash | `v1/data/proof/expected_features.sha256` | `0b82bd45...` |
| Reference signal | `archive/v1/data/proof/sample_csi_data.json` | 1,000 synthetic CSI frames, seed=42 |
| Generator | `archive/v1/data/proof/generate_reference_signal.py` | Deterministic multipath model |
| Verifier | `archive/v1/data/proof/verify.py` | SHA-256 hash comparison |
| Expected hash | `archive/v1/data/proof/expected_features.sha256` | `0b82bd45...` |
**Audit-time result:** PASS. Hash regenerated with numpy 2.4.2 + scipy 1.17.1. Pipeline hash: `8c0680d7d285739ea9597715e84959d9c356c87ee3ad35b5f1e69a4ca41151c6`.
@@ -108,7 +108,7 @@ Remove duplicated platform-detection logic from `ws_server.py` and `install.sh`.
## Implementation Notes
1. Add `create_collector()` and `BaseCollector.is_available()` to `v1/src/sensing/rssi_collector.py`
1. Add `create_collector()` and `BaseCollector.is_available()` to `archive/v1/src/sensing/rssi_collector.py`
2. Refactor `ws_server.py` `_init_collector()` to call `create_collector()`
3. Update `install.sh` `detect_wifi_hardware()` to use shared detection logic
4. Add unit tests for each platform path (mock `/proc/net/wireless` presence/absence)
+3 -3
View File
@@ -17,19 +17,19 @@ Address the 15 prioritized issues from the QE analysis in three waves: P0 (immed
### 1. Rate Limiter Bypass (Security HIGH)
- **Location:** `v1/src/middleware/rate_limit.py:200-206`
- **Location:** `archive/v1/src/middleware/rate_limit.py:200-206`
- **Problem:** Trusts `X-Forwarded-For` without validation. Any client bypasses rate limits via header spoofing.
- **Fix:** Validate forwarded headers against trusted proxy list, or use connection IP directly.
### 2. Exception Details Leaked in Responses (Security HIGH)
- **Location:** `v1/src/api/routers/pose.py:140`, `stream.py:297`, +5 endpoints
- **Location:** `archive/v1/src/api/routers/pose.py:140`, `stream.py:297`, +5 endpoints
- **Problem:** Stack traces visible regardless of environment.
- **Fix:** Wrap with generic error responses in production; log details server-side only.
### 3. WebSocket JWT in URL (Security HIGH, CWE-598)
- **Location:** `v1/src/api/routers/stream.py:74`, `v1/src/middleware/auth.py:243`
- **Location:** `archive/v1/src/api/routers/stream.py:74`, `archive/v1/src/middleware/auth.py:243`
- **Problem:** Tokens in query strings visible in logs/proxies/browser history.
- **Fix:** Use WebSocket subprotocol or first-message auth pattern.