Skip to main content

hara_native/instrumentation/
native.rs

1use std::cell::RefCell;
2use std::collections::BTreeSet;
3use std::error::Error;
4use std::fmt;
5use std::rc::{Rc, Weak};
6
7use super::{
8    Capability, ControlLease, EventBatch, EventDelivery, InstrumentDirective, InstrumentHandle,
9    InstrumentMode, InstrumentRegistration, InstrumentationAttachment, InstrumentationError,
10    InstrumentationHub, RuntimeBackend, TargetDescriptor, TargetHandle,
11};
12
13/// Embedding-only access to the instrumentation hub owned by one live Runtime.
14///
15/// The service retains only a weak Runtime identity. It is never a Hara value,
16/// is not installed in a namespace, and cannot keep a Runtime or Session alive.
17#[derive(Clone)]
18pub struct NativeInstrumentation {
19    session_id: String,
20    hub: Weak<RefCell<InstrumentationHub>>,
21}
22
23/// Opaque, generation-fenced identity for one trusted instrument registration.
24#[derive(Clone)]
25pub struct NativeInstrumentHandle {
26    session_id: String,
27    hub: Weak<RefCell<InstrumentationHub>>,
28    handle: InstrumentHandle,
29}
30
31/// Opaque, generation-fenced identity for one authoritative execution target.
32#[derive(Clone)]
33pub struct NativeTargetHandle {
34    session_id: String,
35    hub: Weak<RefCell<InstrumentationHub>>,
36    handle: TargetHandle,
37}
38
39/// Opaque proof that one trusted controller owns one target's exclusive lease.
40#[derive(Clone)]
41pub struct NativeControlLease {
42    session_id: String,
43    hub: Weak<RefCell<InstrumentationHub>>,
44    lease: ControlLease,
45}
46
47/// Bounded metadata about one successful native attachment. The native handles
48/// remain opaque and cannot be converted into a Hara value.
49#[derive(Clone)]
50pub struct NativeAttachment {
51    instrument: NativeInstrumentHandle,
52    target: NativeTargetHandle,
53    granted_capabilities: BTreeSet<Capability>,
54    registration_order: u64,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub enum NativeInstrumentationError {
59    RuntimeClosed {
60        session_id: String,
61    },
62    UnknownSession(String),
63    SessionClosed(String),
64    CrossRuntimeHandle {
65        kind: &'static str,
66    },
67    CrossSessionHandle {
68        kind: &'static str,
69        expected: String,
70        actual: String,
71    },
72    UnsupportedMode(InstrumentMode),
73    UnsupportedDelivery(&'static str),
74    UnsupportedCapabilities {
75        target_id: String,
76        backend: RuntimeBackend,
77        requested: BTreeSet<Capability>,
78        potential: BTreeSet<Capability>,
79        missing: BTreeSet<Capability>,
80    },
81    Hub(InstrumentationError),
82}
83
84impl fmt::Display for NativeInstrumentationError {
85    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86        match self {
87            Self::RuntimeClosed { session_id } => write!(
88                formatter,
89                "instrumentation/native-runtime-closed: session {session_id}"
90            ),
91            Self::UnknownSession(session_id) => {
92                write!(formatter, "instrumentation/native-unknown-session: {session_id}")
93            }
94            Self::SessionClosed(session_id) => {
95                write!(formatter, "instrumentation/native-session-closed: {session_id}")
96            }
97            Self::CrossRuntimeHandle { kind } => {
98                write!(formatter, "instrumentation/native-cross-runtime-handle: {kind}")
99            }
100            Self::CrossSessionHandle {
101                kind,
102                expected,
103                actual,
104            } => write!(
105                formatter,
106                "instrumentation/native-cross-session-handle: {kind}, expected {expected}, actual {actual}"
107            ),
108            Self::UnsupportedMode(mode) => write!(
109                formatter,
110                "instrumentation/native-unsupported-mode: {mode:?}"
111            ),
112            Self::UnsupportedDelivery(delivery) => write!(
113                formatter,
114                "instrumentation/native-unsupported-delivery: {delivery}"
115            ),
116            Self::UnsupportedCapabilities {
117                target_id,
118                backend,
119                requested,
120                potential,
121                missing,
122            } => write!(
123                formatter,
124                "instrumentation/native-unsupported-capabilities: target {target_id}, backend {}, requested {requested:?}, potential {potential:?}, missing {missing:?}",
125                backend.as_str()
126            ),
127            Self::Hub(error) => fmt::Display::fmt(error, formatter),
128        }
129    }
130}
131
132impl Error for NativeInstrumentationError {}
133
134impl From<InstrumentationError> for NativeInstrumentationError {
135    fn from(error: InstrumentationError) -> Self {
136        Self::Hub(error)
137    }
138}
139
140impl fmt::Debug for NativeInstrumentation {
141    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142        formatter
143            .debug_struct("NativeInstrumentation")
144            .field("session_id", &self.session_id)
145            .field("active", &self.is_active())
146            .finish_non_exhaustive()
147    }
148}
149
150impl fmt::Debug for NativeInstrumentHandle {
151    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
152        formatter
153            .debug_struct("NativeInstrumentHandle")
154            .field("session_id", &self.session_id)
155            .field("instrument_id", &self.handle.instrument_id())
156            .field("generation", &self.handle.generation())
157            .finish_non_exhaustive()
158    }
159}
160
161impl fmt::Debug for NativeTargetHandle {
162    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163        formatter
164            .debug_struct("NativeTargetHandle")
165            .field("session_id", &self.session_id)
166            .field("target_id", &self.handle.target_id())
167            .field("generation", &self.handle.generation())
168            .finish_non_exhaustive()
169    }
170}
171
172impl fmt::Debug for NativeControlLease {
173    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
174        formatter
175            .debug_struct("NativeControlLease")
176            .field("session_id", &self.session_id)
177            .field("instrument_id", &self.lease.instrument().instrument_id())
178            .field("target_id", &self.lease.target().target_id())
179            .finish_non_exhaustive()
180    }
181}
182
183impl fmt::Debug for NativeAttachment {
184    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
185        formatter
186            .debug_struct("NativeAttachment")
187            .field("instrument", &self.instrument)
188            .field("target", &self.target)
189            .field("granted_capabilities", &self.granted_capabilities)
190            .field("registration_order", &self.registration_order)
191            .finish()
192    }
193}
194
195impl NativeInstrumentation {
196    pub(crate) fn new(session_id: impl Into<String>, hub: Rc<RefCell<InstrumentationHub>>) -> Self {
197        Self {
198            session_id: session_id.into(),
199            hub: Rc::downgrade(&hub),
200        }
201    }
202
203    pub fn session_id(&self) -> &str {
204        &self.session_id
205    }
206
207    pub fn is_active(&self) -> bool {
208        self.hub.upgrade().is_some()
209    }
210
211    /// Registers one trusted queued instrument. Transform mode and callback
212    /// binding are intentionally rejected until their later lifecycle seams are
213    /// implemented; they never degrade silently to another mode or delivery.
214    pub fn register(
215        &self,
216        registration: InstrumentRegistration,
217    ) -> Result<NativeInstrumentHandle, NativeInstrumentationError> {
218        if registration.session_id != self.session_id {
219            return Err(self.cross_session("registration", &registration.session_id));
220        }
221        if registration.mode == InstrumentMode::Transform {
222            return Err(NativeInstrumentationError::UnsupportedMode(
223                InstrumentMode::Transform,
224            ));
225        }
226        if registration.delivery == EventDelivery::Callback {
227            return Err(NativeInstrumentationError::UnsupportedDelivery(
228                "callback binding is not available in this tranche",
229            ));
230        }
231        let hub = self.hub()?;
232        let handle = hub.borrow_mut().register(registration)?;
233        Ok(self.instrument_handle(handle))
234    }
235
236    /// Fences a target identity received from a trusted target-producing host
237    /// seam. Target implementation objects remain private to Runtime/Session.
238    pub fn bind_target(
239        &self,
240        target: &TargetHandle,
241    ) -> Result<NativeTargetHandle, NativeInstrumentationError> {
242        let hub = self.hub()?;
243        let descriptor = {
244            let hub = hub.borrow();
245            hub.target_descriptor(target)?.clone()
246        };
247        if descriptor.session_id != self.session_id {
248            return Err(self.cross_session("target", &descriptor.session_id));
249        }
250        Ok(self.target_handle(target.clone()))
251    }
252
253    pub fn registration(
254        &self,
255        instrument: &NativeInstrumentHandle,
256    ) -> Result<InstrumentRegistration, NativeInstrumentationError> {
257        self.ensure_instrument(instrument)?;
258        let hub = self.hub()?;
259        let registration = {
260            let hub = hub.borrow();
261            hub.instrument_registration(&instrument.handle)?.clone()
262        };
263        Ok(registration)
264    }
265
266    pub fn target_descriptor(
267        &self,
268        target: &NativeTargetHandle,
269    ) -> Result<TargetDescriptor, NativeInstrumentationError> {
270        self.ensure_target(target)?;
271        let hub = self.hub()?;
272        let descriptor = {
273            let hub = hub.borrow();
274            hub.target_descriptor(&target.handle)?.clone()
275        };
276        Ok(descriptor)
277    }
278
279    pub fn attach(
280        &self,
281        instrument: &NativeInstrumentHandle,
282        target: &NativeTargetHandle,
283    ) -> Result<NativeAttachment, NativeInstrumentationError> {
284        self.ensure_instrument(instrument)?;
285        self.ensure_target(target)?;
286        let hub = self.hub()?;
287        let (requested, potential) = {
288            let hub = hub.borrow();
289            (
290                hub.instrument_registration(&instrument.handle)?
291                    .capabilities
292                    .clone(),
293                hub.target_descriptor(&target.handle)?.capabilities.clone(),
294            )
295        };
296        let attachment = match hub.borrow_mut().attach(&instrument.handle, &target.handle) {
297            Ok(attachment) => attachment,
298            Err(InstrumentationError::UnsupportedCapabilities {
299                target_id,
300                backend,
301                missing,
302            }) => {
303                return Err(NativeInstrumentationError::UnsupportedCapabilities {
304                    target_id,
305                    backend,
306                    requested,
307                    potential,
308                    missing,
309                });
310            }
311            Err(error) => return Err(error.into()),
312        };
313        Ok(self.attachment(attachment))
314    }
315
316    pub fn granted_capabilities(
317        &self,
318        instrument: &NativeInstrumentHandle,
319        target: &NativeTargetHandle,
320    ) -> Result<BTreeSet<Capability>, NativeInstrumentationError> {
321        self.ensure_instrument(instrument)?;
322        self.ensure_target(target)?;
323        let hub = self.hub()?;
324        let capabilities = {
325            let hub = hub.borrow();
326            hub.attachments_for_target(&target.handle)?
327                .into_iter()
328                .find(|attachment| attachment.instrument == instrument.handle)
329                .map(|attachment| attachment.granted_capabilities.clone())
330                .ok_or_else(|| InstrumentationError::AttachmentRequired {
331                    instrument_id: instrument.instrument_id().into(),
332                    target_id: target.target_id().into(),
333                })?
334        };
335        Ok(capabilities)
336    }
337
338    pub fn queued_event_count(
339        &self,
340        instrument: &NativeInstrumentHandle,
341    ) -> Result<usize, NativeInstrumentationError> {
342        self.ensure_instrument(instrument)?;
343        let hub = self.hub()?;
344        let count = hub.borrow().queued_event_count(&instrument.handle)?;
345        Ok(count)
346    }
347
348    pub fn drain_events(
349        &self,
350        instrument: &NativeInstrumentHandle,
351    ) -> Result<EventBatch, NativeInstrumentationError> {
352        self.ensure_instrument(instrument)?;
353        let hub = self.hub()?;
354        let batch = hub.borrow_mut().drain_events(&instrument.handle)?;
355        Ok(batch)
356    }
357
358    pub fn detach(
359        &self,
360        instrument: &NativeInstrumentHandle,
361    ) -> Result<(), NativeInstrumentationError> {
362        self.ensure_instrument(instrument)?;
363        let hub = self.hub()?;
364        hub.borrow_mut().detach(&instrument.handle)?;
365        Ok(())
366    }
367
368    pub fn acquire_control(
369        &self,
370        instrument: &NativeInstrumentHandle,
371        target: &NativeTargetHandle,
372    ) -> Result<NativeControlLease, NativeInstrumentationError> {
373        self.ensure_instrument(instrument)?;
374        self.ensure_target(target)?;
375        let hub = self.hub()?;
376        let lease = hub
377            .borrow_mut()
378            .acquire_control(&instrument.handle, &target.handle)?;
379        Ok(NativeControlLease {
380            session_id: self.session_id.clone(),
381            hub: self.hub.clone(),
382            lease,
383        })
384    }
385
386    pub fn release_control(
387        &self,
388        lease: &NativeControlLease,
389    ) -> Result<(), NativeInstrumentationError> {
390        self.ensure_lease(lease)?;
391        let hub = self.hub()?;
392        hub.borrow_mut().release_control(&lease.lease)?;
393        Ok(())
394    }
395
396    pub fn request_directive(
397        &self,
398        lease: &NativeControlLease,
399        directive: InstrumentDirective,
400    ) -> Result<(), NativeInstrumentationError> {
401        self.ensure_lease(lease)?;
402        let hub = self.hub()?;
403        hub.borrow_mut()
404            .request_directive(&lease.lease, directive)?;
405        Ok(())
406    }
407
408    fn hub(&self) -> Result<Rc<RefCell<InstrumentationHub>>, NativeInstrumentationError> {
409        self.hub
410            .upgrade()
411            .ok_or_else(|| NativeInstrumentationError::RuntimeClosed {
412                session_id: self.session_id.clone(),
413            })
414    }
415
416    fn ensure_instrument(
417        &self,
418        instrument: &NativeInstrumentHandle,
419    ) -> Result<(), NativeInstrumentationError> {
420        self.ensure_owner("instrument", &instrument.session_id, &instrument.hub)
421    }
422
423    fn ensure_target(&self, target: &NativeTargetHandle) -> Result<(), NativeInstrumentationError> {
424        self.ensure_owner("target", &target.session_id, &target.hub)
425    }
426
427    fn ensure_lease(&self, lease: &NativeControlLease) -> Result<(), NativeInstrumentationError> {
428        self.ensure_owner("control lease", &lease.session_id, &lease.hub)
429    }
430
431    fn ensure_owner(
432        &self,
433        kind: &'static str,
434        session_id: &str,
435        hub: &Weak<RefCell<InstrumentationHub>>,
436    ) -> Result<(), NativeInstrumentationError> {
437        if !Weak::ptr_eq(&self.hub, hub) {
438            return Err(NativeInstrumentationError::CrossRuntimeHandle { kind });
439        }
440        if session_id != self.session_id {
441            return Err(self.cross_session(kind, session_id));
442        }
443        self.hub()?;
444        Ok(())
445    }
446
447    fn cross_session(&self, kind: &'static str, actual: &str) -> NativeInstrumentationError {
448        NativeInstrumentationError::CrossSessionHandle {
449            kind,
450            expected: self.session_id.clone(),
451            actual: actual.into(),
452        }
453    }
454
455    fn instrument_handle(&self, handle: InstrumentHandle) -> NativeInstrumentHandle {
456        NativeInstrumentHandle {
457            session_id: self.session_id.clone(),
458            hub: self.hub.clone(),
459            handle,
460        }
461    }
462
463    fn target_handle(&self, handle: TargetHandle) -> NativeTargetHandle {
464        NativeTargetHandle {
465            session_id: self.session_id.clone(),
466            hub: self.hub.clone(),
467            handle,
468        }
469    }
470
471    fn attachment(&self, attachment: InstrumentationAttachment) -> NativeAttachment {
472        NativeAttachment {
473            instrument: self.instrument_handle(attachment.instrument),
474            target: self.target_handle(attachment.target),
475            granted_capabilities: attachment.granted_capabilities,
476            registration_order: attachment.registration_order,
477        }
478    }
479}
480
481impl NativeInstrumentHandle {
482    pub fn instrument_id(&self) -> &str {
483        self.handle.instrument_id()
484    }
485
486    pub fn generation(&self) -> u64 {
487        self.handle.generation()
488    }
489}
490
491impl NativeTargetHandle {
492    pub fn target_id(&self) -> &str {
493        self.handle.target_id()
494    }
495
496    pub fn generation(&self) -> u64 {
497        self.handle.generation()
498    }
499}
500
501impl NativeControlLease {
502    pub fn instrument_id(&self) -> &str {
503        self.lease.instrument().instrument_id()
504    }
505
506    pub fn target_id(&self) -> &str {
507        self.lease.target().target_id()
508    }
509}
510
511impl NativeAttachment {
512    pub fn instrument(&self) -> &NativeInstrumentHandle {
513        &self.instrument
514    }
515
516    pub fn target(&self) -> &NativeTargetHandle {
517        &self.target
518    }
519
520    pub fn granted_capabilities(&self) -> &BTreeSet<Capability> {
521        &self.granted_capabilities
522    }
523
524    pub fn registration_order(&self) -> u64 {
525        self.registration_order
526    }
527}
528
529#[cfg(test)]
530#[path = "native/tests.rs"]
531mod tests;