Files
RuView/v2
rUv efbaa4de78 feat(rufield): ultrasonic as the field surface's second modality (ADR-262 P4)
ADR-262 §4 lists multi-modality as P4 and §8 question 5 leaves the choice of
the second modality open, asking whether it should be `rvcsi`. This is a
different answer, and the reason is that the cost collapsed.

`rufield-adapters` now ships `UltrasonicReplayAdapter` — the first adapter for
`Modality::Ultrasonic`, registry code 7, which had sat in the §8 registry since
v0.1 with nothing implementing it. It parses, validates and signs BatVu
(https://github.com/ruvnet/batvu) range profiles upstream. RuView does not have
to build any of that. It has to decide what it is willing to put on a wire.

## The submodule bump

`vendor/rufield` moves 43b1df3 -> 9955672, which is main. Two mechanical
breakages in the existing bridge, both struct literals missing fields added
upstream since the old pin:

  Observation      + track_id, attributes, identity_evidence,
                     channel_sounding_provenance
  SensorDescriptor + coordinate_frame, position_m, orientation_xyzw

All left empty, and each for its own reason rather than because a default was
convenient — the comments say which. The pose fields in particular are unset
because a CSI link has no boresight and no surveyed position, and §6 is explicit
that this surface makes no validated room-coordinate claim. A placeholder pose
would be inventing exactly that claim. The ultrasonic path DOES set them,
because a handheld scanner genuinely knows where it was pointing.

The nine existing P1 gates pass unchanged against the bumped submodule.

## The decision this module actually makes

RuView's egress rule is stricter than RuField's default guard — only P1/P2
leave the box — and the difference has to be structural rather than a runtime
refusal.

The adapter offers two output modes. The full per-bin frame is P0, which
`network_egress_allowed` holds edge-local: correctly, but silently, as a
dropped event at the end of a pipeline that did all the parsing and signing
first. The 32-bin max-pooled reduction is P1 and egress-safe.

So `ultrasonic.rs` does not offer the choice. It configures the coarse mode,
because a consumer cannot un-coarsen a coarse profile whereas a runtime check
can be reordered by someone who does not know why it is there. The gate still
runs, and `gate_coarse_scan_passes_the_egress_gate_intact` asserts it drops
nothing — a policy enforced only where it never fires is a policy nobody
notices removing.

## Gates

`tests/ultrasonic_gates.rs`, 12 tests, same shape as `p1_gates.rs`: round-trip
and serde stability, signature-verify plus tamper-detection, fusion ingest, P1
on BOTH tensor and observation (the guard is conjunctive — a P0 tensor under a
P1 observation is the composite leak it exists to close), structural
unreachability of P4/P5, trust-tier refusal in both directions, determinism,
and whole-file rejection of a malformed recording rather than a partial ingest.

And one asserting the honest negative result: an ultrasonic scan produces NO
fused inferences at all. The adapter declines to populate `presence` — one
transducer pair cannot distinguish a person from a coat over the back of a
chair — and the engine's feature vocabulary is entirely statements about a
body, so `range_m` has nothing to drive. RuField v0.1 has no predicate for
static geometry. That is asserted rather than papered over, because a demo that
fabricated `presence` would light up `person_present` and look like success.

## The fixture

`batvu_living_room.ultrasonic.jsonl` is not hand-written. It is produced by
BatVu's own `npm run artifacts` and copied verbatim, and the same file is a
fixture in ruvnet/rufield. A schema drift between BatVu's emitter and RuField's
parser now fails a build in one of three repositories rather than an ingest in
a deployment.

## Scope

Not wired into the running server. P1 shipped as a library before P3 wired it
in; this follows the same staging, so the translation and its gates are
reviewable on their own. `UltrasonicScan::egress_events` returns exactly what
`FieldSurface::emit` would need to broadcast.

Honesty unchanged from P1, plus one that is BatVu's: every current recording is
its own simulator's output, so events carry `synthetic: true` and are fusable
only under simulation trust. `captured_replay()` and `production()` reject them
outright, as they should.

27 tests pass (5 unit + 9 P1 + 12 P4 + 1 doc). clippy clean on the crate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BRtoJRh2M4LV3vLcSg2nSX
2026-08-26 00:33:05 +00:00
..