Skip to main content

ta_changeset/
artifact_type.rs

1// artifact_type.rs — ArtifactType enum for workflow I/O declarations (v0.14.10).
2//
3// Workflow steps declare typed inputs and outputs using this enum. The
4// WorkflowEngine resolves the execution DAG from type compatibility — a step
5// that outputs `PlanDocument` is automatically wired to any step that accepts
6// `PlanDocument` as input. Memory IS the session artifact store.
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt;
10use std::str::FromStr;
11
12/// Typed artifact that a workflow step declares as input or output.
13///
14/// Used by the WorkflowEngine to resolve execution order automatically from
15/// type compatibility — no explicit `depends_on` required when types match.
16///
17/// # Custom types
18/// Any unrecognized string becomes `Custom(string)`. Prefix with `x-` by
19/// convention: `inputs = ["x-my-custom-artifact"]`.
20///
21/// # Example workflow YAML
22/// ```yaml
23/// stages:
24///   - name: generate-plan
25///     outputs: [PlanDocument]
26///   - name: implement-plan
27///     inputs: [PlanDocument]
28///     outputs: [DraftPackage]
29/// ```
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
31pub enum ArtifactType {
32    /// A short goal description string (the starting prompt).
33    GoalTitle,
34    /// A structured plan document (plan items with acceptance criteria).
35    PlanDocument,
36    /// A TA draft package ready for review.
37    DraftPackage,
38    /// Pass/fail/flag verdict from a reviewer agent.
39    ReviewVerdict,
40    /// A single entry in the audit ledger.
41    AuditEntry,
42    /// Output from a constitution compliance review.
43    ConstitutionReport,
44    /// A message emitted by or to an agent.
45    AgentMessage,
46    /// A file or diff artifact (path + content).
47    FileArtifact,
48    /// Test run results (pass/fail counts, failures).
49    TestResult,
50    /// User-defined custom artifact type. Prefix with `x-` by convention.
51    Custom(String),
52}
53
54impl fmt::Display for ArtifactType {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        match self {
57            Self::GoalTitle => write!(f, "GoalTitle"),
58            Self::PlanDocument => write!(f, "PlanDocument"),
59            Self::DraftPackage => write!(f, "DraftPackage"),
60            Self::ReviewVerdict => write!(f, "ReviewVerdict"),
61            Self::AuditEntry => write!(f, "AuditEntry"),
62            Self::ConstitutionReport => write!(f, "ConstitutionReport"),
63            Self::AgentMessage => write!(f, "AgentMessage"),
64            Self::FileArtifact => write!(f, "FileArtifact"),
65            Self::TestResult => write!(f, "TestResult"),
66            Self::Custom(s) => write!(f, "{}", s),
67        }
68    }
69}
70
71impl FromStr for ArtifactType {
72    type Err = std::convert::Infallible;
73
74    fn from_str(s: &str) -> Result<Self, Self::Err> {
75        Ok(match s {
76            "GoalTitle" => Self::GoalTitle,
77            "PlanDocument" => Self::PlanDocument,
78            "DraftPackage" => Self::DraftPackage,
79            "ReviewVerdict" => Self::ReviewVerdict,
80            "AuditEntry" => Self::AuditEntry,
81            "ConstitutionReport" => Self::ConstitutionReport,
82            "AgentMessage" => Self::AgentMessage,
83            "FileArtifact" => Self::FileArtifact,
84            "TestResult" => Self::TestResult,
85            other => Self::Custom(other.to_string()),
86        })
87    }
88}
89
90// Serialize as a plain string.
91impl Serialize for ArtifactType {
92    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
93        serializer.serialize_str(&self.to_string())
94    }
95}
96
97// Deserialize from a plain string.
98impl<'de> Deserialize<'de> for ArtifactType {
99    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
100        let s = String::deserialize(deserializer)?;
101        Ok(s.parse().unwrap()) // Infallible
102    }
103}
104
105impl ArtifactType {
106    /// Returns true if this is a user-defined custom type.
107    pub fn is_custom(&self) -> bool {
108        matches!(self, Self::Custom(_))
109    }
110
111    /// Returns all built-in (non-custom) artifact type variants.
112    pub fn built_ins() -> Vec<ArtifactType> {
113        vec![
114            ArtifactType::GoalTitle,
115            ArtifactType::PlanDocument,
116            ArtifactType::DraftPackage,
117            ArtifactType::ReviewVerdict,
118            ArtifactType::AuditEntry,
119            ArtifactType::ConstitutionReport,
120            ArtifactType::AgentMessage,
121            ArtifactType::FileArtifact,
122            ArtifactType::TestResult,
123        ]
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn display_builtin_types() {
133        assert_eq!(ArtifactType::PlanDocument.to_string(), "PlanDocument");
134        assert_eq!(ArtifactType::DraftPackage.to_string(), "DraftPackage");
135        assert_eq!(ArtifactType::ReviewVerdict.to_string(), "ReviewVerdict");
136        assert_eq!(ArtifactType::GoalTitle.to_string(), "GoalTitle");
137        assert_eq!(ArtifactType::AuditEntry.to_string(), "AuditEntry");
138        assert_eq!(
139            ArtifactType::ConstitutionReport.to_string(),
140            "ConstitutionReport"
141        );
142        assert_eq!(ArtifactType::AgentMessage.to_string(), "AgentMessage");
143        assert_eq!(ArtifactType::FileArtifact.to_string(), "FileArtifact");
144        assert_eq!(ArtifactType::TestResult.to_string(), "TestResult");
145    }
146
147    #[test]
148    fn display_custom_type() {
149        assert_eq!(
150            ArtifactType::Custom("x-my-thing".to_string()).to_string(),
151            "x-my-thing"
152        );
153    }
154
155    #[test]
156    fn from_str_builtin() {
157        assert_eq!(
158            "PlanDocument".parse::<ArtifactType>().unwrap(),
159            ArtifactType::PlanDocument
160        );
161        assert_eq!(
162            "DraftPackage".parse::<ArtifactType>().unwrap(),
163            ArtifactType::DraftPackage
164        );
165        assert_eq!(
166            "TestResult".parse::<ArtifactType>().unwrap(),
167            ArtifactType::TestResult
168        );
169    }
170
171    #[test]
172    fn from_str_custom_falls_back() {
173        let t: ArtifactType = "x-custom-thing".parse().unwrap();
174        assert_eq!(t, ArtifactType::Custom("x-custom-thing".to_string()));
175        assert!(t.is_custom());
176    }
177
178    #[test]
179    fn is_custom_builtin_false() {
180        assert!(!ArtifactType::PlanDocument.is_custom());
181        assert!(!ArtifactType::DraftPackage.is_custom());
182    }
183
184    #[test]
185    fn serde_roundtrip_builtin() {
186        let t = ArtifactType::ReviewVerdict;
187        let json = serde_json::to_string(&t).unwrap();
188        assert_eq!(json, "\"ReviewVerdict\"");
189        let back: ArtifactType = serde_json::from_str(&json).unwrap();
190        assert_eq!(t, back);
191    }
192
193    #[test]
194    fn serde_roundtrip_custom() {
195        let t = ArtifactType::Custom("x-special".to_string());
196        let json = serde_json::to_string(&t).unwrap();
197        assert_eq!(json, "\"x-special\"");
198        let back: ArtifactType = serde_json::from_str(&json).unwrap();
199        assert_eq!(t, back);
200    }
201
202    #[test]
203    fn serde_in_vec() {
204        let types = vec![
205            ArtifactType::PlanDocument,
206            ArtifactType::DraftPackage,
207            ArtifactType::Custom("x-custom".to_string()),
208        ];
209        let json = serde_json::to_string(&types).unwrap();
210        assert_eq!(json, r#"["PlanDocument","DraftPackage","x-custom"]"#);
211        let back: Vec<ArtifactType> = serde_json::from_str(&json).unwrap();
212        assert_eq!(types, back);
213    }
214
215    #[test]
216    fn built_ins_count() {
217        assert_eq!(ArtifactType::built_ins().len(), 9);
218    }
219
220    #[test]
221    fn built_ins_none_are_custom() {
222        for t in ArtifactType::built_ins() {
223            assert!(!t.is_custom(), "{} should not be custom", t);
224        }
225    }
226}