Audit fix bundle (10 areas; details in ADR-117 + commit body below).
Server (main.rs / wiflow_v1.rs):
- UDP receiver filters loopback/multicast/unspecified before NODE_ADDRS
registration. Defends against `cargo test` cross-talk that spawned
250+ ping zombies on the production server's :5005 port.
- csi_keepalive_task pre-reaps `/sbin/ping -i 0.040` orphans at task
entry. macOS doesn't propagate parent death, so killed servers used
to leave init-parented pings running indefinitely.
- run_wiflow_inference stamps real classifier confidence onto every
keypoint (was hardcoded 1.0) — reads 0.037 on live data, honest.
- run_wiflow_inference clones only the tail-20 frames inside the lock,
not the full 600-deep VecDeque (~270 KB → ~9 KB per tick).
- wiflow_v1::build_input_from_history: zero-pad dead channel slots
instead of duplicating subcarrier 0 across all of them. Comment said
"zero the rest", prior code did the opposite.
- GET / now 308-redirects to /ui/index.html; API index moved to /api.
UI (ui/index.html, ui/components/LiveDemoTab.js):
- <section id="sensing"> gets a <div id="sensing-container"> child so
app.js::SensingTab.mount has its mount point. Sensing tab was
permanently blank.
- LiveDemoTab.fetchModels: only inject WiFlow into the dropdown if no
RVF model is already active. Prevents silent flip back to WiFlow
after every poll.
Tests (multi_node_test.rs):
- test_multi_node_udp_send probes 127.0.0.1:5005 first; if bind fails
(e.g. a dev server is running), skip the send. Two-layer defense
with the server-side filter above.
Docs (CHECKLIST.md, ADR-115, espectre-gap-analysis.md, ota-pipeline.md):
- CHECKLIST head sha + count refreshed (43→47 Done, head 0ec1e4b0,
ADR range to 001-117 with ADR-111 noted as intentionally absent).
- ADR-115 typo fixes: "ADR-100" → "ADR-110" (TP-Link WISP),
"ADR-111" → "ADR-109" (AP-MAC tracking actually lives there).
- gap-analysis "Still open" table: 8 shipped items annotated with
commit hashes; remainder reclassified Deferred with reason.
- ota-pipeline.md: new "Operator REST endpoints" section listing
/ota/recalibrate (ADR-109) and /ota/set-target (ADR-115) with
unauthed + bearer-token curl examples.
Verified post-restart:
- exactly 2 ping children, both parented to current PID, one per real
sensor IP, no 127.0.0.1.
- GET / → 308 → /ui/index.html.
- /api/v1/info: pose_estimation=true, version 0.3.0.
- /api/v1/pose/current: 17 COCO keypoints, confidence 0.037 (real).
- cargo test --workspace: 13 passed / 0 failed / 5 ignored.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Saves the comprehensive OTA pipeline reference written by another
agent so future sessions don't lose the diagnostic flowchart or the
"three FW prerequisites" causal chain.
Tested live against current FW (v0.6.4): port 8032 reachable on both
sensors, scripts/ota-deploy.sh round-trip works, both nodes
successfully switched partitions (ota_0 ↔ ota_1) without USB+BOOT
dance. OTA is the supported path for future FW changes from this
session — sensor µs timestamp (ADR-106 open item), NVS persistence
of gain-lock (gap-analysis #5), and any larger FW work.
Kept whole (329 lines, over the usual 200 line cap for docs) because
the flowchart and pitfall table lose meaning if split. The cap is a
guideline for new project ADRs; a verbatim recipe is justified by
diagnostic value.