use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::validation::Detector;
use super::yes;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StopGates {
#[serde(default = "default_max_iterations")]
pub max_iterations: u32,
#[serde(default = "default_max_revisions")]
pub max_revisions_per_node: u32,
#[serde(default)]
pub max_wall_clock_seconds: Option<u64>,
#[serde(default)]
pub max_tokens: Option<u64>,
#[serde(default)]
pub max_cost_usd: Option<f64>,
#[serde(default = "default_no_progress")]
pub no_progress_iterations: u32,
#[serde(default)]
pub no_progress_iterations_randomness: Option<u32>,
#[serde(default = "yes")]
pub stop_on_overall_success: bool,
}
impl Default for StopGates {
fn default() -> Self {
Self {
max_iterations: default_max_iterations(),
max_revisions_per_node: default_max_revisions(),
max_wall_clock_seconds: None,
max_tokens: None,
max_cost_usd: None,
no_progress_iterations: default_no_progress(),
no_progress_iterations_randomness: None,
stop_on_overall_success: true,
}
}
}
fn default_max_iterations() -> u32 {
10
}
fn default_max_revisions() -> u32 {
3
}
fn default_no_progress() -> u32 {
3
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GateOutcome {
Stop,
Escalate,
Pause,
Rollback,
Warn,
}
impl GateOutcome {
pub fn is_blocking(self) -> bool {
!matches!(self, GateOutcome::Warn)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GateRule {
pub id: String,
pub statement: String,
pub detector: Detector,
#[serde(default = "default_on_fail")]
pub on_fail: GateOutcome,
}
fn default_on_fail() -> GateOutcome {
GateOutcome::Stop
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Gates {
#[serde(default)]
pub stop: StopGates,
#[serde(default)]
pub entry: Vec<GateRule>,
#[serde(default)]
pub approval: Vec<GateRule>,
#[serde(default)]
pub rollback: Vec<GateRule>,
}
impl Gates {
pub fn rules(&self) -> impl Iterator<Item = (GateKind, &GateRule)> {
self.entry
.iter()
.map(|r| (GateKind::Entry, r))
.chain(self.approval.iter().map(|r| (GateKind::Approval, r)))
.chain(self.rollback.iter().map(|r| (GateKind::Rollback, r)))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum GateKind {
Entry,
Approval,
Rollback,
}
impl GateKind {
pub fn as_str(self) -> &'static str {
match self {
GateKind::Entry => "entry",
GateKind::Approval => "approval",
GateKind::Rollback => "rollback",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_gate_defaults_to_blocking() {
assert_eq!(default_on_fail(), GateOutcome::Stop);
assert!(default_on_fail().is_blocking());
}
#[test]
fn only_warn_is_non_blocking() {
for o in [
GateOutcome::Stop,
GateOutcome::Escalate,
GateOutcome::Pause,
GateOutcome::Rollback,
] {
assert!(o.is_blocking(), "{o:?} must block");
}
assert!(!GateOutcome::Warn.is_blocking());
}
}