Files
ruvnet--RuView/v2/crates/ruview-hal/src/label.rs
T
Claude 49c594822f feat: implement ADR-297 phase-2 world-model core — HAL, ground-truth, tracking, fusion
The layer that turns the certificate spine into a modality-agnostic perception
substrate. Four crates, all deterministic and green independently (43 tests).

ruview-hal (ADR-317): one abstraction mapping any modality (CSI/802.11bf/BLE/
UWB/mmWave/acoustic/camera/lidar/IMU/custom) to a canonical ontology Observation.
SensorHal trait + two SYNTHETIC/L0 reference adapters; malformed input yields a
degraded UNKNOWN observation, never a panic; synthetic can never alias measured.
8 tests.

ruview-groundtruth (ADR-300): reference sensors as a formal VALIDATION plane
(never an estimator input, enforced by the type boundary); modality-agnostic
ReferenceSeries, deterministic cross-correlation alignment, AgreementReport with
mandatory SessionScope, emitting per-context ruview-evidence records; Measured
requires reference + coverage + reproducer. 15 tests.

ruview-track (ADR-304): privacy-preserving persistent tracks (opaque person ids,
coarse non-reversible features, no civil-identity binding); ambiguous detections
stay tentative rather than misassigned; cross-zone hand-off. 8 tests.

ruview-fusion (ADR-308): multiple HalObservations -> one probabilistic WorldState,
uncertainty-aware (confidence-weighted, not naive averaging); irreconcilable
conflict or insufficient coverage yields UNKNOWN, not a confident average.
9+ tests incl. irreconcilable_conflict_yields_unknown.

Flips ADR-300/304/308/317 to implemented; registers the four crates as workspace
members. SYNTHETIC/L0 throughout; no hardware/MEASURED claims.

Co-Authored-By: claude-flow <ruv@ruv.net>
Claude-Session: https://claude.ai/code/session_015TcKegTS7QqhWPC2L2SzaS
2026-08-11 03:16:33 +00:00

83 lines
2.7 KiB
Rust

//! Bounded-string validation shared by the HAL's boundary types.
//!
//! Capability tags and `Modality::Custom` payloads arrive from potentially
//! untrusted hardware descriptors. They are validated at construction with the
//! same discipline the ontology applies to ids: non-empty, length-bounded, and
//! free of ASCII control characters (CLAUDE.md: validate untrusted input at
//! every boundary; bound allocation).
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// Maximum accepted label length, in bytes. Bounds allocation on untrusted
/// input.
pub const MAX_LABEL_LEN: usize = 128;
/// Reasons a raw label string is rejected at the boundary.
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum LabelError {
/// The label was empty.
#[error("label must not be empty")]
Empty,
/// The label exceeded [`MAX_LABEL_LEN`] bytes.
#[error("label length {len} exceeds maximum {max}")]
TooLong {
/// Actual length in bytes.
len: usize,
/// The enforced maximum.
max: usize,
},
/// The label contained an ASCII control character.
#[error("label contains a control character at byte {pos}")]
ControlChar {
/// Byte offset of the offending control character.
pos: usize,
},
}
/// Validate a raw label: non-empty, bounded length, no control characters.
pub(crate) fn validate_label(raw: &str) -> Result<(), LabelError> {
if raw.is_empty() {
return Err(LabelError::Empty);
}
if raw.len() > MAX_LABEL_LEN {
return Err(LabelError::TooLong {
len: raw.len(),
max: MAX_LABEL_LEN,
});
}
if let Some(pos) = raw.bytes().position(|b| b.is_ascii_control()) {
return Err(LabelError::ControlChar { pos });
}
Ok(())
}
/// A validated, bounded capability tag describing one phenomenon a sensor can
/// observe (e.g. `"amplitude"`, `"range"`, `"accel"`). Reuses the ontology's
/// id-style validation discipline rather than accepting a raw `String`.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct CapabilityTag(String);
impl CapabilityTag {
/// Construct a validated tag, rejecting empty, over-long, or
/// control-character input at the boundary.
pub fn new(raw: impl Into<String>) -> Result<Self, LabelError> {
let s = raw.into();
validate_label(&s)?;
Ok(Self(s))
}
/// Borrow the underlying tag string.
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl core::fmt::Display for CapabilityTag {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.0)
}
}