klieo-workflow-api 3.8.1

Embeddable Axum run-service router fronting klieo-workflow (submit / poll declarative workflows).
Documentation
//! Per-run provenance binding.
//!
//! Records a run's lifecycle into a durable, tamper-evident chain: an
//! `AgentRunStarted` at submit and a terminal `AgentRunCompleted` at finish.
//! The chain scope is the run id (a self-contained 2-entry chain per run);
//! `resource_ref` is the workflow id and `payload_hash_hex` is the
//! definition's canonical hash, so a run is bound to an exact definition and
//! its authenticated author.
//!
//! `klieo-provenance` has no distinct `AgentRunFailed` kind — terminal
//! events use `AgentRunCompleted` with the outcome carried in metadata
//! (`status` + optional `reason`), matching the crate's own episode mapping.

use klieo_provenance::{
    ProvenanceError, ProvenanceEvent, ProvenanceEventKind, ProvenanceRepository,
};
use serde_json::{json, Value};
use std::sync::Arc;

/// Terminal `status` metadata values.
pub(crate) const STATUS_SUCCEEDED: &str = "succeeded";
pub(crate) const STATUS_FAILED: &str = "failed";
pub(crate) const STATUS_ABORTED: &str = "aborted";

/// Terminal metadata keys.
const METADATA_STATUS_KEY: &str = "status";
const METADATA_REASON_KEY: &str = "reason";

/// Provenance context for one run, threaded into the executor.
#[derive(Clone)]
pub(crate) struct RunProvenance {
    pub repo: Arc<dyn ProvenanceRepository>,
    /// Chain scope — the run id.
    pub run_id: String,
    /// Authenticated caller; the event actor.
    pub author: String,
    /// The workflow id; the event `resource_ref`.
    pub workflow_id: String,
    /// The definition's canonical hash; the event `payload_hash_hex`.
    pub content_hash: String,
}

impl RunProvenance {
    /// Append the run-start entry to the run's chain (authoritative).
    pub async fn record_started(&self) -> Result<(), ProvenanceError> {
        let event = self.event(ProvenanceEventKind::AgentRunStarted, Value::Null);
        self.repo.append(&self.run_id, event).await.map(|_| ())
    }

    /// Append the terminal entry, carrying the outcome in metadata.
    pub async fn record_terminal(
        &self,
        status: &str,
        reason: Option<String>,
    ) -> Result<(), ProvenanceError> {
        let mut metadata = json!({ METADATA_STATUS_KEY: status });
        if let Some(reason) = reason {
            metadata[METADATA_REASON_KEY] = Value::String(reason);
        }
        let event = self.event(ProvenanceEventKind::AgentRunCompleted, metadata);
        self.repo.append(&self.run_id, event).await.map(|_| ())
    }

    fn event(&self, kind: ProvenanceEventKind, metadata: Value) -> ProvenanceEvent {
        ProvenanceEvent {
            kind,
            actor: self.author.clone(),
            resource_ref: self.workflow_id.clone(),
            payload_hash_hex: self.content_hash.clone(),
            metadata,
        }
    }
}