use crate::retrieval::Confidence;
use super::GoldenCase;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ComboKind {
GroundGate,
VerifyReceipt,
VocabLoop,
GapPropose,
AunRepair,
RynReconcile,
VyreQueue,
TrustReview,
AetherStale,
NavigationPacket,
GlyphNavigation,
}
impl ComboKind {
pub fn parse(value: &str) -> Option<Self> {
match value {
"ground_gate" => Some(Self::GroundGate),
"verify_receipt" => Some(Self::VerifyReceipt),
"vocab_loop" => Some(Self::VocabLoop),
"gap_propose" => Some(Self::GapPropose),
"aun_repair" => Some(Self::AunRepair),
"ryn_reconcile" => Some(Self::RynReconcile),
"vyre_queue" => Some(Self::VyreQueue),
"trust_review" => Some(Self::TrustReview),
"aether_stale" => Some(Self::AetherStale),
"navigation_packet" => Some(Self::NavigationPacket),
"glyph_navigation" => Some(Self::GlyphNavigation),
_ => None,
}
}
pub fn label(self) -> &'static str {
match self {
Self::GroundGate => "ground_gate",
Self::VerifyReceipt => "verify_receipt",
Self::VocabLoop => "vocab_loop",
Self::GapPropose => "gap_propose",
Self::AunRepair => "aun_repair",
Self::RynReconcile => "ryn_reconcile",
Self::VyreQueue => "vyre_queue",
Self::TrustReview => "trust_review",
Self::AetherStale => "aether_stale",
Self::NavigationPacket => "navigation_packet",
Self::GlyphNavigation => "glyph_navigation",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ComboSpec {
GroundGate {
query: String,
min_band_text: String,
min_band: Confidence,
expected_pass: bool,
},
VerifyReceipt {
file: String,
line_start: usize,
line_end: Option<usize>,
quote: Option<String>,
expected_holds: bool,
},
VocabLoop {
query: String,
expected_top: String,
target_node: Option<String>,
min_band_text: String,
min_band: Confidence,
expected_pass: bool,
},
GapPropose {
query: String,
expected_pass: bool,
},
AunRepair {
query: String,
expected_pass: bool,
},
RynReconcile {
query: String,
expected_pass: bool,
},
VyreQueue {
query: String,
expected_pass: bool,
},
TrustReview {
query: String,
expected_pass: bool,
},
AetherStale {
query: String,
min_band_text: String,
min_band: Confidence,
expected_pass: bool,
},
NavigationPacket {
tool: String,
query: String,
expected_route: Option<String>,
expected_nodes: Vec<String>,
expected_partition: Option<String>,
expected_pass: bool,
},
GlyphNavigation {
query: String,
expected_pass: bool,
},
}
impl ComboSpec {
pub fn kind(&self) -> ComboKind {
match self {
Self::GroundGate { .. } => ComboKind::GroundGate,
Self::VerifyReceipt { .. } => ComboKind::VerifyReceipt,
Self::VocabLoop { .. } => ComboKind::VocabLoop,
Self::GapPropose { .. } => ComboKind::GapPropose,
Self::AunRepair { .. } => ComboKind::AunRepair,
Self::RynReconcile { .. } => ComboKind::RynReconcile,
Self::VyreQueue { .. } => ComboKind::VyreQueue,
Self::TrustReview { .. } => ComboKind::TrustReview,
Self::AetherStale { .. } => ComboKind::AetherStale,
Self::NavigationPacket { .. } => ComboKind::NavigationPacket,
Self::GlyphNavigation { .. } => ComboKind::GlyphNavigation,
}
}
pub fn from_golden(raw: &GoldenCase) -> Result<Self, ComboSpecError> {
let combo = raw.combo.as_deref().unwrap_or("route");
let Some(kind) = ComboKind::parse(combo) else {
return Err(ComboSpecError::Unsupported(combo.to_string()));
};
match kind {
ComboKind::GroundGate => {
let query = require_query(raw, kind)?;
let (min_band_text, min_band) = optional_confidence(raw, kind, "weak")?;
Ok(Self::GroundGate {
query,
min_band_text,
min_band,
expected_pass: raw.expected_pass.unwrap_or(true),
})
}
ComboKind::VerifyReceipt => {
let file = require_text(
raw.file.as_deref(),
kind,
"file",
"set file to the receipt path, relative to the package root",
)?;
let line_start = raw.line_start.filter(|line| *line > 0).ok_or_else(|| {
validation_error(
kind,
"line_start",
"missing positive line_start",
"set line_start to a 1-indexed receipt line",
)
})?;
Ok(Self::VerifyReceipt {
file,
line_start,
line_end: raw.line_end,
quote: raw.quote.clone(),
expected_holds: raw.expected_holds.unwrap_or(true),
})
}
ComboKind::VocabLoop => {
let query = require_query(raw, kind)?;
let expected_top = raw
.expect
.first()
.map(String::as_str)
.map(str::trim)
.filter(|expect| !expect.is_empty())
.map(ToOwned::to_owned)
.ok_or_else(|| {
validation_error(
kind,
"expect",
"missing first expect node id",
"set expect to the node id that should receive the query example",
)
})?;
let (min_band_text, min_band) = optional_confidence(raw, kind, "strong")?;
Ok(Self::VocabLoop {
query,
expected_top,
target_node: raw.target_node.clone(),
min_band_text,
min_band,
expected_pass: raw.expected_pass.unwrap_or(true),
})
}
ComboKind::GapPropose => Ok(Self::GapPropose {
query: require_query(raw, kind)?,
expected_pass: raw.expected_pass.unwrap_or(true),
}),
ComboKind::AunRepair => Ok(Self::AunRepair {
query: require_query(raw, kind)?,
expected_pass: raw.expected_pass.unwrap_or(true),
}),
ComboKind::RynReconcile => Ok(Self::RynReconcile {
query: require_query(raw, kind)?,
expected_pass: raw.expected_pass.unwrap_or(true),
}),
ComboKind::VyreQueue => Ok(Self::VyreQueue {
query: require_query(raw, kind)?,
expected_pass: raw.expected_pass.unwrap_or(true),
}),
ComboKind::TrustReview => Ok(Self::TrustReview {
query: require_query(raw, kind)?,
expected_pass: raw.expected_pass.unwrap_or(true),
}),
ComboKind::AetherStale => {
let query = require_query(raw, kind)?;
let (min_band_text, min_band) = optional_confidence(raw, kind, "ambiguous")?;
Ok(Self::AetherStale {
query,
min_band_text,
min_band,
expected_pass: raw.expected_pass.unwrap_or(true),
})
}
ComboKind::NavigationPacket => {
let tool = require_text(
raw.tool.as_deref(),
kind,
"tool",
"set tool to callers, impact, explain, neighbors, or paths",
)?;
Ok(Self::NavigationPacket {
tool,
query: require_query(raw, kind)?,
expected_route: raw.expected_focus_route.clone(),
expected_nodes: raw.expect.clone(),
expected_partition: raw.expected_partition.clone(),
expected_pass: raw.expected_pass.unwrap_or(true),
})
}
ComboKind::GlyphNavigation => Ok(Self::GlyphNavigation {
query: require_query(raw, kind)?,
expected_pass: raw.expected_pass.unwrap_or(true),
}),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ComboSpecError {
Invalid(ComboValidationError),
Unsupported(String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ComboValidationError {
pub combo: String,
pub field: String,
pub reason: String,
pub suggested_fix: String,
}
pub fn classify_combo_case(raw: &GoldenCase) -> Result<ComboSpec, ComboSpecError> {
ComboSpec::from_golden(raw)
}
fn require_query(raw: &GoldenCase, kind: ComboKind) -> Result<String, ComboSpecError> {
require_text(
Some(raw.query.as_str()),
kind,
"query",
"set query to the agent request this combo should exercise",
)
}
fn require_text(
value: Option<&str>,
kind: ComboKind,
field: &'static str,
suggested_fix: &'static str,
) -> Result<String, ComboSpecError> {
value
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.ok_or_else(|| validation_error(kind, field, format!("missing {field}"), suggested_fix))
}
fn optional_confidence(
raw: &GoldenCase,
kind: ComboKind,
default: &'static str,
) -> Result<(String, Confidence), ComboSpecError> {
let text = raw
.min_confidence
.as_deref()
.map(str::trim)
.filter(|text| !text.is_empty())
.unwrap_or(default);
let confidence = parse_confidence(text).ok_or_else(|| {
validation_error(
kind,
"min_confidence",
format!("unknown min_confidence '{text}'"),
"set min_confidence to one of exact, strong, ambiguous, weak, fallback",
)
})?;
Ok((text.to_string(), confidence))
}
fn parse_confidence(value: &str) -> Option<Confidence> {
match value.trim().to_ascii_lowercase().as_str() {
"exact" => Some(Confidence::Exact),
"strong" => Some(Confidence::Strong),
"ambiguous" => Some(Confidence::Ambiguous),
"weak" => Some(Confidence::Weak),
"fallback" => Some(Confidence::Fallback),
_ => None,
}
}
fn validation_error(
kind: ComboKind,
field: impl Into<String>,
reason: impl Into<String>,
suggested_fix: impl Into<String>,
) -> ComboSpecError {
ComboSpecError::Invalid(ComboValidationError {
combo: kind.label().to_string(),
field: field.into(),
reason: reason.into(),
suggested_fix: suggested_fix.into(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn combo_spec_requires_vocab_loop_expected_target() {
let err = ComboSpec::from_golden(&GoldenCase {
combo: Some("vocab_loop".to_string()),
query: "what happens to failed messages".to_string(),
..Default::default()
})
.unwrap_err();
let ComboSpecError::Invalid(err) = err else {
panic!("expected invalid combo spec");
};
assert_eq!(err.combo, "vocab_loop");
assert_eq!(err.field, "expect");
assert!(err.suggested_fix.contains("node id"));
}
#[test]
fn combo_spec_parses_vocab_loop_target_node() {
let spec = ComboSpec::from_golden(&GoldenCase {
combo: Some("vocab_loop".to_string()),
query: "what happens to failed messages".to_string(),
expect: vec!["doc.dlq".to_string()],
target_node: Some("doc.dlq".to_string()),
..Default::default()
})
.unwrap();
assert_eq!(
spec,
ComboSpec::VocabLoop {
query: "what happens to failed messages".to_string(),
expected_top: "doc.dlq".to_string(),
target_node: Some("doc.dlq".to_string()),
min_band_text: "strong".to_string(),
min_band: Confidence::Strong,
expected_pass: true,
}
);
}
#[test]
fn combo_spec_parses_ground_gate_defaults() {
let spec = ComboSpec::from_golden(&GoldenCase {
combo: Some("ground_gate".to_string()),
query: "ground me".to_string(),
..Default::default()
})
.unwrap();
assert_eq!(
spec,
ComboSpec::GroundGate {
query: "ground me".to_string(),
min_band_text: "weak".to_string(),
min_band: Confidence::Weak,
expected_pass: true,
}
);
}
#[test]
fn combo_spec_parses_vyre_queue() {
let spec = ComboSpec::from_golden(&GoldenCase {
combo: Some("vyre_queue".to_string()),
query: "quartz semaphore scheduling covenant".to_string(),
..Default::default()
})
.unwrap();
assert_eq!(
spec,
ComboSpec::VyreQueue {
query: "quartz semaphore scheduling covenant".to_string(),
expected_pass: true,
}
);
}
#[test]
fn combo_spec_parses_ryn_reconcile() {
let spec = ComboSpec::from_golden(&GoldenCase {
combo: Some("ryn_reconcile".to_string()),
query: "legacy retrieval".to_string(),
..Default::default()
})
.unwrap();
assert_eq!(
spec,
ComboSpec::RynReconcile {
query: "legacy retrieval".to_string(),
expected_pass: true,
}
);
}
#[test]
fn combo_spec_parses_aun_repair() {
let spec = ComboSpec::from_golden(&GoldenCase {
combo: Some("aun_repair".to_string()),
query: "duplicate query example repair".to_string(),
..Default::default()
})
.unwrap();
assert_eq!(
spec,
ComboSpec::AunRepair {
query: "duplicate query example repair".to_string(),
expected_pass: true,
}
);
}
#[test]
fn combo_spec_parses_glyph_navigation() {
let spec = ComboSpec::from_golden(&GoldenCase {
combo: Some("glyph_navigation".to_string()),
query: "retry payment callers precision".to_string(),
expected_pass: Some(false),
..Default::default()
})
.unwrap();
assert_eq!(
spec,
ComboSpec::GlyphNavigation {
query: "retry payment callers precision".to_string(),
expected_pass: false,
}
);
}
}