greentic_types/
run.rs

1//! Run-level telemetry shared between runners, CLIs, and CI integrations.
2
3use alloc::{collections::BTreeMap, string::String, vec::Vec};
4
5use semver::Version;
6
7use crate::{ComponentId, FlowId, NodeId, PackId, SessionKey};
8
9#[cfg(feature = "schemars")]
10use schemars::JsonSchema;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13#[cfg(all(feature = "serde", feature = "time"))]
14use serde_with::serde_as;
15#[cfg(feature = "time")]
16use time::OffsetDateTime;
17
18/// Overall execution status emitted by the runner.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
22#[cfg_attr(feature = "schemars", derive(JsonSchema))]
23pub enum RunStatus {
24    /// Flow finished successfully.
25    Success,
26    /// Flow finished with partial failures but continued.
27    PartialFailure,
28    /// Flow failed.
29    Failure,
30}
31
32/// Per-node execution status.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
36#[cfg_attr(feature = "schemars", derive(JsonSchema))]
37pub enum NodeStatus {
38    /// Node executed successfully.
39    Ok,
40    /// Node skipped execution (e.g. gated by conditionals).
41    Skipped,
42    /// Node errored.
43    Error,
44}
45
46/// Aggregated timing summary per node.
47#[derive(Clone, Debug, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
49#[cfg_attr(feature = "schemars", derive(JsonSchema))]
50pub struct NodeSummary {
51    /// Stable node identifier.
52    pub node_id: NodeId,
53    /// Component backing the node implementation.
54    pub component: ComponentId,
55    /// Final status of the node execution.
56    pub status: NodeStatus,
57    /// Execution time reported by the runner.
58    pub duration_ms: u64,
59}
60
61/// Byte-range offsets referencing captured transcripts/logs.
62#[derive(Clone, Debug, PartialEq, Eq, Hash)]
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64#[cfg_attr(feature = "schemars", derive(JsonSchema))]
65pub struct TranscriptOffset {
66    /// Start offset (inclusive) counted in bytes.
67    pub start: u64,
68    /// End offset (exclusive) counted in bytes.
69    pub end: u64,
70}
71
72/// Rich failure diagnostics for a node.
73#[derive(Clone, Debug, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
75#[cfg_attr(feature = "schemars", derive(JsonSchema))]
76pub struct NodeFailure {
77    /// Machine readable error code.
78    pub code: String,
79    /// Human readable explanation.
80    pub message: String,
81    /// Optional structured metadata.
82    #[cfg_attr(
83        feature = "serde",
84        serde(default, skip_serializing_if = "BTreeMap::is_empty")
85    )]
86    pub details: BTreeMap<String, String>,
87    /// Transcript offsets referencing the failure within captured logs.
88    #[cfg_attr(
89        feature = "serde",
90        serde(default, skip_serializing_if = "Vec::is_empty")
91    )]
92    pub transcript_offsets: Vec<TranscriptOffset>,
93    /// Disk paths or URIs pointing at log bundles.
94    #[cfg_attr(
95        feature = "serde",
96        serde(default, skip_serializing_if = "Vec::is_empty")
97    )]
98    pub log_paths: Vec<String>,
99}
100
101/// Aggregated run outcome emitted by the runtime.
102#[cfg(feature = "time")]
103#[cfg_attr(feature = "serde", serde_as)]
104#[derive(Clone, Debug, PartialEq)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[cfg_attr(feature = "schemars", derive(JsonSchema))]
107pub struct RunResult {
108    /// Session identifier emitted by the runtime.
109    pub session_id: SessionKey,
110    /// Pack identifier.
111    pub pack_id: PackId,
112    /// Pack version executed for the run.
113    #[cfg_attr(
114        feature = "serde",
115        serde_as(as = "serde_with::formats::DisplayFromStr")
116    )]
117    #[cfg_attr(
118        feature = "schemars",
119        schemars(with = "String", description = "SemVer version")
120    )]
121    pub pack_version: Version,
122    /// Flow identifier executed for the session.
123    pub flow_id: FlowId,
124    /// Wall-clock start timestamp in UTC.
125    #[cfg_attr(
126        feature = "schemars",
127        schemars(with = "String", description = "RFC3339 timestamp (UTC)")
128    )]
129    pub started_at_utc: OffsetDateTime,
130    /// Wall-clock finish timestamp in UTC.
131    #[cfg_attr(
132        feature = "schemars",
133        schemars(with = "String", description = "RFC3339 timestamp (UTC)")
134    )]
135    pub finished_at_utc: OffsetDateTime,
136    /// Final run status.
137    pub status: RunStatus,
138    /// Per-node execution summaries.
139    #[cfg_attr(
140        feature = "serde",
141        serde(default, skip_serializing_if = "Vec::is_empty")
142    )]
143    pub node_summaries: Vec<NodeSummary>,
144    /// Rich failure diagnostics, if any.
145    #[cfg_attr(
146        feature = "serde",
147        serde(default, skip_serializing_if = "Vec::is_empty")
148    )]
149    pub failures: Vec<NodeFailure>,
150    /// Directory containing emitted artifacts/log bundles.
151    #[cfg_attr(
152        feature = "serde",
153        serde(default, skip_serializing_if = "Option::is_none")
154    )]
155    pub artifacts_dir: Option<String>,
156}
157
158#[cfg(feature = "time")]
159impl RunResult {
160    /// Returns the total duration in milliseconds.
161    pub fn duration_ms(&self) -> u64 {
162        let duration = self.finished_at_utc - self.started_at_utc;
163        duration.whole_milliseconds().max(0) as u64
164    }
165}