feat: complete Epic 1 production foundation
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
use admin_api::error::{runtime_error_context, runtime_test_failure};
|
||||
use admin_api::error::{
|
||||
execution_test_failure, execution_test_failure_localized, runtime_test_failure,
|
||||
};
|
||||
use crank_core::{CorrelationContext, ExecutionErrorCode, ExecutionFailure, ExecutionLocale};
|
||||
use crank_runtime::RuntimeError;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn runtime_test_failure_includes_structured_context() {
|
||||
@@ -9,23 +11,23 @@ fn runtime_test_failure_includes_structured_context() {
|
||||
reason: "must be an object".to_owned(),
|
||||
});
|
||||
|
||||
assert_eq!(payload["code"], "runtime_request_error");
|
||||
assert_eq!(
|
||||
payload["context"],
|
||||
json!({
|
||||
"field": "request.headers"
|
||||
})
|
||||
);
|
||||
assert_eq!(payload["code"], "prepared_request_invalid");
|
||||
assert_eq!(payload["stage"], "request_preparation");
|
||||
assert_eq!(payload["retryability"], "never");
|
||||
assert_eq!(payload["outcome_certainty"], "certain");
|
||||
assert!(payload.get("context").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn runtime_error_context_does_not_expose_secret_crypto_details() {
|
||||
let context = runtime_error_context(&RuntimeError::SecretCrypto {
|
||||
let canary = "story18-secret-crypto-canary";
|
||||
let payload = runtime_test_failure(&RuntimeError::SecretCrypto {
|
||||
operation: "decode secret envelope",
|
||||
details: "bad base64".to_owned(),
|
||||
details: canary.to_owned(),
|
||||
});
|
||||
|
||||
assert_eq!(context, None);
|
||||
assert_eq!(payload["code"], "secret_invalid");
|
||||
assert!(!payload.to_string().contains(canary));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -35,12 +37,45 @@ fn runtime_test_failure_includes_runtime_overload_context() {
|
||||
limit: 16,
|
||||
});
|
||||
|
||||
assert_eq!(payload["code"], "runtime_overloaded");
|
||||
assert_eq!(
|
||||
payload["context"],
|
||||
json!({
|
||||
"kind": "window",
|
||||
"limit": 16
|
||||
})
|
||||
);
|
||||
assert_eq!(payload["code"], "execution_overloaded");
|
||||
assert_eq!(payload["stage"], "admission");
|
||||
assert_eq!(payload["retryability"], "after_delay");
|
||||
assert!(payload.get("context").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_execution_code_projects_the_canonical_semantics_without_raw_context() {
|
||||
let canary = "story18-admin-projection-canary";
|
||||
for code in ExecutionErrorCode::ALL {
|
||||
let failure = ExecutionFailure::new(code, CorrelationContext::generate());
|
||||
let payload = execution_test_failure(&failure);
|
||||
assert_eq!(payload["code"], code.as_str());
|
||||
assert_eq!(payload["stage"], code.stage().as_str());
|
||||
assert_eq!(payload["retryability"], code.retryability().as_str());
|
||||
assert_eq!(
|
||||
payload["outcome_certainty"],
|
||||
code.outcome_certainty().as_str()
|
||||
);
|
||||
assert!(!payload["message"].as_str().unwrap_or_default().is_empty());
|
||||
assert!(!payload.to_string().contains(canary));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn confirmation_challenge_is_bounded_and_localized_at_the_admin_boundary() {
|
||||
let failure = ExecutionFailure::new(
|
||||
ExecutionErrorCode::ConfirmationRequired,
|
||||
CorrelationContext::generate(),
|
||||
)
|
||||
.try_with_confirmation("confirmation-capability", 30_000)
|
||||
.unwrap();
|
||||
let payload = execution_test_failure_localized(&failure, ExecutionLocale::Ru);
|
||||
|
||||
assert_eq!(payload["message"], "Операция требует подтверждения.");
|
||||
assert_eq!(
|
||||
payload["context"]["confirmation_token"],
|
||||
"confirmation-capability"
|
||||
);
|
||||
assert_eq!(payload["context"]["expires_in_ms"], 30_000);
|
||||
assert!(!format!("{failure:?}").contains("confirmation-capability"));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
use admin_api::error::execution_test_failure_localized;
|
||||
use crank_community_mcp::tool_error::tool_error_contract_from_failure;
|
||||
use crank_core::{
|
||||
CorrelationContext, ExecutionErrorCode, ExecutionFailure, ExecutionLocale, Retryability,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn admin_and_mcp_project_every_execution_failure_with_identical_semantics() {
|
||||
for locale in [ExecutionLocale::En, ExecutionLocale::Ru] {
|
||||
for code in ExecutionErrorCode::ALL {
|
||||
let failure = ExecutionFailure::new(code, CorrelationContext::generate());
|
||||
let admin = execution_test_failure_localized(&failure, locale);
|
||||
let mcp = tool_error_contract_from_failure(&failure, locale);
|
||||
|
||||
assert_eq!(admin["code"], mcp.error_code);
|
||||
assert_eq!(admin["stage"], mcp.stage);
|
||||
assert_eq!(admin["retryability"], mcp.retryability);
|
||||
assert_eq!(admin["outcome_certainty"], mcp.outcome_certainty);
|
||||
assert_eq!(admin["message"], mcp.message);
|
||||
assert_eq!(
|
||||
mcp.recoverable,
|
||||
matches!(
|
||||
code.retryability(),
|
||||
Retryability::Safe
|
||||
| Retryability::AfterDelay
|
||||
| Retryability::RequiresConfirmation
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn admin_and_mcp_preserve_dispatch_uncertainty_without_changing_the_code() {
|
||||
for locale in [ExecutionLocale::En, ExecutionLocale::Ru] {
|
||||
let failure = ExecutionFailure::new(
|
||||
ExecutionErrorCode::UpstreamTransportError,
|
||||
CorrelationContext::generate(),
|
||||
)
|
||||
.with_dispatch_uncertainty();
|
||||
let admin = execution_test_failure_localized(&failure, locale);
|
||||
let mcp = tool_error_contract_from_failure(&failure, locale);
|
||||
|
||||
assert_eq!(admin["retryability"], "manual_reconcile");
|
||||
assert_eq!(admin["outcome_certainty"], "outcome_unknown");
|
||||
assert_eq!(admin["retryability"], mcp.retryability);
|
||||
assert_eq!(admin["outcome_certainty"], mcp.outcome_certainty);
|
||||
assert!(!mcp.recoverable);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user