//! # wifi-densepose-rufield
//!
//! ADR-262 **anti-corruption bridge**: converts RuView's live WiFi-CSI sensing
//! output into signed RuField [`FieldEvent`](rufield_core::FieldEvent)s.
//!
//! This crate is the **single coupling point** (ADR-262 §5.4) between RuView and
//! the standalone RuField MFS spec (`vendor/rufield`, ADR-260). It depends on
//! the four pure-Rust rufield crates **via path** — `rufield-core`,
//! `-provenance`, `-privacy`, `-fusion` — and on **no** RuView internal crate.
//! Inputs are owned primitives ([`SensingSnapshot`]) that mirror what RuView's
//! sensing cycle produces, so the bridge never imports `SensingUpdate` /
//! `TrustedOutput` directly.
//!
//! ## What P1 ships (honesty — ADR-262 §0 / §6)
//!
//! This is **P1 plumbing**: a tested `SensingSnapshot → FieldEvent` conversion
//! plus the **fail-closed privacy mapping** that is the §3.3 correctness item.
//! It is **not** wired into the live server (that is P3) and makes **no accuracy
//! claim** — RuField v0.1 is synthetic end-to-end and RuView's single-link CSI
//! carries its own caveats. The gates here are round-trip / fusability /
//! privacy-safety / determinism, not validated F1.
//!
//! ## The critical correctness item: the privacy mapping (§3.3)
//!
//! RuView's `Derived` class has byte value `1` (below `Anonymous = 2`) yet
//! carries an identity embedding. The bridge maps it to **P4/P5 by information
//! content, never P1** — see [`map_privacy`]. Mapping off the byte would leak
//! identity as low-privacy; [`map_privacy`] (and its dedicated test
//! `derived_identity_never_maps_to_low_privacy`) exist specifically to prevent
//! that.
//!
//! ## Example
//!
//! ```
//! use wifi_densepose_rufield::{
//!     snapshot_to_field_event, SensingSnapshot, SensingFeatures, SensingClass,
//!     RuViewPrivacyClass,
//! };
//! use rufield_provenance::{Signer, is_fusable};
//!
//! let snap = SensingSnapshot {
//!     timestamp_ns: 1_791_986_400_000_000_000,
//!     features: SensingFeatures {
//!         mean_rssi: -55.0,
//!         variance: 0.4,
//!         motion_band_power: 2.0,
//!         breathing_band_power: 0.3,
//!         dominant_freq_hz: 0.25,
//!         change_points: 1,
//!         spectral_power: 3.0,
//!     },
//!     classification: SensingClass {
//!         motion_level: "low".into(),
//!         presence: true,
//!         confidence: 0.82,
//!     },
//!     signal_field: None,
//!     trust_class: RuViewPrivacyClass::Anonymous,
//!     demoted: false,
//!     identity_bound: false,
//!     node_id: "esp32_room_01".into(),
//! };
//!
//! let signer = Signer::from_seed(b"adr-262-bridge-seed-32-bytes-ok!");
//! let event = snapshot_to_field_event(&snap, &signer);
//! assert!(is_fusable(&event)); // ed25519-signed, non-synthetic ⇒ fusable
//! ```

#![forbid(unsafe_code)]

pub mod bridge;
pub mod privacy;
pub mod snapshot;

pub use bridge::{snapshot_egress_class, snapshot_to_field_event};
pub use privacy::{apply_demotion_floor, egress_class, map_privacy};
pub use snapshot::{
    RuViewPrivacyClass, SensingClass, SensingFeatures, SensingSnapshot, SignalField,
};

// Re-export the rufield surface a bridge consumer needs, so callers depend on
// one crate.
pub use rufield_core::{Destination, FieldEvent, Modality, PrivacyClass, PrivacyDecision};
pub use rufield_fusion::RuFieldFusion;
pub use rufield_privacy::{DefaultPrivacyGuard, PrivacyPolicy};
pub use rufield_provenance::{is_fusable, verify_event, Signer};

/// Whether a mapped [`PrivacyClass`] may be surfaced on a **network** egress
/// (ADR-262 §4 P3 — the live `/api/field` / `/ws/field` surface must respect
/// the same default §10 network policy `/ws/sensing` honours, never emitting
/// above-policy data).
///
/// **Fail-closed for a live, unattended surface.** The live RuView surface has
/// **no per-event consent or identity-binding ceremony** — so this is *stricter*
/// than [`DefaultPrivacyGuard::authorize`]: it requires BOTH that the default
/// guard would `Allow` the class onto [`Destination::Network`] with **no consent
/// granted**, AND that the class is at or below the default network ceiling
/// ([`PrivacyClass::P2`]). The second clause deliberately drops P4/P5 even
/// though the guard's consent/identity *exceptions* would let an explicitly
/// consented/identity-bound P4/P5 through — because the live surface cannot
/// honestly assert that consent. Net effect: only **P1/P2** leave the box; P0
/// (raw) and P3/P4/P5 are held edge-local.
///
/// This is the privacy-safety pin for the live surface: a `Derived` cycle maps
/// to P4 (or P5 when identity-bound) via [`map_privacy`] and is therefore
/// **never** surfaced as a network event — neither as a low-privacy P1 (the
/// §3.3 mapping trap) nor at all.
#[must_use]
pub fn network_egress_allowed(class: PrivacyClass, identity_bound: bool) -> bool {
    use rufield_core::PrivacyGuard;
    let guard_allows = matches!(
        DefaultPrivacyGuard::default().authorize(
            class,
            Destination::Network,
            false, // no per-event consent on the live network surface (fail-closed)
            identity_bound,
        ),
        PrivacyDecision::Allow
    );
    // Additionally cap at the default network ceiling: an unattended live
    // surface never asserts the P4-consent / P5-identity exception.
    guard_allows && class <= PrivacyClass::P2
}
