#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum HarnessError {
#[error("capability not supported: {primitive}")]
CapabilityNotSupported {
primitive: String,
},
#[error("stale target: {detail}")]
StaleTarget {
detail: String,
},
#[error("transport error: {detail}")]
Transport {
detail: String,
},
#[error("protocol error: {detail}")]
Protocol {
detail: String,
},
#[error("harness reported failure: {detail}")]
Harness {
detail: String,
},
#[error("agent-outcome contract refusal: {detail}")]
Contract {
detail: String,
},
}
impl HarnessError {
#[must_use]
pub fn capability_not_supported(primitive: impl Into<String>) -> Self {
Self::CapabilityNotSupported {
primitive: primitive.into(),
}
}
#[must_use]
pub fn stale_target(detail: impl Into<String>) -> Self {
Self::StaleTarget {
detail: detail.into(),
}
}
#[must_use]
pub fn transport(detail: impl Into<String>) -> Self {
Self::Transport {
detail: detail.into(),
}
}
#[must_use]
pub fn protocol(detail: impl Into<String>) -> Self {
Self::Protocol {
detail: detail.into(),
}
}
#[must_use]
pub fn harness(detail: impl Into<String>) -> Self {
Self::Harness {
detail: detail.into(),
}
}
#[must_use]
pub fn contract(detail: impl Into<String>) -> Self {
Self::Contract {
detail: detail.into(),
}
}
#[must_use]
pub fn is_deterministic(&self) -> bool {
match self {
Self::Contract { .. } => true,
Self::CapabilityNotSupported { .. }
| Self::StaleTarget { .. }
| Self::Transport { .. }
| Self::Protocol { .. }
| Self::Harness { .. } => false,
}
}
}
#[cfg(test)]
mod tests {
use super::HarnessError;
fn assert_send_sync_static<T: Send + Sync + 'static>() {}
#[test]
fn harness_error_is_send_sync_static() {
assert_send_sync_static::<HarnessError>();
}
#[test]
fn capability_not_supported_names_the_primitive() {
let error = HarnessError::capability_not_supported("pause_resume");
assert_eq!(error.to_string(), "capability not supported: pause_resume");
assert!(matches!(error, HarnessError::CapabilityNotSupported { .. }));
}
#[test]
fn each_constructor_renders_its_class() {
assert_eq!(
HarnessError::stale_target("attempt 2 superseded").to_string(),
"stale target: attempt 2 superseded"
);
assert_eq!(
HarnessError::transport("broken pipe").to_string(),
"transport error: broken pipe"
);
assert_eq!(
HarnessError::protocol("no matching id").to_string(),
"protocol error: no matching id"
);
assert_eq!(
HarnessError::harness("exit code 1").to_string(),
"harness reported failure: exit code 1"
);
assert_eq!(
HarnessError::contract("output is a JSON object").to_string(),
"agent-outcome contract refusal: output is a JSON object"
);
}
#[test]
fn only_the_contract_refusal_is_deterministic() {
assert!(HarnessError::contract("output is a JSON object").is_deterministic());
for transient in [
HarnessError::transport("broken pipe"),
HarnessError::protocol("invalid JSON frame"),
HarnessError::harness("run stopped without completing"),
HarnessError::stale_target("attempt 2 superseded"),
HarnessError::capability_not_supported("pause_resume"),
] {
assert!(
!transient.is_deterministic(),
"{transient:?} can be transient and must not classify deterministic"
);
}
}
}