Усилить безопасность и надёжность выполнения операций
CI / Rust Checks (push) Successful in 5m7s
CI / UI Checks (push) Successful in 4s
CI / Deployment Manifests (push) Successful in 3s
CI / Frontend E2E (push) Successful in 3m9s
CI / Deploy (push) Successful in 1m41s

This commit is contained in:
2026-07-11 14:08:07 +03:00
parent 626f2845e2
commit 8318e4b560
40 changed files with 1343 additions and 185 deletions
+3
View File
@@ -21,6 +21,9 @@ pub async fn confirm_operation(
if !safety.class.requires_confirmation() {
return Ok(());
}
if request_context.is_some_and(RuntimeRequestContext::approval_granted) {
return Ok(());
}
let Some(store) = store else {
return Err(RuntimeError::ConfirmationStoreUnavailable {
+11 -1
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use crank_adapter_rest::RestAdapter;
use crank_adapter_rest::{OutboundHttpPolicy, RestAdapter, RestAdapterError};
use crank_core::{
AdapterRegistry, CoordinationStateStore, MeteringSink, NoopMeteringSink, ResponseCacheStore,
SharedMeteringSink, SharedProtocolAdapter,
@@ -73,3 +73,13 @@ pub fn community_default() -> RuntimeExecutorBuilder {
RuntimeExecutorBuilder::new()
.register_adapter(Arc::new(RestAdapter::new()) as SharedProtocolAdapter)
}
pub fn community_from_env() -> Result<RuntimeExecutorBuilder, RestAdapterError> {
Ok(RuntimeExecutorBuilder::new()
.register_adapter(Arc::new(RestAdapter::from_env()?) as SharedProtocolAdapter))
}
pub fn community_with_outbound_policy(policy: OutboundHttpPolicy) -> RuntimeExecutorBuilder {
RuntimeExecutorBuilder::new()
.register_adapter(Arc::new(RestAdapter::with_policy(policy)) as SharedProtocolAdapter)
}
+4 -1
View File
@@ -23,9 +23,12 @@ pub use cache::{
pub use cache_factory::{
BuiltinCacheBackendFactory, CacheBackendFactory, SharedCacheBackendFactory,
};
pub use crank_adapter_rest::OutboundHttpPolicy;
pub use error::RuntimeError;
pub use executor::{RuntimeExecutionRequest, RuntimeExecutor};
pub use executor_builder::{RuntimeExecutorBuilder, community_default};
pub use executor_builder::{
RuntimeExecutorBuilder, community_default, community_from_env, community_with_outbound_policy,
};
pub use limits::{RuntimeLimits, RuntimeLimitsConfigError};
pub use model::{AdapterResponse, PreparedRequest, RuntimeOperation};
pub use rate_limit::{
@@ -15,6 +15,7 @@ pub struct RuntimeRequestContext {
pub response_cache_scope: Option<ResponseCacheScope>,
pub metering_context: Option<MeteringContext>,
pub confirmation_token: Option<String>,
pub approval_granted: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -32,6 +33,7 @@ impl RuntimeRequestContext {
response_cache_scope: None,
metering_context: None,
confirmation_token: None,
approval_granted: false,
}
}
@@ -89,6 +91,15 @@ impl RuntimeRequestContext {
pub fn confirmation_token(&self) -> Option<&str> {
self.confirmation_token.as_deref()
}
pub fn with_approval_granted(mut self) -> Self {
self.approval_granted = true;
self
}
pub fn approval_granted(&self) -> bool {
self.approval_granted
}
}
impl From<ResponseCacheScope> for crank_core::ResponseCacheScope {
@@ -154,6 +165,7 @@ mod tests {
assert_eq!(context.correlation_id, "req_123");
assert!(context.response_cache_scope.is_none());
assert!(context.confirmation_token.is_none());
assert!(!context.approval_granted());
}
#[test]
@@ -187,4 +199,11 @@ mod tests {
assert_eq!(context.confirmation_token(), Some("ct_123"));
}
#[test]
fn records_prior_human_approval() {
let context = RuntimeRequestContext::from_request_id("req_123").with_approval_granted();
assert!(context.approval_granted());
}
}
@@ -75,6 +75,18 @@ async fn destructive_operation_requires_single_use_confirmation() {
RuntimeError::InvalidConfirmationToken { .. }
));
assert_eq!(call_count.load(Ordering::SeqCst), 1);
let approved_context = context.with_approval_granted();
let approved = executor
.execute_with_context(
&operation,
&json!({ "order_id": "ord_123" }),
Some(&approved_context),
)
.await
.unwrap();
assert_eq!(approved, json!({ "deleted": true }));
assert_eq!(call_count.load(Ordering::SeqCst), 2);
}
struct CountingAdapter {