feat: complete Epic 1 production foundation
This commit is contained in:
@@ -9,16 +9,13 @@ use crank_core::{CorrelationContext, RequestId, TraceContext};
|
||||
use crank_registry::{ApprovalRequestRecord, FinishApprovalRequest};
|
||||
use crank_runtime::{RuntimeExecutionRequest, RuntimeRequestContext};
|
||||
use crank_trace::{DbOperation, ErrorCategory, Stage, StageOutcome, observe_db_query};
|
||||
use serde_json::json;
|
||||
use serde_json::{Value, json};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{Instrument, warn};
|
||||
|
||||
use crate::{
|
||||
app::{
|
||||
AgentRoutePath, AppState, InvocationRecord, build_request_preview, persist_invocation,
|
||||
resolve_operation_auth, runtime_operation,
|
||||
},
|
||||
tool_error::{runtime_error_code, safe_runtime_error_message},
|
||||
use crate::app::{
|
||||
AgentRoutePath, AppState, InvocationRecord, persist_invocation, resolve_operation_auth,
|
||||
runtime_operation,
|
||||
};
|
||||
|
||||
const RECOVERY_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5);
|
||||
@@ -184,11 +181,6 @@ pub(super) async fn execute_approved_request(
|
||||
};
|
||||
|
||||
let operation = runtime_operation(&tool);
|
||||
let request_preview = build_request_preview(
|
||||
&state.runtime,
|
||||
&operation,
|
||||
&approval.approval.request_payload,
|
||||
);
|
||||
let started_at = Instant::now();
|
||||
let runtime_request_context = RuntimeRequestContext::from_correlation(&correlation)
|
||||
.with_response_cache_scope(
|
||||
@@ -205,42 +197,86 @@ pub(super) async fn execute_approved_request(
|
||||
resolve_operation_auth(state, &tool.workspace_id, &operation.execution_config).await;
|
||||
let result = match resolved_auth {
|
||||
Ok(resolved_auth) => {
|
||||
state
|
||||
.runtime
|
||||
.execute_request(
|
||||
RuntimeExecutionRequest::new(&operation, &approval.approval.request_payload)
|
||||
.with_optional_auth(resolved_auth.as_ref())
|
||||
.with_context(&runtime_request_context),
|
||||
)
|
||||
.await
|
||||
match RuntimeExecutionRequest::try_new(
|
||||
&tool.workspace_id,
|
||||
crank_core::ExecutionOrigin::AgentSnapshot,
|
||||
Some(&tool.agent_id),
|
||||
&operation,
|
||||
&approval.approval.request_payload,
|
||||
crank_runtime::ExecutionAuthorization::Authorized,
|
||||
resolved_auth.as_ref(),
|
||||
&runtime_request_context,
|
||||
Instant::now()
|
||||
+ std::time::Duration::from_millis(
|
||||
operation.execution_config.timeout_ms.max(1),
|
||||
),
|
||||
) {
|
||||
Ok(request) => state.runtime.execute_outcome(request).await,
|
||||
Err(_) => Err(crank_core::ExecutionFailure::new(
|
||||
crank_core::ExecutionErrorCode::RuntimeInternal,
|
||||
correlation.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
Err(error) => Err(crank_runtime::normalize_runtime_error(&error, &correlation)),
|
||||
};
|
||||
|
||||
let (status, response_payload, invocation_status, invocation_level, message, error_kind) =
|
||||
match result {
|
||||
Ok(output) => (
|
||||
let (
|
||||
status,
|
||||
response_payload,
|
||||
invocation_status,
|
||||
invocation_level,
|
||||
message,
|
||||
error_kind,
|
||||
execution_stage,
|
||||
execution_error_code,
|
||||
retryability,
|
||||
outcome_certainty,
|
||||
upstream_status,
|
||||
request_preview,
|
||||
) = match result {
|
||||
Ok(success) => {
|
||||
let request_preview = success.request_preview;
|
||||
(
|
||||
ApprovalRequestStatus::Completed,
|
||||
output,
|
||||
success.output,
|
||||
InvocationStatus::Ok,
|
||||
InvocationLevel::Info,
|
||||
"approved tool call completed",
|
||||
None,
|
||||
),
|
||||
Err(error) => (
|
||||
ApprovalRequestStatus::Failed,
|
||||
json!({
|
||||
"error": {
|
||||
"code": runtime_error_code(&error),
|
||||
"message": safe_runtime_error_message(&error),
|
||||
}
|
||||
}),
|
||||
InvocationStatus::Error,
|
||||
InvocationLevel::Error,
|
||||
"approved tool call failed",
|
||||
Some(runtime_error_code(&error)),
|
||||
),
|
||||
};
|
||||
Some(crank_core::ExecutionStage::Runtime),
|
||||
None,
|
||||
Some(crank_core::Retryability::Never),
|
||||
Some(crank_core::OutcomeCertainty::Certain),
|
||||
None,
|
||||
request_preview,
|
||||
)
|
||||
}
|
||||
Err(failure) => (
|
||||
ApprovalRequestStatus::Failed,
|
||||
json!({
|
||||
"error": {
|
||||
"code": failure.error_code().as_str(),
|
||||
"message": failure.error_code().message(crank_core::ExecutionLocale::Ru),
|
||||
"stage": failure.stage().as_str(),
|
||||
"retryability": failure.retryability().as_str(),
|
||||
"outcome_certainty": failure.outcome_certainty().as_str(),
|
||||
"request_id": request_id,
|
||||
"trace_id": correlation.trace_id().as_str(),
|
||||
}
|
||||
}),
|
||||
InvocationStatus::Error,
|
||||
InvocationLevel::Error,
|
||||
"approved tool call failed",
|
||||
Some(failure.error_code().as_str()),
|
||||
Some(failure.stage()),
|
||||
Some(failure.error_code()),
|
||||
Some(failure.retryability()),
|
||||
Some(failure.outcome_certainty()),
|
||||
failure.upstream_status(),
|
||||
Value::Null,
|
||||
),
|
||||
};
|
||||
|
||||
persist_invocation(
|
||||
state,
|
||||
@@ -252,8 +288,12 @@ pub(super) async fn execute_approved_request(
|
||||
status: invocation_status,
|
||||
level: invocation_level,
|
||||
message,
|
||||
status_code: None,
|
||||
status_code: upstream_status,
|
||||
error_kind,
|
||||
execution_stage,
|
||||
execution_error_code,
|
||||
retryability,
|
||||
outcome_certainty,
|
||||
duration: started_at.elapsed(),
|
||||
request_preview,
|
||||
response_preview: response_payload.clone(),
|
||||
@@ -275,8 +315,47 @@ pub(super) async fn execute_approved_request(
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())?
|
||||
.ok_or_else(|| StatusCode::CONFLICT.into_response())
|
||||
.map_err(|_| approved_completion_persistence_error(&correlation))?
|
||||
.ok_or_else(|| approved_completion_conflict_error(&correlation))
|
||||
}
|
||||
|
||||
fn approved_completion_conflict_error(correlation: &CorrelationContext) -> Response {
|
||||
(
|
||||
StatusCode::CONFLICT,
|
||||
axum::Json(json!({
|
||||
"error": {
|
||||
"code": "approval_state_conflict",
|
||||
"stage": "mandatory_persistence",
|
||||
"retryability": "manual_reconcile",
|
||||
"outcome_certainty": "outcome_unknown",
|
||||
"request_id": correlation.request_id().as_str(),
|
||||
"trace_id": correlation.trace_id().as_str(),
|
||||
}
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
fn approved_completion_persistence_error(correlation: &CorrelationContext) -> Response {
|
||||
let failure = crank_core::ExecutionFailure::new(
|
||||
crank_core::ExecutionErrorCode::PersistenceUnavailable,
|
||||
correlation.clone(),
|
||||
)
|
||||
.with_dispatch_uncertainty();
|
||||
(
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
axum::Json(json!({
|
||||
"error": {
|
||||
"code": failure.error_code().as_str(),
|
||||
"stage": failure.stage().as_str(),
|
||||
"retryability": failure.retryability().as_str(),
|
||||
"outcome_certainty": failure.outcome_certainty().as_str(),
|
||||
"request_id": failure.correlation().request_id().as_str(),
|
||||
"trace_id": failure.correlation().trace_id().as_str(),
|
||||
}
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
async fn finish_unavailable_approval(
|
||||
|
||||
Reference in New Issue
Block a user