greentic-runner-host 1.1.7

Host runtime shim for Greentic runner: config, pack loading, activity handling
Documentation
use crate::validate::ValidationIssue;
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceEnvelope {
    pub trace_version: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub runner_version: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub git_sha: Option<String>,
    pub pack: TracePack,
    pub flow: TraceFlow,
    pub steps: Vec<TraceStep>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TracePack {
    pub pack_ref: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resolved_digest: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceFlow {
    pub id: String,
    pub version: String,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceStep {
    pub node_id: String,
    pub component_id: String,
    pub operation: String,
    pub input_hash: TraceHash,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub invocation_json: Option<Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub invocation_path: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub output_hash: Option<TraceHash>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub state_delta_hash: Option<TraceHash>,
    pub duration_ms: u64,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub validation_issues: Vec<ValidationIssue>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<TraceError>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceError {
    pub code: String,
    pub message: String,
    pub details: Value,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraceHash {
    pub algorithm: String,
    pub value: String,
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn trace_serializes_with_version() {
        let trace = TraceEnvelope {
            trace_version: 1,
            runner_version: Some("0.0.0".to_string()),
            git_sha: None,
            pack: TracePack {
                pack_ref: "pack@0.0.0".to_string(),
                resolved_digest: Some("sha256:deadbeef".to_string()),
            },
            flow: TraceFlow {
                id: "flow.test".to_string(),
                version: "0.0.1".to_string(),
            },
            steps: vec![TraceStep {
                node_id: "node-1".to_string(),
                component_id: "component.exec".to_string(),
                operation: "render".to_string(),
                input_hash: TraceHash {
                    algorithm: "blake3".to_string(),
                    value: "hash".to_string(),
                },
                invocation_json: None,
                invocation_path: None,
                output_hash: None,
                state_delta_hash: None,
                duration_ms: 5,
                validation_issues: Vec::new(),
                error: Some(TraceError {
                    code: "node_error".to_string(),
                    message: "boom".to_string(),
                    details: json!({ "detail": "value" }),
                }),
            }],
        };

        let serialized = serde_json::to_string(&trace).expect("trace json");
        assert!(serialized.contains("\"trace_version\":1"));
    }
}