Skip to content

feat(introspection): ADR-099 midstream tap + /ws/introspection + /api/v1/introspection/snapshot#554

Merged
ruvnet merged 6 commits into
mainfrom
feat/midstream-introspection
May 14, 2026
Merged

feat(introspection): ADR-099 midstream tap + /ws/introspection + /api/v1/introspection/snapshot#554
ruvnet merged 6 commits into
mainfrom
feat/midstream-introspection

Conversation

@ruvnet
Copy link
Copy Markdown
Owner

@ruvnet ruvnet commented May 14, 2026

Updated 2026-05-13 — implementation + I5/I6 benchmark + I7 validation now landed.

Summary

Adds midstream as a parallel tap alongside RuView's existing event pipeline — not a replacement, no behaviour change to /ws/sensing or wifi-densepose-signal. Two new endpoints (off by default, --introspection):

  • GET /ws/introspection — newline-delimited JSON snapshots streamed at CSI frame rate.
  • GET /api/v1/introspection/snapshot — single-shot snapshot, auth-gated when RUVIEW_API_TOKEN is set.

Each snapshot carries frame_count, regime (Idle / Periodic / Transient / Chaotic / Unknown), lyapunov_exponent, attractor_dim, attractor_confidence, regime_changed (boolean — flips on first frame after a regime transition), top_k_similarity[] (highest-scoring signature matches).

What's in this PR

# Commit What it does
1 900b877c6 ADR-099 — design record
2 94ef12524 introspection module skeleton (530 lines, 8 lib tests)
3 4a1f3a1e1 Wire the tap into main.rs + /ws/introspection + /api/v1/introspection/snapshot
4 59d2d0e54 tests/introspection_latency.rs — empirical baseline against ADR-099 D4/D8
5 ca9752764 I6: regime_changed signal + per-frame analyze + honest D8 amendment
6 ce3304222 CHANGELOG entry under [Unreleased]

Empirical baseline (ADR-099 §D4 + §D8)

Measured on this branch via tests/introspection_latency.rs (200 frames of noise warm-up → 5-frame motion-ramp signature, host-side L1 stand-in scoring on a 1-D mean-amplitude feature):

Signal Frames to recognise Ratio vs event-path floor (16 frames) Status
top_k_similarity[0].above_threshold 5 3.20× Real, repeatable
regime_changed (10-frame motion window) did not fire Lyapunov dominated by 200-frame warm-up — needs multi-dim features
Per-frame update() p99 0.041 ms ~24× under ADR-099 D4's 1 ms budget ✅ Meets budget

Honest D8 amendment: the aspirational 10× ratio is architecturally unreachable on 1-D scalar features. Closing the gap requires ADR-208 Phase 2 (Hailo NPU vec128 embeddings) so multi-dim partial matches can discriminate from noise in 1–2 frames rather than 5. Until that lands, the tap ships off by default behind --introspection — D4 (per-frame budget) is met today, D8 (10× ratio) is contingent on ADR-208 P2.

Side finding (documented in ADR-099 D8): midstream's temporal-compare::dtw() uses discrete equality cost (designed for LLM tokens — 0 if seq1[i].value == seq2[j].value else 1). On f64 amplitude values that is strictly worse than the L1 stand-in. The L1 stand-in is what produces the 5-frame number; swapping in midstream DTW is not the path to closing D8. A numeric DTW is a separate concern (hand-roll or new crate).

I7 validation

Check Result
cargo test --workspace --no-default-features 1,656 passed, 0 failed, 8 ignored
python archive/v1/data/proof/verify.py ✅ VERDICT PASS — hash 8c0680d7…51c6 matches expected
cargo test -p wifi-densepose-sensing-server --no-default-features --lib introspection ✅ 8 / 8
cargo test -p wifi-densepose-sensing-server --no-default-features --test introspection_latency ✅ 5 / 5

What's NOT in this PR

  • No firmware change — the introspection tap consumes the existing CSI frame stream produced by current firmware (v0.6.4-esp32). Firmware versioning is unchanged.
  • No change to /ws/sensing or wifi-densepose-signal — ADR-098 already settled that those existing seams are correct; this ADR is the parallel-addition counterpart, not a substitution.
  • No NPU dependency — runs entirely on the existing host. D8's 10× target is documented as contingent on ADR-208 P2.

Files

  • docs/adr/ADR-099-midstream-introspection-tap.md — 242 lines, D8 reflects measured reality.
  • v2/crates/wifi-densepose-sensing-server/src/introspection.rs — 578 lines, 8 unit tests.
  • v2/crates/wifi-densepose-sensing-server/src/main.rs — +119 lines (state wiring, WS handler, snapshot endpoint, route registration).
  • v2/crates/wifi-densepose-sensing-server/src/lib.rspub mod introspection;
  • v2/crates/wifi-densepose-sensing-server/Cargo.tomlmidstreamer-temporal-compare = "0.2", midstreamer-attractor = "0.2".
  • v2/crates/wifi-densepose-sensing-server/tests/introspection_latency.rs — 288 lines, 5 tests.
  • CHANGELOG.md — entry under [Unreleased].

Closes / relates to

  • Implements ADR-099 (Proposed → Accepted on merge).
  • Complements ADR-098 (Rejected as a replacement — this ADR is the parallel-addition answer.).
  • No issue auto-closed; companion issue with benchmarks + capability surface will be opened in I9.

🤖 Generated with claude-flow

ruvnet added 6 commits May 13, 2026 22:42
…ion + low-latency tap (Proposed)

ADR-098 rejected midstream as a *replacement* for RuView's existing seams.
ADR-099 is the other half: midstream's `temporal-compare` (DTW) and
`temporal-attractor-studio` (Lyapunov + regime classification) crates as a
*parallel* per-frame introspection tap, alongside the existing window-aggregated
event pipeline.

The 8 decisions:

  D1 — Only midstreamer-temporal-compare 0.2 + midstreamer-attractor 0.2;
       scheduler / neural-solver / strange-loop are out of scope of this ADR.
  D2 — Tap point: post-validate, parallel to WindowBuffer::push in csi.rs.
       The existing /ws/sensing path is unchanged.
  D3 — New /ws/introspection topic + /api/v1/introspection/snapshot REST endpoint
       carrying IntrospectionSnapshot { regime, lyapunov_exponent,
       attractor_dim, top_k_similarity }.
  D4 — Per-frame updates only, never window-blocked. Soonest-event latency on
       the "shape recognized" path collapses from ~533 ms (16-frame @ 30 Hz
       window) to ~33 ms (one frame), a ~16× win.
  D5 — temporal-neural-solver (LTL) is out of scope (separate MAT audit ADR).
  D6 — ESP32 firmware unchanged; deployment is host-side only.
  D7 — Signature library is JSON, on-disk, customer-owned; three reference
       signatures ship as developer fixtures.
  D8 — Promotion bar is empirical: ≥10× p99 latency reduction vs. the existing
       /ws/sensing event path, or the feature stays behind a CLI flag.

Indexed in docs/adr/README.md. Phased adoption (P0 spike + benchmark → P1 first
real signature library → P2 dashboard widget → P3 capture workflow → P4 optional
adaptive_classifier hook). Implementation lands as ~150–250 lines + one
integration test in v2/crates/wifi-densepose-sensing-server in follow-up PRs.

Co-Authored-By: claude-flow <ruv@ruv.net>
Adds the per-frame introspection state that ADR-099 specifies, plus the two
midstream dependencies. Pure addition — no other code touched.

  v2/crates/wifi-densepose-sensing-server/Cargo.toml
    + midstreamer-temporal-compare = "0.2"
    + midstreamer-attractor        = "0.2"

  v2/crates/wifi-densepose-sensing-server/src/introspection.rs (new, 530 lines)
    pub struct IntrospectionState
      ├─ midstreamer-attractor's AttractorAnalyzer (regime + Lyapunov)
      ├─ SignatureLibrary (JSON-loaded labelled segments)
      ├─ VecDeque<f64> sliding amplitude buffer (default 128 points)
      └─ update(timestamp_ns, derived_feature) — never window-blocked
         + snapshot() -> IntrospectionSnapshot
            { timestamp_ns, frame_count, regime, lyapunov_exponent,
              attractor_dim, attractor_confidence, top_k_similarity }
    pub enum Regime { Idle, Periodic, Transient, Chaotic, Unknown }
    pub struct Signature { id, label, vectors, dtw, promotion_threshold }
    pub struct SimilarityMatch { signature_id, score, above_threshold }

DTW path is currently a host-side stand-in (length-normalised L1 with the
real DTW call deferred to I3/I5 once vec128 embeddings exist — ADR-099 P1).
The attractor path is wired to midstream directly. The analyze() step only
runs every N frames (default 8) to stay under the per-frame ms budget.

8 unit tests (snapshot defaults, frame-count + timestamp advance, empty
library, scoring + ordering invariants, threshold gating, empty-signature
fault-tolerance, regime classification after 200 frames). 199 → 207 lib tests,
0 failures. cargo build clean (only pre-existing warnings).

Co-Authored-By: claude-flow <ruv@ruv.net>
…ion + /api/v1/introspection/snapshot

I3 (per ADR-099). Three changes in main.rs:

1) AppStateInner: + intro: IntrospectionState + intro_tx: broadcast::Sender<String>
   (256-slot ring, same shape as the existing tx).

2) ESP32 frame path: after the global frame_history push, before the
   per-node mutable borrow of s.node_states, compute the per-frame derived
   feature (mean amplitude across subcarriers), call s.intro.update(ts_ns,
   feature), and broadcast the snapshot JSON to s.intro_tx. Placement is
   deliberate — between the global state's mutable touch and the per-node
   &mut so borrow-checking stays linear; ns is borrowed *after* the tap
   completes its s.intro / s.intro_tx access.

3) Routes:
     ws_introspection_handler   → /ws/introspection
     api_introspection_snapshot → /api/v1/introspection/snapshot
   Same Axum + tokio::sync::broadcast pattern as ws_sensing_handler,
   subscribed against s.intro_tx. Wrapped by the bearer-auth middleware
   already on /api/v1/* — orchestrator probes and unauthenticated /ws/sensing
   reachers continue to land on the existing topic.

Verified:
  cargo build -p wifi-densepose-sensing-server --no-default-features ✓
  cargo test  -p wifi-densepose-sensing-server --no-default-features
    lib:           207 passed, 0 failed (199 pre-tap + 8 introspection)
    integration suites: 70, 8, 16, 18 passed, 0 failed
  cargo clippy: clean on the introspection surface (pre-existing warnings
                on -core / -ruvector / -signal unchanged).

Co-Authored-By: claude-flow <ruv@ruv.net>
…seline

I5. Measures the architectural latency floor of the introspection path
vs. the window-aggregated event path, plus the per-frame update cost.

  Result on this run:
    ADR-099 D8 floor ratio    : 3.20× (16 frames / 5 frames)
                                D8 target ≥10× — NOT YET MET on the host-side
                                L1 stand-in scoring; I6 closes the gap.
    ADR-099 D4 update p50/p99 : 0.001 ms / 0.012 ms (~83× under the 1 ms
                                budget on a desktop runner; even with thermal
                                throttling on a Pi 5 we have orders of
                                magnitude of headroom).
    Regime after 200 frames   : Idle, lyapunov=-2.32, confidence=1.0
                                (attractor analyzer is firing as designed).

The D8 gap is structural to the current scoring: signature_score() uses a
length-normalised L1 over the trailing window, which requires roughly the
full signature length of in-shape frames before crossing
promotion_threshold. Closing it is the I6 work — swap in the real
midstreamer-temporal-compare DTW (partial-match scoring) and/or surface
the attractor's regime-change as an *earlier* trigger than full signature
match.

The latency-ratio test asserts a regression bar (≥3.0×) on the L1 baseline,
prints the D8 ratio + whether it's met, and explicitly defers the ≥10×
target to I6 in the docstring. Better empirical reporting than a flag that
silently fails until tuned.

ESP32 sanity (independent of the benchmark): COM7 device alive at csi_collector
cb #84500 (~30 min uptime), len=128/256 HT20/HT40, ch5, RSSI swings -44 to
-79 (= real motion in the room). UDP target still unreachable from this
host per the earlier diagnosis; that's a deployment fix, not a measurement
gate.

Co-Authored-By: claude-flow <ruv@ruv.net>
… honest ADR-099 D8 amendment

Three threads in this commit:

1) Per-frame attractor analysis (default analyze_every_n: 8 → 1).
   The I5 benchmark put per-frame update at 0.012 ms p99 — 83× under D4's
   1 ms budget. The cost case for the every-8th-frame default doesn't hold;
   per-frame analysis is what makes regime_changed a viable early-detection
   trigger.

2) New `regime_changed: bool` field in IntrospectionSnapshot — flips on any
   frame whose attractor regime classification differs from the previous
   frame's. Pairs with top_k_similarity (full-shape match) to give
   downstream consumers two latencies with different robustness profiles.

3) Honest amendment of ADR-099 D8 to reflect empirical reality:
   - L1 stand-in achieves 3.20× ratio (5-frame shape match vs 16-frame
     event-path floor); the 10× aspirational bar is architecturally
     unreachable at 1-D scalar feature resolution.
   - regime_changed didn't fire in the 10-frame motion window — the
     200-frame noise trajectory dominates the Lyapunov classification, and
     short perturbations don't shift the regime fast enough on a scalar
     feature.
   - Path to 10×: ADR-208 Phase 2 (Hailo NPU vec128 embeddings) — multi-dim
     partial matches discriminate from noise in 1-2 frames, not 5.
   - Side finding: midstream temporal-compare::DTW uses *discrete equality*
     cost (designed for LLM tokens), not numeric distance — swapping it in
     for f64 amplitude scoring would be strictly worse than the L1 stand-in.
     A numeric DTW is a separate concern (hand-roll or new crate).
   - Revised D8: ship behind --introspection (off by default) until multi-
     dim features land. Per-frame update budget IS met (0.041 ms p99 in this
     bench, ~24× under the 1 ms bar) — the feature is cheap enough to
     carry dark today.

cargo test -p wifi-densepose-sensing-server --no-default-features:
  introspection (lib): 8 passed, 0 failed
  introspection_latency (test): 5 passed, 0 failed (incl. new
                                 regime_change_path_latency)
clippy: clean on the introspection surface (pre-existing approx_constant
        lints in pose.rs / main.rs unchanged).

Co-Authored-By: claude-flow <ruv@ruv.net>
Lists the new `/ws/introspection` + `/api/v1/introspection/snapshot`
endpoints, the empirical baseline (0.041 ms p99 update, 5-frame shape
match on 1-D L1 stand-in), and the honest D8 amendment.

Co-Authored-By: claude-flow <ruv@ruv.net>
@ruvnet ruvnet changed the title docs(adr): ADR-099 — adopt midstream as RuView's real-time introspection + low-latency tap (Proposed) feat(introspection): ADR-099 midstream tap + /ws/introspection + /api/v1/introspection/snapshot May 14, 2026
@ruvnet
Copy link
Copy Markdown
Owner Author

ruvnet commented May 14, 2026

Companion issue tracking the v0.8.0 capability surface, measured baseline, and the remaining work to close ADR-099 D8 (10× ratio): #556.

@ruvnet ruvnet merged commit 457f713 into main May 14, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant