Iter 20. Adds the wire-bytes companion to BfldPipeline::process so
callers needing BfldFrame (for ESP-NOW, UDP, file dump, witness
bundles, etc.) don't have to drop down to BfldEmitter + manual
BfldFrame construction.
Added (in src/pipeline.rs):
- BfldPipeline::process_to_frame(
inputs: SensingInputs,
header_template: BfldFrameHeader,
payload: BfldPayload,
embedding: Option<IdentityEmbedding>,
) -> Option<BfldFrame>
Algorithm:
1. Cache timestamp_ns from inputs (consumed by the inner process()).
2. Call self.process(inputs, embedding) — gate logic decides drop/emit.
Returns None if the gate rejects, propagating to caller.
3. Clone header_template, override timestamp_ns and privacy_class from
the current pipeline state (privacy_mode-aware).
4. Build via BfldFrame::from_payload — CRC covers the section-prefixed
payload bytes per ADR-119 §2.2.
Separation of concerns: pipeline owns gate / ring / hasher state; caller
owns AP / STA / session identity (provided via header_template).
tests/pipeline_to_frame.rs (6 named tests, all green):
process_to_frame_emits_frame_under_low_risk
(timestamp_ns + privacy_class correctly propagated from pipeline)
process_to_frame_returns_none_under_sustained_high_risk
(gate Reject path: two consecutive high-risk calls → None)
process_to_frame_round_trips_through_bytes
(frame.to_bytes() → BfldFrame::from_bytes() → parse_payload() identity)
process_to_frame_overrides_class_in_privacy_mode
(enable_privacy_mode → frame.header.privacy_class = Restricted byte)
process_to_frame_preserves_header_template_identity_fields
(ap_hash, sta_hash, session_id, channel from template survive)
process_to_frame_uses_input_timestamp_not_template_timestamp
(template.timestamp_ns = 12345 is overridden by inputs.timestamp_ns)
ACs progressed:
- ADR-118 §2.1 wire-bytes consumer path now reachable from BfldPipeline,
not just from low-level BfldEmitter + manual frame construction.
- ADR-119 AC5/AC6 — round-trip-through-bytes test exercises the full
pipeline+frame stack, not just the frame in isolation.
- ADR-122 §2.2 prep — the BfldFrame is the wire format MQTT eventually
publishes via tokio loop (next iter pair); process_to_frame is the
per-frame producer that loop will call.
Test config:
- cargo test --no-default-features → 72 passed (pipeline_to_frame cfg-out)
- cargo test → 152 passed (146 + 6)
Out of scope (next iter target):
- BfldPipelineHandle: Arc<Mutex<BfldPipeline>> + tokio task that pumps
an inbound (SensingInputs, IdentityEmbedding) channel into MQTT
per-class topics (ADR-122 §2.2). Brings in tokio + rumqttc deps
behind a `mqtt` feature.
- Cargo benchmark: pipeline throughput target ≥ 40 frames/sec on a
Pi 5 core (ADR-118 §6 P2 effort estimate).
Co-Authored-By: claude-flow <ruv@ruv.net>