use harn_serve::adapters::acp::{AcpPromptErrorData, AcpPromptFailureFacts};
use harn_vm::llm::AgentTerminalClass;
use super::generated_rust_binding::{HarnLlmErrorCategory, HarnLlmErrorKind, HarnLlmErrorReason};
use super::*;
#[test]
fn admission_snapshot_round_trips_through_schema_generated_host_records() {
use super::generated_rust_binding::{
HarnInferenceAdmissionSnapshot, HarnInferenceAdmissionStatus,
};
use harn_vm::llm::api::{
preview_inference_admission, InferenceAdmissionRequest, InferenceBoundary, InferenceReach,
};
let _guard = crate::tests::common::harn_state_lock::lock_harn_state();
let _host = crate::env_guard::ScopedEnvVar::unset("HARN_INFERENCE_BOUNDARY_JSON");
let _endpoint = crate::env_guard::ScopedEnvVar::set("OLLAMA_HOST", "http://127.0.0.1:9");
for (provider, expected) in [
("ollama", HarnInferenceAdmissionStatus::Admitted),
("openai", HarnInferenceAdmissionStatus::Denied),
(
"unresearched-provider",
HarnInferenceAdmissionStatus::Unknown,
),
] {
let request = InferenceAdmissionRequest {
provider: provider.into(),
model: "projection-fixture-model".into(),
boundary: Some(InferenceBoundary {
reach: InferenceReach::LocalOnly,
allow_training_discounts: false,
}),
data_controls: None,
};
let wire = serde_json::to_value(preview_inference_admission(&request)).unwrap();
let decoded: HarnInferenceAdmissionSnapshot = serde_json::from_value(wire.clone()).unwrap();
assert_eq!(decoded.status, expected);
assert_eq!(serde_json::to_value(decoded).unwrap(), wire);
let mut absent = wire;
absent.as_object_mut().unwrap().remove("status");
assert!(serde_json::from_value::<HarnInferenceAdmissionSnapshot>(absent).is_err());
}
}
#[test]
fn local_policy_denial_survives_the_native_protocol_projection() {
let facts = AcpPromptFailureFacts::from_thrown(&serde_json::json!({
"category": "egress_blocked",
"kind": "terminal",
"reason": "policy_denied",
"origin": "local",
"rule": "inference_boundary.local_only",
"retryable": false,
}));
let envelope = serde_json::to_value(AcpPromptErrorData::with_facts(
AgentTerminalClass::ToolPolicyRejected,
facts,
))
.expect("policy envelope serializes");
let reason: HarnLlmErrorReason =
serde_json::from_value(envelope["reason"].clone()).expect("typed reason decodes");
assert_eq!(reason, HarnLlmErrorReason::PolicyDenied);
assert_eq!(envelope["origin"], "local");
assert_eq!(envelope["rule"], "inference_boundary.local_only");
assert_eq!(envelope["retryable"], false);
assert_eq!(envelope["terminalClass"], "tool_policy_rejected");
}
#[test]
fn managed_spend_pause_survives_the_generated_prompt_error_projection() {
for (owner, expected) in [
(
AgentTerminalClass::ManagedSpendPaused,
HarnLlmErrorReason::ManagedSpendPaused,
),
(
AgentTerminalClass::ProviderBilling,
HarnLlmErrorReason::BillingLimit,
),
(
AgentTerminalClass::RateLimited,
HarnLlmErrorReason::RateLimit,
),
] {
let reason = match owner {
AgentTerminalClass::ManagedSpendPaused => "managed_spend_paused",
AgentTerminalClass::ProviderBilling => "billing_limit",
AgentTerminalClass::RateLimited => "rate_limit",
_ => unreachable!(),
};
let transient = owner == AgentTerminalClass::RateLimited;
let facts = AcpPromptFailureFacts::from_thrown(&serde_json::json!({
"category": if transient { "rate_limit" } else { "generic" },
"kind": if transient { "transient" } else { "terminal" }, "reason": reason,
}));
let envelope = serde_json::to_value(AcpPromptErrorData::with_facts(owner, facts)).unwrap();
let decoded: HarnLlmErrorReason =
serde_json::from_value(envelope["reason"].clone()).unwrap();
assert_eq!(decoded, expected);
assert!(decoded.is_known());
assert_eq!(envelope["terminalClass"], owner.as_str());
assert_eq!(serde_json::to_value(decoded).unwrap(), envelope["reason"]);
}
}
#[test]
fn generated_rust_enums_match_the_owning_vocabularies() {
let known = |values: Vec<String>, generated: Vec<String>| {
assert_eq!(values, generated);
};
known(
llm_error_reason_values(),
HarnLlmErrorReason::KNOWN
.iter()
.map(|reason| reason.as_str().to_string())
.collect(),
);
known(
llm_error_kind_values(),
HarnLlmErrorKind::KNOWN
.iter()
.map(|kind| kind.as_str().to_string())
.collect(),
);
known(
llm_error_category_values(),
HarnLlmErrorCategory::KNOWN
.iter()
.map(|category| category.as_str().to_string())
.collect(),
);
}
#[test]
fn network_error_failure_round_trips_through_the_generated_rust_types() {
let facts = AcpPromptFailureFacts::from_thrown(&serde_json::json!({
"category": "transient_network",
"kind": "transient",
"reason": "network_error",
"message": "error sending request: connection reset by peer",
}));
let envelope = serde_json::to_value(AcpPromptErrorData::with_facts(
AgentTerminalClass::ProviderUnavailable,
facts,
))
.expect("envelope serializes");
let reason: HarnLlmErrorReason =
serde_json::from_value(envelope["reason"].clone()).expect("reason decodes");
let kind: HarnLlmErrorKind =
serde_json::from_value(envelope["kind"].clone()).expect("kind decodes");
let category: HarnLlmErrorCategory =
serde_json::from_value(envelope["category"].clone()).expect("category decodes");
assert_eq!(reason, HarnLlmErrorReason::NetworkError);
assert_eq!(kind, HarnLlmErrorKind::Transient);
assert_eq!(category, HarnLlmErrorCategory::TransientNetwork);
assert!(reason.is_known() && kind.is_known() && category.is_known());
assert_eq!(
serde_json::to_value(&reason).expect("reason serializes"),
envelope["reason"]
);
assert_eq!(
serde_json::to_value(&kind).expect("kind serializes"),
envelope["kind"]
);
}
#[test]
fn a_value_outside_the_vocabulary_lands_in_the_unknown_escape() {
let reason: HarnLlmErrorReason =
serde_json::from_value(serde_json::json!("provider_connection_failed"))
.expect("an unrecognized reason still decodes");
assert_eq!(
reason,
HarnLlmErrorReason::Unrecognized("provider_connection_failed".to_string())
);
assert!(!reason.is_known());
assert_eq!(reason.as_str(), "provider_connection_failed");
assert_eq!(
serde_json::to_value(&reason).expect("serializes"),
serde_json::json!("provider_connection_failed")
);
let classified_unknown: HarnLlmErrorReason =
serde_json::from_value(serde_json::json!("unknown")).expect("decodes");
assert_eq!(classified_unknown, HarnLlmErrorReason::Unknown);
assert!(classified_unknown.is_known());
let kind: HarnLlmErrorKind =
serde_json::from_value(serde_json::json!("provider_degraded")).expect("decodes");
assert_eq!(
kind,
HarnLlmErrorKind::Unrecognized("provider_degraded".to_string())
);
assert!(!kind.is_known());
}