greentic-dw-reflection 0.5.3

Reflection contracts for greentic-dw deep-agent flows.
Documentation
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ReviewVerdict {
    Accept,
    Revise,
    Retry,
    Delegate,
    Fail,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ReviewTargetKind {
    PlanStep,
    Artifact,
    Agent,
    FinalOutput,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ReviewTarget {
    pub kind: ReviewTargetKind,
    pub reference: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ReviewFinding {
    pub code: String,
    pub message: String,
    pub target: ReviewTarget,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct SuggestedAction {
    pub action: String,
    pub target: ReviewTarget,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ReviewOutcome {
    pub verdict: ReviewVerdict,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub score: Option<f32>,
    #[serde(default)]
    pub findings: Vec<ReviewFinding>,
    #[serde(default)]
    pub suggested_actions: Vec<SuggestedAction>,
    #[serde(default)]
    pub binding: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ReviewStepRequest {
    pub plan_step_id: String,
    pub output_artifact_ref: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ReviewPlanRequest {
    pub plan_id: String,
    pub revision: u32,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ReviewFinalRequest {
    pub run_id: String,
    pub output_artifact_ref: String,
}

impl ReviewOutcome {
    pub fn validate(&self) -> Result<(), crate::ReflectionError> {
        if let Some(score) = self.score
            && !(0.0..=1.0).contains(&score)
        {
            return Err(crate::ReflectionError::Validation(
                "review score must be between 0.0 and 1.0".to_string(),
            ));
        }
        for finding in &self.findings {
            if finding.code.trim().is_empty() || finding.message.trim().is_empty() {
                return Err(crate::ReflectionError::Validation(
                    "review findings must include code and message".to_string(),
                ));
            }
            if finding.target.reference.trim().is_empty() {
                return Err(crate::ReflectionError::Validation(
                    "review finding target reference must not be empty".to_string(),
                ));
            }
        }
        for action in &self.suggested_actions {
            if action.action.trim().is_empty() || action.target.reference.trim().is_empty() {
                return Err(crate::ReflectionError::Validation(
                    "suggested actions must include an action and target reference".to_string(),
                ));
            }
        }
        Ok(())
    }
}