Skip to main content

sim_lib_stream_host/
glasses.rs

1//! Provider-side glasses consent gates for XR stream expressions.
2
3use sim_kernel::{CapabilityName, Cx, Error, Expr, Result, Symbol};
4use sim_lib_view_device::{ConsentReceipt, EdgeId, require_with_consent};
5use sim_value::{access, build};
6
7use crate::{BoundedContentStore, ContentFrame, RetentionWindow, StoreEvicted, StoreKey};
8
9/// Capability required for glasses pose and tracking samples.
10pub const CAP_GLASSES_POSE: &str = "glasses/pose";
11
12/// Capability required for glasses camera frames.
13pub const CAP_GLASSES_CAMERA: &str = "glasses/camera";
14
15/// Capability required for stable world-anchor observations.
16pub const CAP_GLASSES_WORLD_ANCHOR: &str = "glasses/world-anchor";
17
18/// Capability required for glasses hand-ray samples.
19pub const CAP_GLASSES_HAND: &str = "glasses/hand";
20
21/// Capability required for glasses microphone capture.
22pub const CAP_GLASSES_MIC: &str = "glasses/mic";
23
24/// Capability required for vendor diagnostic reporting.
25pub const CAP_GLASSES_VENDOR_REPORT: &str = "glasses/vendor-report";
26
27const XR_NAMESPACE: &str = "xr";
28const DEVICE_SAMPLE_NAMESPACE: &str = "stream/device-sample";
29const GLASSES_NAMESPACE: &str = "glasses";
30
31/// Glasses-sensitive provider capability classes.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum GlassesCapability {
34    /// Pose or spatial tracking samples.
35    Pose,
36    /// Camera frame references.
37    Camera,
38    /// Stable world-anchor observations.
39    WorldAnchor,
40    /// Hand-ray samples.
41    Hand,
42    /// Raw microphone chunk references.
43    Mic,
44    /// Vendor diagnostics, off unless explicitly granted.
45    VendorReport,
46}
47
48impl GlassesCapability {
49    /// All provider-side glasses capabilities.
50    pub const ALL: [Self; 6] = [
51        Self::Pose,
52        Self::Camera,
53        Self::WorldAnchor,
54        Self::Hand,
55        Self::Mic,
56        Self::VendorReport,
57    ];
58
59    /// Stable kernel capability name.
60    pub fn as_str(self) -> &'static str {
61        match self {
62            Self::Pose => CAP_GLASSES_POSE,
63            Self::Camera => CAP_GLASSES_CAMERA,
64            Self::WorldAnchor => CAP_GLASSES_WORLD_ANCHOR,
65            Self::Hand => CAP_GLASSES_HAND,
66            Self::Mic => CAP_GLASSES_MIC,
67            Self::VendorReport => CAP_GLASSES_VENDOR_REPORT,
68        }
69    }
70
71    /// Stable local token after the `glasses/` prefix.
72    pub fn local_name(self) -> &'static str {
73        match self {
74            Self::Pose => "pose",
75            Self::Camera => "camera",
76            Self::WorldAnchor => "world-anchor",
77            Self::Hand => "hand",
78            Self::Mic => "mic",
79            Self::VendorReport => "vendor-report",
80        }
81    }
82
83    /// Kernel capability value.
84    pub fn capability_name(self) -> CapabilityName {
85        CapabilityName::new(self.as_str())
86    }
87
88    /// Visible consent grant symbol carried by a receipt.
89    pub fn grant_symbol(self) -> Symbol {
90        Symbol::qualified(GLASSES_NAMESPACE, self.local_name())
91    }
92
93    /// Resolves a glasses capability name.
94    pub fn from_name(name: &str) -> Option<Self> {
95        Self::ALL
96            .into_iter()
97            .find(|capability| capability.as_str() == name)
98    }
99}
100
101/// Returns the visible grant symbol for glasses pose samples.
102pub fn glasses_pose_grant() -> Symbol {
103    GlassesCapability::Pose.grant_symbol()
104}
105
106/// Returns the visible grant symbol for glasses camera frames.
107pub fn glasses_camera_grant() -> Symbol {
108    GlassesCapability::Camera.grant_symbol()
109}
110
111/// Returns the visible grant symbol for glasses world-anchor observations.
112pub fn glasses_world_anchor_grant() -> Symbol {
113    GlassesCapability::WorldAnchor.grant_symbol()
114}
115
116/// Returns the visible grant symbol for glasses hand-ray samples.
117pub fn glasses_hand_grant() -> Symbol {
118    GlassesCapability::Hand.grant_symbol()
119}
120
121/// Returns the visible grant symbol for glasses microphone capture.
122pub fn glasses_mic_grant() -> Symbol {
123    GlassesCapability::Mic.grant_symbol()
124}
125
126/// Returns the visible grant symbol for glasses vendor diagnostics.
127pub fn glasses_vendor_report_grant() -> Symbol {
128    GlassesCapability::VendorReport.grant_symbol()
129}
130
131/// Classifies an XR stream expression into the glasses capability it needs.
132pub fn glasses_capability_for_sample(sample: &Expr) -> Result<GlassesCapability> {
133    if access::field(sample, "world-anchor").is_some() {
134        return Ok(GlassesCapability::WorldAnchor);
135    }
136    let symbol = access::field_sym(sample, "sample")
137        .or_else(|| access::field_sym(sample, "kind"))
138        .ok_or_else(|| Error::Eval("missing glasses sample or kind field".to_owned()))?;
139    match (symbol.namespace.as_deref(), symbol.name.as_ref()) {
140        (Some(XR_NAMESPACE), "pose") => Ok(GlassesCapability::Pose),
141        (Some(XR_NAMESPACE), "camera-frame") => Ok(GlassesCapability::Camera),
142        (Some(XR_NAMESPACE), "hand") => Ok(GlassesCapability::Hand),
143        (Some(XR_NAMESPACE), "mic-chunk") => Ok(GlassesCapability::Mic),
144        (Some(DEVICE_SAMPLE_NAMESPACE), "xr/pose") => Ok(GlassesCapability::Pose),
145        (Some(DEVICE_SAMPLE_NAMESPACE), "xr/camera-frame") => Ok(GlassesCapability::Camera),
146        (Some(DEVICE_SAMPLE_NAMESPACE), "xr/hand") => Ok(GlassesCapability::Hand),
147        (Some(DEVICE_SAMPLE_NAMESPACE), "xr/mic-chunk") => Ok(GlassesCapability::Mic),
148        (Some(GLASSES_NAMESPACE), "world-anchor") => Ok(GlassesCapability::WorldAnchor),
149        (Some(GLASSES_NAMESPACE), "vendor-report") => Ok(GlassesCapability::VendorReport),
150        _ => Err(Error::HostError(format!(
151            "no glasses consent capability for {}",
152            symbol.as_qualified_str()
153        ))),
154    }
155}
156
157/// Requires kernel authority, visible consent, and same-session receipt ownership.
158pub fn require_glasses_consent(
159    cx: &Cx,
160    capability: GlassesCapability,
161    receipt: &ConsentReceipt,
162    session: &EdgeId,
163) -> Result<()> {
164    require_with_consent(cx, capability.as_str(), receipt, session)
165}
166
167/// Requires the authority needed to ingest one glasses-sensitive stream expression.
168pub fn require_glasses_sample_ingest(
169    cx: &Cx,
170    sample: &Expr,
171    receipt: &ConsentReceipt,
172    session: &EdgeId,
173) -> Result<GlassesCapability> {
174    let capability = glasses_capability_for_sample(sample)?;
175    require_glasses_consent(cx, capability, receipt, session)?;
176    Ok(capability)
177}
178
179/// Stores one by-reference glasses frame in a bounded content store.
180pub fn store_glasses_frame(
181    store: &mut BoundedContentStore,
182    capability: GlassesCapability,
183    ref_id: Symbol,
184    value: Expr,
185    receipt: &ConsentReceipt,
186    inserted_tick: u64,
187    size_bytes: usize,
188) -> Result<(StoreKey, Vec<StoreEvicted>)> {
189    let key = StoreKey::new(ref_id);
190    let evicted = store.insert(ContentFrame::new(
191        key.clone(),
192        receipt.session.as_symbol().clone(),
193        receipt.seq,
194        inserted_tick,
195        size_bytes,
196        stored_glasses_value(capability, value),
197    ))?;
198    Ok((key, evicted))
199}
200
201/// Builds bounded-store retention windows from glasses consent receipts.
202pub fn glasses_retention_windows(receipts: &[ConsentReceipt]) -> Vec<RetentionWindow> {
203    receipts
204        .iter()
205        .filter(|receipt| {
206            receipt
207                .grants
208                .iter()
209                .any(|grant| grant.namespace.as_deref() == Some(GLASSES_NAMESPACE))
210        })
211        .map(|receipt| {
212            RetentionWindow::new(
213                receipt.session.as_symbol().clone(),
214                receipt.seq,
215                receipt.retain_ms,
216            )
217        })
218        .collect()
219}
220
221/// Sweeps stored glasses frames under modeled-clock retention windows.
222pub fn sweep_glasses_retention(
223    store: &mut BoundedContentStore,
224    receipts: &[ConsentReceipt],
225    now_tick: u64,
226    adapt_hz: u16,
227) -> Vec<StoreEvicted> {
228    store.sweep_retention(now_tick, adapt_hz, &glasses_retention_windows(receipts))
229}
230
231fn stored_glasses_value(capability: GlassesCapability, value: Expr) -> Expr {
232    build::map(vec![
233        ("kind", build::qsym("glasses", "content-frame")),
234        ("capability", Expr::Symbol(capability.grant_symbol())),
235        (capability.local_name(), value),
236    ])
237}