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}