swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/crates/ra-mcp-domain/src/fault.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra-mcp-domain/src/fault.rs')
-rw-r--r--crates/ra-mcp-domain/src/fault.rs129
1 files changed, 129 insertions, 0 deletions
diff --git a/crates/ra-mcp-domain/src/fault.rs b/crates/ra-mcp-domain/src/fault.rs
new file mode 100644
index 0000000..6d404ab
--- /dev/null
+++ b/crates/ra-mcp-domain/src/fault.rs
@@ -0,0 +1,129 @@
+//! Fault taxonomy and recovery guidance.
+
+use crate::types::Generation;
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+/// Logical fault class.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum FaultClass {
+ /// Underlying I/O or transport channel failure.
+ Transport,
+ /// Child process startup/liveness/exiting failures.
+ Process,
+ /// Malformed or unexpected protocol payloads.
+ Protocol,
+ /// Deadline exceeded.
+ Timeout,
+ /// Internal resource budget exhaustion.
+ Resource,
+}
+
+/// Fine-grained fault code.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum FaultCode {
+ /// Pipe write failed with `EPIPE`.
+ BrokenPipe,
+ /// Pipe reached EOF.
+ UnexpectedEof,
+ /// Child process exited unexpectedly.
+ ChildExited,
+ /// Child process failed to spawn.
+ SpawnFailed,
+ /// Startup sequence exceeded deadline.
+ StartupTimedOut,
+ /// Request exceeded deadline.
+ RequestTimedOut,
+ /// Received an invalid protocol frame.
+ InvalidFrame,
+ /// Received invalid JSON.
+ InvalidJson,
+ /// Response could not be correlated with a pending request.
+ UnknownResponseId,
+}
+
+/// Recovery strategy for a fault.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
+pub enum RecoveryDirective {
+ /// Retry the request on the same process.
+ RetryInPlace,
+ /// Restart the worker process and retry once.
+ RestartAndReplay,
+ /// Fail-fast and bubble to the caller.
+ AbortRequest,
+}
+
+/// Structured fault event.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct Fault {
+ /// Generation in which this fault happened.
+ pub generation: Generation,
+ /// Broad fault class.
+ pub class: FaultClass,
+ /// Specific fault code.
+ pub code: FaultCode,
+ /// Caller-facing context.
+ pub detail: FaultDetail,
+}
+
+impl Fault {
+ /// Constructs a new fault.
+ #[must_use]
+ pub fn new(
+ generation: Generation,
+ class: FaultClass,
+ code: FaultCode,
+ detail: FaultDetail,
+ ) -> Self {
+ Self {
+ generation,
+ class,
+ code,
+ detail,
+ }
+ }
+
+ /// Returns the default recovery directive for this fault.
+ #[must_use]
+ pub fn directive(&self) -> RecoveryDirective {
+ match (self.class, self.code) {
+ (FaultClass::Transport, FaultCode::BrokenPipe)
+ | (FaultClass::Transport, FaultCode::UnexpectedEof)
+ | (FaultClass::Process, FaultCode::ChildExited)
+ | (FaultClass::Process, FaultCode::SpawnFailed)
+ | (FaultClass::Timeout, FaultCode::StartupTimedOut) => {
+ RecoveryDirective::RestartAndReplay
+ }
+ (FaultClass::Timeout, FaultCode::RequestTimedOut) => {
+ RecoveryDirective::RestartAndReplay
+ }
+ (FaultClass::Protocol, FaultCode::UnknownResponseId) => RecoveryDirective::RetryInPlace,
+ (FaultClass::Protocol, FaultCode::InvalidFrame)
+ | (FaultClass::Protocol, FaultCode::InvalidJson)
+ | (FaultClass::Resource, _) => RecoveryDirective::AbortRequest,
+ _ => RecoveryDirective::AbortRequest,
+ }
+ }
+}
+
+/// Typed detail payload for a fault.
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct FaultDetail {
+ /// Human-consumable context.
+ pub message: String,
+}
+
+impl FaultDetail {
+ /// Creates a new detail message.
+ #[must_use]
+ pub fn new(message: impl Into<String>) -> Self {
+ Self {
+ message: message.into(),
+ }
+ }
+}
+
+/// Domain fault conversion error.
+#[derive(Debug, Error)]
+#[error("fault conversion failure: {0}")]
+pub struct FaultConversionError(String);