наблюдаемость: ввести безопасный контракт метрик
CI / Rust Checks (pull_request) Successful in 6m15s
CI / UI Checks (pull_request) Successful in 5s
CI / Community Image Smoke (pull_request) Successful in 4m25s
CI / Frontend E2E (pull_request) Successful in 5m17s
CI / Deploy (pull_request) Has been skipped
CI / Rust Checks (push) Successful in 6m9s
CI / UI Checks (push) Successful in 5s
CI / Community Image Smoke (push) Successful in 1m3s
CI / Frontend E2E (push) Successful in 3m47s
CI / Deploy (push) Failing after 3s

This commit is contained in:
2026-07-31 05:04:01 +03:00
parent ec2453c00f
commit 9b1a739e39
50 changed files with 3066 additions and 433 deletions
@@ -6,8 +6,9 @@ use std::sync::{
use async_trait::async_trait;
use crank_core::{
AdapterResponse, ConfirmationPolicy, ExecutionConfig, ExecutionMode, HttpMethod, Operation,
OperationId, OperationSafetyClass, OperationSafetyPolicy, OperationSecurityLevel,
AdapterResponse, CacheScope, CacheStoreError, ConfirmationPolicy, CoordinationStateReservation,
CoordinationStateStore, CoordinationStateValue, ExecutionConfig, ExecutionMode, HttpMethod,
Operation, OperationId, OperationSafetyClass, OperationSafetyPolicy, OperationSecurityLevel,
OperationStatus, Protocol, ProtocolAdapter, ProtocolAdapterError, RestTarget, Target,
ToolDescription,
};
@@ -146,6 +147,111 @@ async fn confirmation_token_allows_only_one_concurrent_execution() {
);
}
#[tokio::test]
async fn unavailable_confirmation_store_is_preserved_as_a_typed_error() {
let executor = RuntimeExecutorBuilder::new()
.register_adapter(Arc::new(CountingAdapter {
call_count: Arc::new(AtomicUsize::new(0)),
}))
.with_coordination_store(Arc::new(UnavailableCoordinationStore))
.build();
let operation: crank_runtime::RuntimeOperation = destructive_delete_operation().into();
let context = RuntimeRequestContext::from_request_id("req_confirm_unavailable")
.with_response_cache_scope("workspace_1", "agent_1");
let issue_error = executor
.execute_with_context(
&operation,
&json!({ "order_id": "ord_123" }),
Some(&context),
)
.await
.expect_err("unavailable store must prevent issuing a token");
assert!(matches!(
issue_error,
RuntimeError::ConfirmationStoreUnavailable { ref operation_id }
if operation_id == "op_delete_order"
));
let consume_error = executor
.execute_with_context(
&operation,
&json!({ "order_id": "ord_123" }),
Some(&context.with_confirmation_token("ct_unavailable")),
)
.await
.expect_err("unavailable store must not look like an invalid token");
assert!(matches!(
consume_error,
RuntimeError::ConfirmationStoreUnavailable { ref operation_id }
if operation_id == "op_delete_order"
));
}
struct UnavailableCoordinationStore;
impl UnavailableCoordinationStore {
fn error() -> CacheStoreError {
CacheStoreError::Unavailable {
message: "test backend unavailable".to_owned(),
}
}
}
#[async_trait]
impl CoordinationStateStore for UnavailableCoordinationStore {
async fn get_value(
&self,
_scope: CacheScope,
_key: &str,
) -> Result<Option<CoordinationStateValue>, CacheStoreError> {
Err(Self::error())
}
async fn put_value(
&self,
_scope: CacheScope,
_key: &str,
_value: CoordinationStateValue,
_ttl: std::time::Duration,
) -> Result<(), CacheStoreError> {
Err(Self::error())
}
async fn delete_value(&self, _scope: CacheScope, _key: &str) -> Result<(), CacheStoreError> {
Err(Self::error())
}
async fn take_value(
&self,
_scope: CacheScope,
_key: &str,
) -> Result<Option<CoordinationStateValue>, CacheStoreError> {
Err(Self::error())
}
async fn reserve_value(
&self,
_scope: CacheScope,
_key: &str,
_value: CoordinationStateValue,
_ttl: std::time::Duration,
) -> Result<CoordinationStateReservation, CacheStoreError> {
Err(Self::error())
}
async fn compare_and_set_value(
&self,
_scope: CacheScope,
_key: &str,
_expected: &CoordinationStateValue,
_value: CoordinationStateValue,
_ttl: std::time::Duration,
) -> Result<bool, CacheStoreError> {
Err(Self::error())
}
}
struct CountingAdapter {
call_count: Arc<AtomicUsize>,
}