feat: complete Epic 1 production foundation
This commit is contained in:
@@ -0,0 +1,521 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::{AgentId, CorrelationContext, OperationId};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecutionOrigin {
|
||||
AdminDraft,
|
||||
AgentSnapshot,
|
||||
}
|
||||
|
||||
impl ExecutionOrigin {
|
||||
pub fn validate_agent(self, agent_id: Option<&AgentId>) -> Result<(), ExecutionOriginError> {
|
||||
match (self, agent_id) {
|
||||
(Self::AdminDraft, None) | (Self::AgentSnapshot, Some(_)) => Ok(()),
|
||||
(Self::AdminDraft, Some(_)) => Err(ExecutionOriginError::UnexpectedAgent),
|
||||
(Self::AgentSnapshot, None) => Err(ExecutionOriginError::MissingAgent),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum ExecutionOriginError {
|
||||
#[error("admin draft execution cannot carry an agent identity")]
|
||||
UnexpectedAgent,
|
||||
#[error("agent snapshot execution requires an agent identity")]
|
||||
MissingAgent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecutionStage {
|
||||
Authorization,
|
||||
InputSchema,
|
||||
InputMapping,
|
||||
RequestPreparation,
|
||||
Admission,
|
||||
Adapter,
|
||||
Upstream,
|
||||
OutputMapping,
|
||||
OutputSchema,
|
||||
MandatoryPersistence,
|
||||
Runtime,
|
||||
}
|
||||
|
||||
impl ExecutionStage {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Authorization => "authorization",
|
||||
Self::InputSchema => "input_schema",
|
||||
Self::InputMapping => "input_mapping",
|
||||
Self::RequestPreparation => "request_preparation",
|
||||
Self::Admission => "admission",
|
||||
Self::Adapter => "adapter",
|
||||
Self::Upstream => "upstream",
|
||||
Self::OutputMapping => "output_mapping",
|
||||
Self::OutputSchema => "output_schema",
|
||||
Self::MandatoryPersistence => "mandatory_persistence",
|
||||
Self::Runtime => "runtime",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Retryability {
|
||||
Never,
|
||||
Safe,
|
||||
AfterDelay,
|
||||
ManualReconcile,
|
||||
RequiresConfirmation,
|
||||
}
|
||||
|
||||
impl Retryability {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Never => "never",
|
||||
Self::Safe => "safe",
|
||||
Self::AfterDelay => "after_delay",
|
||||
Self::ManualReconcile => "manual_reconcile",
|
||||
Self::RequiresConfirmation => "requires_confirmation",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OutcomeCertainty {
|
||||
Certain,
|
||||
OutcomeUnknown,
|
||||
}
|
||||
|
||||
impl OutcomeCertainty {
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::Certain => "certain",
|
||||
Self::OutcomeUnknown => "outcome_unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecutionErrorCode {
|
||||
AuthorizationDenied,
|
||||
AuthProfileNotFound,
|
||||
SecretNotFound,
|
||||
SecretInvalid,
|
||||
InputSchemaInvalid,
|
||||
InputMappingInvalid,
|
||||
PreparedRequestInvalid,
|
||||
ExecutionOverloaded,
|
||||
SafetyStoreUnavailable,
|
||||
ProtocolUnsupported,
|
||||
ExecutionModeUnsupported,
|
||||
AdapterConfigurationInvalid,
|
||||
OutboundTargetRejected,
|
||||
UpstreamAuthError,
|
||||
UpstreamNotFound,
|
||||
UpstreamRateLimited,
|
||||
UpstreamServerError,
|
||||
UpstreamStatusError,
|
||||
UpstreamTimeout,
|
||||
UpstreamTransportError,
|
||||
UpstreamRequestTooLarge,
|
||||
UpstreamResponseTooLarge,
|
||||
OutputMappingInvalid,
|
||||
OutputSchemaInvalid,
|
||||
PersistenceUnavailable,
|
||||
RuntimeInternal,
|
||||
ConfirmationRequired,
|
||||
ConfirmationInvalid,
|
||||
IdempotencyInProgress,
|
||||
IdempotencyConflict,
|
||||
IdempotencyOutcomeUnknown,
|
||||
}
|
||||
|
||||
impl ExecutionErrorCode {
|
||||
pub const ALL: [Self; 31] = [
|
||||
Self::AuthorizationDenied,
|
||||
Self::AuthProfileNotFound,
|
||||
Self::SecretNotFound,
|
||||
Self::SecretInvalid,
|
||||
Self::InputSchemaInvalid,
|
||||
Self::InputMappingInvalid,
|
||||
Self::PreparedRequestInvalid,
|
||||
Self::ExecutionOverloaded,
|
||||
Self::SafetyStoreUnavailable,
|
||||
Self::ProtocolUnsupported,
|
||||
Self::ExecutionModeUnsupported,
|
||||
Self::AdapterConfigurationInvalid,
|
||||
Self::OutboundTargetRejected,
|
||||
Self::UpstreamAuthError,
|
||||
Self::UpstreamNotFound,
|
||||
Self::UpstreamRateLimited,
|
||||
Self::UpstreamServerError,
|
||||
Self::UpstreamStatusError,
|
||||
Self::UpstreamTimeout,
|
||||
Self::UpstreamTransportError,
|
||||
Self::UpstreamRequestTooLarge,
|
||||
Self::UpstreamResponseTooLarge,
|
||||
Self::OutputMappingInvalid,
|
||||
Self::OutputSchemaInvalid,
|
||||
Self::PersistenceUnavailable,
|
||||
Self::RuntimeInternal,
|
||||
Self::ConfirmationRequired,
|
||||
Self::ConfirmationInvalid,
|
||||
Self::IdempotencyInProgress,
|
||||
Self::IdempotencyConflict,
|
||||
Self::IdempotencyOutcomeUnknown,
|
||||
];
|
||||
pub const fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::AuthorizationDenied => "authorization_denied",
|
||||
Self::AuthProfileNotFound => "auth_profile_not_found",
|
||||
Self::SecretNotFound => "secret_not_found",
|
||||
Self::SecretInvalid => "secret_invalid",
|
||||
Self::InputSchemaInvalid => "input_schema_invalid",
|
||||
Self::InputMappingInvalid => "input_mapping_invalid",
|
||||
Self::PreparedRequestInvalid => "prepared_request_invalid",
|
||||
Self::ExecutionOverloaded => "execution_overloaded",
|
||||
Self::SafetyStoreUnavailable => "safety_store_unavailable",
|
||||
Self::ProtocolUnsupported => "protocol_unsupported",
|
||||
Self::ExecutionModeUnsupported => "execution_mode_unsupported",
|
||||
Self::AdapterConfigurationInvalid => "adapter_configuration_invalid",
|
||||
Self::OutboundTargetRejected => "outbound_target_rejected",
|
||||
Self::UpstreamAuthError => "upstream_auth_error",
|
||||
Self::UpstreamNotFound => "upstream_not_found",
|
||||
Self::UpstreamRateLimited => "upstream_rate_limited",
|
||||
Self::UpstreamServerError => "upstream_server_error",
|
||||
Self::UpstreamStatusError => "upstream_status_error",
|
||||
Self::UpstreamTimeout => "upstream_timeout",
|
||||
Self::UpstreamTransportError => "upstream_transport_error",
|
||||
Self::UpstreamRequestTooLarge => "upstream_request_too_large",
|
||||
Self::UpstreamResponseTooLarge => "upstream_response_too_large",
|
||||
Self::OutputMappingInvalid => "output_mapping_invalid",
|
||||
Self::OutputSchemaInvalid => "output_schema_invalid",
|
||||
Self::PersistenceUnavailable => "persistence_unavailable",
|
||||
Self::RuntimeInternal => "runtime_internal",
|
||||
Self::ConfirmationRequired => "confirmation_required",
|
||||
Self::ConfirmationInvalid => "confirmation_invalid",
|
||||
Self::IdempotencyInProgress => "idempotency_in_progress",
|
||||
Self::IdempotencyConflict => "idempotency_conflict",
|
||||
Self::IdempotencyOutcomeUnknown => "idempotency_outcome_unknown",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn stage(self) -> ExecutionStage {
|
||||
match self {
|
||||
Self::AuthorizationDenied
|
||||
| Self::AuthProfileNotFound
|
||||
| Self::SecretNotFound
|
||||
| Self::SecretInvalid => ExecutionStage::Authorization,
|
||||
Self::InputSchemaInvalid => ExecutionStage::InputSchema,
|
||||
Self::InputMappingInvalid => ExecutionStage::InputMapping,
|
||||
Self::PreparedRequestInvalid => ExecutionStage::RequestPreparation,
|
||||
Self::ExecutionOverloaded
|
||||
| Self::SafetyStoreUnavailable
|
||||
| Self::ConfirmationRequired
|
||||
| Self::ConfirmationInvalid
|
||||
| Self::IdempotencyInProgress
|
||||
| Self::IdempotencyConflict
|
||||
| Self::IdempotencyOutcomeUnknown => ExecutionStage::Admission,
|
||||
Self::ProtocolUnsupported
|
||||
| Self::ExecutionModeUnsupported
|
||||
| Self::AdapterConfigurationInvalid
|
||||
| Self::OutboundTargetRejected => ExecutionStage::Adapter,
|
||||
Self::UpstreamRequestTooLarge => ExecutionStage::RequestPreparation,
|
||||
Self::UpstreamAuthError
|
||||
| Self::UpstreamNotFound
|
||||
| Self::UpstreamRateLimited
|
||||
| Self::UpstreamServerError
|
||||
| Self::UpstreamStatusError
|
||||
| Self::UpstreamTimeout
|
||||
| Self::UpstreamTransportError
|
||||
| Self::UpstreamResponseTooLarge => ExecutionStage::Upstream,
|
||||
Self::OutputMappingInvalid => ExecutionStage::OutputMapping,
|
||||
Self::OutputSchemaInvalid => ExecutionStage::OutputSchema,
|
||||
Self::PersistenceUnavailable => ExecutionStage::MandatoryPersistence,
|
||||
Self::RuntimeInternal => ExecutionStage::Runtime,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn retryability(self) -> Retryability {
|
||||
match self {
|
||||
Self::ExecutionOverloaded
|
||||
| Self::SafetyStoreUnavailable
|
||||
| Self::UpstreamRateLimited
|
||||
| Self::UpstreamServerError
|
||||
| Self::UpstreamTimeout
|
||||
| Self::UpstreamTransportError
|
||||
| Self::PersistenceUnavailable
|
||||
| Self::IdempotencyInProgress => Retryability::AfterDelay,
|
||||
Self::ConfirmationRequired => Retryability::RequiresConfirmation,
|
||||
Self::IdempotencyOutcomeUnknown => Retryability::ManualReconcile,
|
||||
_ => Retryability::Never,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outcome_certainty(self) -> OutcomeCertainty {
|
||||
match self {
|
||||
Self::IdempotencyOutcomeUnknown => OutcomeCertainty::OutcomeUnknown,
|
||||
_ => OutcomeCertainty::Certain,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn message(self, locale: ExecutionLocale) -> &'static str {
|
||||
match locale {
|
||||
ExecutionLocale::Ru => self.message_ru(),
|
||||
ExecutionLocale::En => self.message_en(),
|
||||
}
|
||||
}
|
||||
|
||||
const fn message_ru(self) -> &'static str {
|
||||
match self {
|
||||
Self::AuthorizationDenied => "Выполнение операции запрещено.",
|
||||
Self::AuthProfileNotFound => "Профиль авторизации не найден.",
|
||||
Self::SecretNotFound => "Секрет авторизации не найден.",
|
||||
Self::SecretInvalid => "Секрет авторизации имеет неподходящий формат.",
|
||||
Self::InputSchemaInvalid => "Входные параметры не прошли проверку схемы.",
|
||||
Self::InputMappingInvalid => "Не удалось сопоставить входные параметры.",
|
||||
Self::PreparedRequestInvalid => "Не удалось подготовить корректный API-запрос.",
|
||||
Self::ExecutionOverloaded => "Сервис временно перегружен.",
|
||||
Self::SafetyStoreUnavailable => "Обязательное хранилище безопасности недоступно.",
|
||||
Self::ProtocolUnsupported => "Протокол операции не поддерживается.",
|
||||
Self::ExecutionModeUnsupported => "Режим выполнения не поддерживается.",
|
||||
Self::AdapterConfigurationInvalid => "Конфигурация адаптера некорректна.",
|
||||
Self::OutboundTargetRejected => "Целевой адрес отклонён политикой безопасности.",
|
||||
Self::UpstreamAuthError => "Внешний API отклонил авторизацию.",
|
||||
Self::UpstreamNotFound => "Ресурс внешнего API не найден.",
|
||||
Self::UpstreamRateLimited => "Внешний API ограничил частоту запросов.",
|
||||
Self::UpstreamServerError => "Внешний API временно недоступен.",
|
||||
Self::UpstreamStatusError => "Внешний API вернул ошибочный статус.",
|
||||
Self::UpstreamTimeout => "Истекло время ожидания внешнего API.",
|
||||
Self::UpstreamTransportError => "Не удалось подключиться к внешнему API.",
|
||||
Self::UpstreamRequestTooLarge => "Запрос во внешний API превышает лимит.",
|
||||
Self::UpstreamResponseTooLarge => "Ответ внешнего API превышает лимит.",
|
||||
Self::OutputMappingInvalid => "Не удалось сопоставить ответ внешнего API.",
|
||||
Self::OutputSchemaInvalid => "Ответ не прошёл проверку схемы.",
|
||||
Self::PersistenceUnavailable => "Обязательное сохранение результата недоступно.",
|
||||
Self::RuntimeInternal => "Внутренняя ошибка выполнения.",
|
||||
Self::ConfirmationRequired => "Операция требует подтверждения.",
|
||||
Self::ConfirmationInvalid => "Подтверждение недействительно или истекло.",
|
||||
Self::IdempotencyInProgress => "Операция с этим ключом уже выполняется.",
|
||||
Self::IdempotencyConflict => "Ключ идемпотентности использован с другими параметрами.",
|
||||
Self::IdempotencyOutcomeUnknown => {
|
||||
"Результат предыдущего выполнения неизвестен; автоматический повтор запрещён."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn message_en(self) -> &'static str {
|
||||
match self {
|
||||
Self::AuthorizationDenied => "Operation execution is denied.",
|
||||
Self::AuthProfileNotFound => "Authorization profile was not found.",
|
||||
Self::SecretNotFound => "Authorization secret was not found.",
|
||||
Self::SecretInvalid => "Authorization secret has an invalid format.",
|
||||
Self::InputSchemaInvalid => "Input does not satisfy the operation schema.",
|
||||
Self::InputMappingInvalid => "Input parameters could not be mapped.",
|
||||
Self::PreparedRequestInvalid => "A valid upstream request could not be prepared.",
|
||||
Self::ExecutionOverloaded => "The service is temporarily overloaded.",
|
||||
Self::SafetyStoreUnavailable => "A mandatory safety store is unavailable.",
|
||||
Self::ProtocolUnsupported => "The operation protocol is unsupported.",
|
||||
Self::ExecutionModeUnsupported => "The execution mode is unsupported.",
|
||||
Self::AdapterConfigurationInvalid => "The adapter configuration is invalid.",
|
||||
Self::OutboundTargetRejected => "The target was rejected by the safety policy.",
|
||||
Self::UpstreamAuthError => "The upstream API rejected authorization.",
|
||||
Self::UpstreamNotFound => "The upstream resource was not found.",
|
||||
Self::UpstreamRateLimited => "The upstream API rate-limited the request.",
|
||||
Self::UpstreamServerError => "The upstream API is temporarily unavailable.",
|
||||
Self::UpstreamStatusError => "The upstream API returned an error status.",
|
||||
Self::UpstreamTimeout => "The upstream API timed out.",
|
||||
Self::UpstreamTransportError => "The upstream API could not be reached.",
|
||||
Self::UpstreamRequestTooLarge => "The upstream request exceeded its limit.",
|
||||
Self::UpstreamResponseTooLarge => "The upstream response exceeded its limit.",
|
||||
Self::OutputMappingInvalid => "The upstream response could not be mapped.",
|
||||
Self::OutputSchemaInvalid => "The output does not satisfy the operation schema.",
|
||||
Self::PersistenceUnavailable => "Mandatory result persistence is unavailable.",
|
||||
Self::RuntimeInternal => "Internal execution failure.",
|
||||
Self::ConfirmationRequired => "The operation requires confirmation.",
|
||||
Self::ConfirmationInvalid => "The confirmation is invalid or expired.",
|
||||
Self::IdempotencyInProgress => "The operation is already running for this key.",
|
||||
Self::IdempotencyConflict => "The idempotency key was used with different input.",
|
||||
Self::IdempotencyOutcomeUnknown => {
|
||||
"The previous outcome is unknown; automatic retry is unsafe."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ExecutionLocale {
|
||||
Ru,
|
||||
En,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ConfirmationChallenge {
|
||||
token: String,
|
||||
expires_in_ms: u64,
|
||||
}
|
||||
|
||||
impl ConfirmationChallenge {
|
||||
fn try_new(
|
||||
token: impl Into<String>,
|
||||
expires_in_ms: u64,
|
||||
) -> Result<Self, ExecutionFailureBuildError> {
|
||||
let token = token.into();
|
||||
if token.is_empty() || token.len() > 4_096 || token.chars().any(char::is_control) {
|
||||
return Err(ExecutionFailureBuildError::InvalidConfirmation);
|
||||
}
|
||||
if !(1..=3_600_000).contains(&expires_in_ms) {
|
||||
return Err(ExecutionFailureBuildError::InvalidConfirmation);
|
||||
}
|
||||
Ok(Self {
|
||||
token,
|
||||
expires_in_ms,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn token(&self) -> &str {
|
||||
&self.token
|
||||
}
|
||||
|
||||
pub fn expires_in_ms(&self) -> u64 {
|
||||
self.expires_in_ms
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum ExecutionFailureBuildError {
|
||||
#[error("failure metadata is incompatible with the execution error code")]
|
||||
IncompatibleMetadata,
|
||||
#[error("confirmation metadata is invalid or exceeds its bound")]
|
||||
InvalidConfirmation,
|
||||
#[error("retry metadata is invalid or exceeds its bound")]
|
||||
InvalidRetryAfter,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ConfirmationChallenge {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter
|
||||
.debug_struct("ConfirmationChallenge")
|
||||
.field("token", &"[REDACTED]")
|
||||
.field("expires_in_ms", &self.expires_in_ms)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ExecutionFailure {
|
||||
code: ExecutionErrorCode,
|
||||
retryability: Retryability,
|
||||
outcome_certainty: OutcomeCertainty,
|
||||
correlation: CorrelationContext,
|
||||
upstream_status: Option<u16>,
|
||||
retry_after_ms: Option<u64>,
|
||||
confirmation: Option<ConfirmationChallenge>,
|
||||
}
|
||||
|
||||
impl ExecutionFailure {
|
||||
pub fn new(code: ExecutionErrorCode, correlation: CorrelationContext) -> Self {
|
||||
Self {
|
||||
code,
|
||||
retryability: code.retryability(),
|
||||
outcome_certainty: code.outcome_certainty(),
|
||||
correlation,
|
||||
upstream_status: None,
|
||||
retry_after_ms: None,
|
||||
confirmation: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error_code(&self) -> ExecutionErrorCode {
|
||||
self.code
|
||||
}
|
||||
|
||||
pub fn stage(&self) -> ExecutionStage {
|
||||
self.code.stage()
|
||||
}
|
||||
|
||||
pub fn retryability(&self) -> Retryability {
|
||||
self.retryability
|
||||
}
|
||||
|
||||
pub fn outcome_certainty(&self) -> OutcomeCertainty {
|
||||
self.outcome_certainty
|
||||
}
|
||||
|
||||
pub fn correlation(&self) -> &CorrelationContext {
|
||||
&self.correlation
|
||||
}
|
||||
|
||||
pub fn upstream_status(&self) -> Option<u16> {
|
||||
self.upstream_status
|
||||
}
|
||||
|
||||
pub fn retry_after_ms(&self) -> Option<u64> {
|
||||
self.retry_after_ms
|
||||
}
|
||||
|
||||
pub fn confirmation(&self) -> Option<&ConfirmationChallenge> {
|
||||
self.confirmation.as_ref()
|
||||
}
|
||||
|
||||
pub fn with_upstream_status(mut self, status: u16) -> Self {
|
||||
self.upstream_status = (100..=599).contains(&status).then_some(status);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn try_with_retry_after_ms(
|
||||
mut self,
|
||||
retry_after_ms: u64,
|
||||
) -> Result<Self, ExecutionFailureBuildError> {
|
||||
if self.retryability != Retryability::AfterDelay {
|
||||
return Err(ExecutionFailureBuildError::IncompatibleMetadata);
|
||||
}
|
||||
if !(1..=3_600_000).contains(&retry_after_ms) {
|
||||
return Err(ExecutionFailureBuildError::InvalidRetryAfter);
|
||||
}
|
||||
self.retry_after_ms = Some(retry_after_ms);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn try_with_confirmation(
|
||||
mut self,
|
||||
token: impl Into<String>,
|
||||
expires_in_ms: u64,
|
||||
) -> Result<Self, ExecutionFailureBuildError> {
|
||||
if self.code != ExecutionErrorCode::ConfirmationRequired {
|
||||
return Err(ExecutionFailureBuildError::IncompatibleMetadata);
|
||||
}
|
||||
self.confirmation = Some(ConfirmationChallenge::try_new(token, expires_in_ms)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn with_dispatch_uncertainty(mut self) -> Self {
|
||||
if matches!(
|
||||
self.code,
|
||||
ExecutionErrorCode::UpstreamTimeout
|
||||
| ExecutionErrorCode::UpstreamTransportError
|
||||
| ExecutionErrorCode::IdempotencyOutcomeUnknown
|
||||
| ExecutionErrorCode::PersistenceUnavailable
|
||||
) {
|
||||
self.retryability = Retryability::ManualReconcile;
|
||||
self.outcome_certainty = OutcomeCertainty::OutcomeUnknown;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExecutionSuccess {
|
||||
pub operation_id: OperationId,
|
||||
pub operation_version: u32,
|
||||
pub origin: ExecutionOrigin,
|
||||
pub correlation: CorrelationContext,
|
||||
pub request_preview: Value,
|
||||
pub output: Value,
|
||||
}
|
||||
Reference in New Issue
Block a user