af-workflow 0.2.0

Spec-driven workflow chassis: typed node expressions composed into a branched DAG. Port of agent_core/workflow/v2.
Documentation
//! Infra-agnostic workflow run and step persistence seam.

use std::sync::Arc;

use async_trait::async_trait;
use serde_json::Value;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepStatus {
    Ok,
    Skipped,
    Error,
}

impl StepStatus {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Ok => "ok",
            Self::Skipped => "skipped",
            Self::Error => "error",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunStatus {
    Ok,
    Skipped,
    Error,
}

impl RunStatus {
    pub fn as_str(self) -> &'static str {
        match self {
            Self::Ok => "ok",
            Self::Skipped => "skipped",
            Self::Error => "error",
        }
    }
}

#[async_trait]
pub trait RunHandle: Send {
    async fn record_step(
        &mut self,
        node_id: &str,
        node_type: &str,
        status: StepStatus,
        exit_reason: Option<&str>,
        detail: Value,
    );

    /// Record routine negative evidence without materializing a full run.
    async fn mark_filtered(&mut self, node_id: &str, node_type: &str, reason: &str);

    async fn end(self: Box<Self>, status: RunStatus, exit_reason: Option<&str>);
}

#[async_trait]
pub trait RunRecorder: Send + Sync {
    async fn start(&self, trigger_kind: &str, trigger: Value) -> Box<dyn RunHandle>;
}

pub struct NoopRecorder;

#[async_trait]
impl RunRecorder for NoopRecorder {
    async fn start(&self, _trigger_kind: &str, _trigger: Value) -> Box<dyn RunHandle> {
        Box::new(NoopHandle)
    }
}

struct NoopHandle;

#[async_trait]
impl RunHandle for NoopHandle {
    async fn record_step(&mut self, _: &str, _: &str, _: StepStatus, _: Option<&str>, _: Value) {}
    async fn mark_filtered(&mut self, _: &str, _: &str, _: &str) {}
    async fn end(self: Box<Self>, _: RunStatus, _: Option<&str>) {}
}

pub fn noop_recorder() -> Arc<dyn RunRecorder> {
    Arc::new(NoopRecorder)
}