Skip to main content

greentic_dw_reflection/
model.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
5#[serde(rename_all = "snake_case")]
6pub enum ReviewVerdict {
7    Accept,
8    Revise,
9    Retry,
10    Delegate,
11    Fail,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15#[serde(rename_all = "snake_case")]
16pub enum ReviewTargetKind {
17    PlanStep,
18    Artifact,
19    Agent,
20    FinalOutput,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
24pub struct ReviewTarget {
25    pub kind: ReviewTargetKind,
26    pub reference: String,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
30pub struct ReviewFinding {
31    pub code: String,
32    pub message: String,
33    pub target: ReviewTarget,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
37pub struct SuggestedAction {
38    pub action: String,
39    pub target: ReviewTarget,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
43pub struct ReviewOutcome {
44    pub verdict: ReviewVerdict,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub score: Option<f32>,
47    #[serde(default)]
48    pub findings: Vec<ReviewFinding>,
49    #[serde(default)]
50    pub suggested_actions: Vec<SuggestedAction>,
51    #[serde(default)]
52    pub binding: bool,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
56pub struct ReviewStepRequest {
57    pub plan_step_id: String,
58    pub output_artifact_ref: String,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
62pub struct ReviewPlanRequest {
63    pub plan_id: String,
64    pub revision: u32,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
68pub struct ReviewFinalRequest {
69    pub run_id: String,
70    pub output_artifact_ref: String,
71}
72
73impl ReviewOutcome {
74    pub fn validate(&self) -> Result<(), crate::ReflectionError> {
75        if let Some(score) = self.score
76            && !(0.0..=1.0).contains(&score)
77        {
78            return Err(crate::ReflectionError::Validation(
79                "review score must be between 0.0 and 1.0".to_string(),
80            ));
81        }
82        for finding in &self.findings {
83            if finding.code.trim().is_empty() || finding.message.trim().is_empty() {
84                return Err(crate::ReflectionError::Validation(
85                    "review findings must include code and message".to_string(),
86                ));
87            }
88            if finding.target.reference.trim().is_empty() {
89                return Err(crate::ReflectionError::Validation(
90                    "review finding target reference must not be empty".to_string(),
91                ));
92            }
93        }
94        for action in &self.suggested_actions {
95            if action.action.trim().is_empty() || action.target.reference.trim().is_empty() {
96                return Err(crate::ReflectionError::Validation(
97                    "suggested actions must include an action and target reference".to_string(),
98                ));
99            }
100        }
101        Ok(())
102    }
103}