Skip to main content

ferrum_interfaces/vnext/event/
sink.rs

1use std::error::Error;
2use std::fmt;
3use std::sync::Arc;
4
5use crate::model_executor::ExecutorRequestOrigin;
6
7use super::{
8    has_active, BoundExecutionResourceMaintenance, ExecutionEvent, ExecutionEventCursor,
9    ExecutionEventKind, RequestIdentity, RunId, TrustedExecutionEventContext,
10};
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ExecutionEventSinkError {
14    message: String,
15}
16
17impl ExecutionEventSinkError {
18    pub fn new(message: impl Into<String>) -> Self {
19        Self {
20            message: message.into(),
21        }
22    }
23
24    pub fn message(&self) -> &str {
25        &self.message
26    }
27}
28
29impl fmt::Display for ExecutionEventSinkError {
30    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31        formatter.write_str(&self.message)
32    }
33}
34
35impl Error for ExecutionEventSinkError {}
36
37mod event_sink_seal {
38    pub struct Seal;
39}
40
41/// Owned capability created only after the emitter has validated the event
42/// against its transactional cursor. Ownership lets asynchronous sinks defer
43/// materialization without cloning the event or extending producer lifetimes.
44pub struct EventEmissionPermit {
45    event: ExecutionEvent,
46    _seal: event_sink_seal::Seal,
47}
48
49impl EventEmissionPermit {
50    pub fn event(&self) -> &ExecutionEvent {
51        &self.event
52    }
53
54    pub fn into_event(self) -> ExecutionEvent {
55        self.event
56    }
57}
58
59/// Owned capability created only after the emitter has validated an ordered
60/// event batch against one transactional cursor.
61pub struct EventBatchEmissionPermit {
62    events: Vec<ExecutionEvent>,
63    _seal: event_sink_seal::Seal,
64}
65
66impl EventBatchEmissionPermit {
67    pub fn events(&self) -> &[ExecutionEvent] {
68        &self.events
69    }
70
71    pub fn into_events(self) -> Vec<ExecutionEvent> {
72        self.events
73    }
74}
75
76#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
77pub enum ExecutionEventCapturePolicy {
78    #[default]
79    AllFrames,
80    FirstFramePerRequest,
81    LifecycleOnly,
82}
83
84impl ExecutionEventCapturePolicy {
85    pub const fn captures_frame(self, completed_frames: u64) -> bool {
86        match self {
87            Self::AllFrames => true,
88            Self::FirstFramePerRequest | Self::LifecycleOnly => completed_frames == 0,
89        }
90    }
91
92    pub const fn records_event(self, kind: ExecutionEventKind) -> bool {
93        match self {
94            Self::AllFrames | Self::FirstFramePerRequest => true,
95            Self::LifecycleOnly => matches!(
96                kind,
97                ExecutionEventKind::RequestAccepted
98                    | ExecutionEventKind::PlanBuilt
99                    | ExecutionEventKind::FailureObserved
100                    | ExecutionEventKind::SequenceCompleted
101                    | ExecutionEventKind::SequenceAborted
102                    | ExecutionEventKind::RequestCompleted
103                    | ExecutionEventKind::RequestFailed
104            ),
105        }
106    }
107
108    pub const fn as_str(self) -> &'static str {
109        match self {
110            Self::AllFrames => "all_frames",
111            Self::FirstFramePerRequest => "first_frame_per_request",
112            Self::LifecycleOnly => "lifecycle_only",
113        }
114    }
115}
116
117#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
118pub enum ExecutionEventSinkEnablement {
119    None,
120    All,
121    #[default]
122    PerKind,
123}
124
125pub trait ExecutionEventSink: Send + Sync {
126    /// Resolves stable event enablement once when an emitter is constructed.
127    ///
128    /// Dynamically filtered sinks keep the conservative `PerKind` default.
129    /// Permanently disabled and all-event sinks select `None` or `All` so the
130    /// event hot path does not perform a virtual enablement query.
131    fn enablement(&self) -> ExecutionEventSinkEnablement {
132        ExecutionEventSinkEnablement::PerKind
133    }
134
135    fn is_enabled(&self, kind: ExecutionEventKind) -> bool;
136
137    fn device_timing_mode(&self) -> super::super::DeviceTimingMode {
138        super::super::DeviceTimingMode::Off
139    }
140
141    fn capture_policy(&self) -> ExecutionEventCapturePolicy {
142        ExecutionEventCapturePolicy::AllFrames
143    }
144
145    fn capture_policy_for_request(
146        &self,
147        _origin: ExecutorRequestOrigin,
148    ) -> ExecutionEventCapturePolicy {
149        self.capture_policy()
150    }
151
152    fn record_device_submission_attribution(
153        &self,
154        _attribution: &super::super::BoundDeviceSubmissionAttribution,
155    ) -> Result<(), ExecutionEventSinkError> {
156        Ok(())
157    }
158
159    fn record_physical_device_submission_timing(
160        &self,
161        _completion: &super::super::OperationCompletionReceipt,
162    ) -> Result<(), ExecutionEventSinkError> {
163        Ok(())
164    }
165
166    /// Resource maintenance is a plan/batch event and therefore does not run
167    /// through a per-request execution cursor. Sinks opt in explicitly so a
168    /// disabled profile path performs no event construction or serialization.
169    fn records_execution_resource_maintenance(&self) -> bool {
170        false
171    }
172
173    fn record_execution_resource_maintenance(
174        &self,
175        _maintenance: BoundExecutionResourceMaintenance,
176    ) -> Result<(), ExecutionEventSinkError> {
177        Ok(())
178    }
179
180    fn record(&self, permit: EventEmissionPermit) -> Result<(), ExecutionEventSinkError>;
181
182    /// Records one cursor-ordered batch. Sinks with a buffered transport should
183    /// override this boundary; the default preserves compatibility and order.
184    fn record_batch(
185        &self,
186        permit: EventBatchEmissionPermit,
187    ) -> Result<(), ExecutionEventSinkError> {
188        for event in permit.into_events() {
189            self.record(EventEmissionPermit {
190                event,
191                _seal: event_sink_seal::Seal,
192            })?;
193        }
194        Ok(())
195    }
196}
197
198enum ExecutionEventSinkHandle<'sink, S>
199where
200    S: ExecutionEventSink + ?Sized,
201{
202    Borrowed(&'sink S),
203    Shared(Arc<S>),
204}
205
206impl<S> ExecutionEventSinkHandle<'_, S>
207where
208    S: ExecutionEventSink + ?Sized,
209{
210    #[inline]
211    fn as_sink(&self) -> &S {
212        match self {
213            Self::Borrowed(sink) => *sink,
214            Self::Shared(sink) => sink.as_ref(),
215        }
216    }
217}
218
219pub struct ExecutionEventEmitter<'sink, S = dyn ExecutionEventSink>
220where
221    S: ExecutionEventSink + ?Sized,
222{
223    sink: ExecutionEventSinkHandle<'sink, S>,
224    cursor: ExecutionEventCursor,
225    capture_policy: ExecutionEventCapturePolicy,
226    sink_enablement: ExecutionEventSinkEnablement,
227    sink_failed: bool,
228}
229
230impl<'sink, S> ExecutionEventEmitter<'sink, S>
231where
232    S: ExecutionEventSink + ?Sized,
233{
234    #[inline]
235    pub fn new(sink: &'sink S, run_id: RunId, request_id: RequestIdentity) -> Self {
236        let sink_enablement = sink.enablement();
237        let capture_policy = if sink_enablement == ExecutionEventSinkEnablement::None {
238            ExecutionEventCapturePolicy::AllFrames
239        } else {
240            sink.capture_policy()
241        };
242        Self {
243            sink: ExecutionEventSinkHandle::Borrowed(sink),
244            cursor: ExecutionEventCursor::new(run_id, request_id),
245            capture_policy,
246            sink_enablement,
247            sink_failed: false,
248        }
249    }
250}
251
252impl ExecutionEventEmitter<'static, dyn ExecutionEventSink> {
253    /// Creates a durable emitter that may be owned by a request/session.
254    ///
255    /// The borrowed constructor remains useful for bounded validation. Product
256    /// runtimes use this form so event authority cannot outlive its sink.
257    pub fn from_shared(
258        sink: Arc<dyn ExecutionEventSink>,
259        run_id: RunId,
260        request_id: RequestIdentity,
261    ) -> ExecutionEventEmitter<'static> {
262        let sink_enablement = sink.enablement();
263        let capture_policy = if sink_enablement == ExecutionEventSinkEnablement::None {
264            ExecutionEventCapturePolicy::AllFrames
265        } else {
266            sink.capture_policy()
267        };
268        Self::from_shared_parts(sink, run_id, request_id, capture_policy, sink_enablement)
269    }
270
271    pub fn from_shared_with_capture_policy(
272        sink: Arc<dyn ExecutionEventSink>,
273        run_id: RunId,
274        request_id: RequestIdentity,
275        capture_policy: ExecutionEventCapturePolicy,
276    ) -> ExecutionEventEmitter<'static> {
277        let sink_enablement = sink.enablement();
278        Self::from_shared_parts(sink, run_id, request_id, capture_policy, sink_enablement)
279    }
280
281    fn from_shared_parts(
282        sink: Arc<dyn ExecutionEventSink>,
283        run_id: RunId,
284        request_id: RequestIdentity,
285        capture_policy: ExecutionEventCapturePolicy,
286        sink_enablement: ExecutionEventSinkEnablement,
287    ) -> ExecutionEventEmitter<'static> {
288        ExecutionEventEmitter {
289            sink: ExecutionEventSinkHandle::Shared(sink),
290            cursor: ExecutionEventCursor::new(run_id, request_id),
291            capture_policy,
292            sink_enablement,
293            sink_failed: false,
294        }
295    }
296}
297
298impl<'sink, S> ExecutionEventEmitter<'sink, S>
299where
300    S: ExecutionEventSink + ?Sized,
301{
302    #[inline]
303    fn records_event(&self, event: &ExecutionEvent) -> bool {
304        self.capture_policy.records_event(event.kind())
305            && match self.sink_enablement {
306                ExecutionEventSinkEnablement::None => false,
307                ExecutionEventSinkEnablement::All => true,
308                ExecutionEventSinkEnablement::PerKind => {
309                    self.sink.as_sink().is_enabled(event.kind())
310                }
311            }
312    }
313
314    #[inline]
315    fn validate_next(
316        cursor: &mut ExecutionEventCursor,
317        event: &ExecutionEvent,
318        context: &TrustedExecutionEventContext<'_>,
319    ) -> Result<(), ExecutionEventSinkError> {
320        match event.kind() {
321            ExecutionEventKind::FrameStarted | ExecutionEventKind::NodeStarted => context
322                .active_binding()
323                .ok_or_else(|| {
324                    ExecutionEventSinkError::new(
325                        "active execution emission lacks live sequence evidence",
326                    )
327                })?
328                .ensure_open_for_emission()
329                .map_err(|error| ExecutionEventSinkError::new(error.to_string()))?,
330            ExecutionEventKind::OperationSubmitted
331            | ExecutionEventKind::NodeRetired
332            | ExecutionEventKind::FrameCompleted => context
333                .active_binding()
334                .ok_or_else(|| {
335                    ExecutionEventSinkError::new(
336                        "active execution emission lacks live sequence evidence",
337                    )
338                })?
339                .ensure_live_for_emission()
340                .map_err(|error| ExecutionEventSinkError::new(error.to_string()))?,
341            ExecutionEventKind::FailureObserved if has_active(event.identity().parts()) => context
342                .active_binding()
343                .ok_or_else(|| {
344                    ExecutionEventSinkError::new(
345                        "active execution emission lacks live sequence evidence",
346                    )
347                })?
348                .ensure_live_for_emission()
349                .map_err(|error| ExecutionEventSinkError::new(error.to_string()))?,
350            _ => {}
351        }
352        cursor
353            .observe_candidate_in_place(event, context)
354            .map_err(|error| ExecutionEventSinkError::new(error.to_string()))
355    }
356
357    #[inline]
358    pub fn emit(
359        &mut self,
360        event: ExecutionEvent,
361        context: &TrustedExecutionEventContext<'_>,
362    ) -> Result<(), ExecutionEventSinkError> {
363        if self.sink_enablement == ExecutionEventSinkEnablement::None {
364            let mut next_cursor = self.cursor.clone();
365            Self::validate_next(&mut next_cursor, &event, context)?;
366            self.cursor = next_cursor;
367            return Ok(());
368        }
369        if self.sink_failed {
370            return Err(ExecutionEventSinkError::new(
371                "execution event emitter is sealed after a sink failure",
372            ));
373        }
374        let mut next_cursor = self.cursor.clone();
375        Self::validate_next(&mut next_cursor, &event, context)?;
376        if self.records_event(&event) {
377            let permit = EventEmissionPermit {
378                event,
379                _seal: event_sink_seal::Seal,
380            };
381            if let Err(error) = self.sink.as_sink().record(permit) {
382                self.sink_failed = true;
383                return Err(error);
384            }
385        }
386        self.cursor = next_cursor;
387        Ok(())
388    }
389
390    pub fn emit_batch(
391        &mut self,
392        events: Vec<ExecutionEvent>,
393        contexts: &[TrustedExecutionEventContext<'_>],
394    ) -> Result<(), ExecutionEventSinkError> {
395        if self.sink_enablement == ExecutionEventSinkEnablement::None {
396            if events.len() != contexts.len() {
397                return Err(ExecutionEventSinkError::new(
398                    "execution event batch context count differs from event count",
399                ));
400            }
401            if events.is_empty() {
402                return Ok(());
403            }
404            let mut next_cursor = self.cursor.clone();
405            for (event, context) in events.iter().zip(contexts) {
406                Self::validate_next(&mut next_cursor, event, context)?;
407            }
408            self.cursor = next_cursor;
409            return Ok(());
410        }
411        if self.sink_failed {
412            return Err(ExecutionEventSinkError::new(
413                "execution event emitter is sealed after a sink failure",
414            ));
415        }
416        if events.len() != contexts.len() {
417            return Err(ExecutionEventSinkError::new(
418                "execution event batch context count differs from event count",
419            ));
420        }
421        if events.is_empty() {
422            return Ok(());
423        }
424
425        let mut next_cursor = self.cursor.clone();
426        for (event, context) in events.iter().zip(contexts) {
427            Self::validate_next(&mut next_cursor, event, context)?;
428        }
429
430        let all_enabled = events.iter().all(|event| self.records_event(event));
431        if all_enabled {
432            let permit = EventBatchEmissionPermit {
433                events,
434                _seal: event_sink_seal::Seal,
435            };
436            if let Err(error) = self.sink.as_sink().record_batch(permit) {
437                self.sink_failed = true;
438                return Err(error);
439            }
440        } else {
441            for event in events {
442                if !self.records_event(&event) {
443                    continue;
444                }
445                let permit = EventEmissionPermit {
446                    event,
447                    _seal: event_sink_seal::Seal,
448                };
449                if let Err(error) = self.sink.as_sink().record(permit) {
450                    self.sink_failed = true;
451                    return Err(error);
452                }
453            }
454        }
455        self.cursor = next_cursor;
456        Ok(())
457    }
458
459    pub fn cursor(&self) -> &ExecutionEventCursor {
460        &self.cursor
461    }
462
463    pub const fn sink_failed(&self) -> bool {
464        self.sink_failed
465    }
466}
467
468#[derive(Debug, Default)]
469pub struct DisabledExecutionEventSink;
470
471impl ExecutionEventSink for DisabledExecutionEventSink {
472    fn enablement(&self) -> ExecutionEventSinkEnablement {
473        ExecutionEventSinkEnablement::None
474    }
475
476    fn is_enabled(&self, _kind: ExecutionEventKind) -> bool {
477        false
478    }
479
480    fn record(&self, _permit: EventEmissionPermit) -> Result<(), ExecutionEventSinkError> {
481        Ok(())
482    }
483}