Skip to main content

af_workflow/
recorder.rs

1//! Infra-agnostic workflow run and step persistence seam.
2
3use std::sync::Arc;
4
5use async_trait::async_trait;
6use serde_json::Value;
7
8/// Recorded status of a step.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum StepStatus {
11    /// Succeeded.
12    Ok,
13    /// Filtered or skipped.
14    Skipped,
15    /// Failed.
16    Error,
17}
18
19impl StepStatus {
20    /// Stable lowercase name.
21    pub fn as_str(self) -> &'static str {
22        match self {
23            Self::Ok => "ok",
24            Self::Skipped => "skipped",
25            Self::Error => "error",
26        }
27    }
28}
29
30/// Recorded status of a run.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum RunStatus {
33    /// Succeeded.
34    Ok,
35    /// Filtered or skipped.
36    Skipped,
37    /// Failed.
38    Error,
39}
40
41impl RunStatus {
42    /// Stable lowercase name.
43    pub fn as_str(self) -> &'static str {
44        match self {
45            Self::Ok => "ok",
46            Self::Skipped => "skipped",
47            Self::Error => "error",
48        }
49    }
50}
51
52/// Open run being recorded.
53#[async_trait]
54pub trait RunHandle: Send {
55    /// Record one step outcome.
56    async fn record_step(
57        &mut self,
58        node_id: &str,
59        node_type: &str,
60        status: StepStatus,
61        exit_reason: Option<&str>,
62        detail: Value,
63    );
64
65    /// Record routine negative evidence without materializing a full run.
66    async fn mark_filtered(&mut self, node_id: &str, node_type: &str, reason: &str);
67
68    /// Close the run.
69    async fn end(self: Box<Self>, status: RunStatus, exit_reason: Option<&str>);
70}
71
72/// Sink for run and step facts (audit, UI, metrics).
73#[async_trait]
74pub trait RunRecorder: Send + Sync {
75    /// Open a run for a trigger.
76    async fn start(&self, trigger_kind: &str, trigger: Value) -> Box<dyn RunHandle>;
77}
78
79/// Recorder that discards everything.
80pub struct NoopRecorder;
81
82#[async_trait]
83impl RunRecorder for NoopRecorder {
84    async fn start(&self, _trigger_kind: &str, _trigger: Value) -> Box<dyn RunHandle> {
85        Box::new(NoopHandle)
86    }
87}
88
89struct NoopHandle;
90
91#[async_trait]
92impl RunHandle for NoopHandle {
93    async fn record_step(&mut self, _: &str, _: &str, _: StepStatus, _: Option<&str>, _: Value) {}
94    async fn mark_filtered(&mut self, _: &str, _: &str, _: &str) {}
95    async fn end(self: Box<Self>, _: RunStatus, _: Option<&str>) {}
96}
97
98/// Shared [`NoopRecorder`].
99pub fn noop_recorder() -> Arc<dyn RunRecorder> {
100    Arc::new(NoopRecorder)
101}