Skip to main content

forge_memory_bridge/
error.rs

1//! Error types for the bridge crate.
2
3use semantic_memory_forge::ExportEnvelopeError;
4use thiserror::Error;
5
6/// Errors produced by the forge-memory-bridge.
7#[derive(Debug, Error)]
8pub enum BridgeError {
9    /// The export envelope is structurally invalid.
10    #[error("invalid envelope: {reason}")]
11    InvalidEnvelope { reason: String },
12
13    /// Schema version mismatch.
14    #[error("incompatible version: expected {expected}, got {actual}")]
15    IncompatibleVersion { expected: String, actual: String },
16
17    /// Content digest does not match computed value.
18    #[error("digest mismatch: expected {expected}, got {actual}")]
19    DigestMismatch { expected: String, actual: String },
20
21    /// Failed to compute content digest.
22    #[error("digest computation failed: {reason}")]
23    DigestComputationFailed { reason: String },
24
25    /// A record in the envelope is malformed.
26    #[error("invalid record: {reason}")]
27    InvalidRecord { reason: String },
28
29    /// Transformation from export to import failed.
30    #[error("transform failed: {reason}")]
31    TransformFailed { reason: String },
32
33    /// A legacy import record is missing an episode identity that the bridge
34    /// cannot synthesize without violating the episode-first identity law.
35    #[error("missing episode identity in legacy import: {record_context}")]
36    MissingEpisodeIdentity { record_context: String },
37}
38
39impl BridgeError {
40    /// Stable error kind discriminant.
41    pub fn kind(&self) -> &'static str {
42        match self {
43            Self::InvalidEnvelope { .. } => "invalid_envelope",
44            Self::IncompatibleVersion { .. } => "incompatible_version",
45            Self::DigestMismatch { .. } => "digest_mismatch",
46            Self::DigestComputationFailed { .. } => "digest_computation_failed",
47            Self::InvalidRecord { .. } => "invalid_record",
48            Self::TransformFailed { .. } => "transform_failed",
49            Self::MissingEpisodeIdentity { .. } => "missing_episode_identity",
50        }
51    }
52}
53
54/// First-class bridge import failure artifact for replayability and audit.
55///
56/// LIB-C005: Bridge failures must become replayable artifacts, not just logged errors.
57/// This artifact captures the envelope identity, error classification, and provenance
58/// required to diagnose and replay failed imports.
59#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
60pub struct BridgeImportFailureArtifact {
61    /// Stable schema version for this artifact family.
62    pub schema_version: String,
63    /// Source envelope ID that failed to import.
64    pub source_envelope_id: String,
65    /// Source authority (e.g. "forge").
66    pub source_authority: String,
67    /// Scope namespace of the attempted import.
68    pub scope_namespace: String,
69    /// Machine-readable error kind (matches `BridgeError::kind()`).
70    pub error_kind: String,
71    /// Human-readable error description.
72    pub error_message: String,
73    /// When the failure occurred.
74    pub failed_at: String,
75}
76
77/// Schema version constant for `BridgeImportFailureArtifact`.
78pub const BRIDGE_IMPORT_FAILURE_ARTIFACT_V1_SCHEMA: &str = "bridge_import_failure_artifact_v1";
79
80impl BridgeImportFailureArtifact {
81    /// Constructs a failure artifact from a `BridgeError` and provenance context.
82    pub fn from_error(
83        error: &BridgeError,
84        source_envelope_id: &str,
85        source_authority: &str,
86        scope_namespace: &str,
87    ) -> Self {
88        Self {
89            schema_version: BRIDGE_IMPORT_FAILURE_ARTIFACT_V1_SCHEMA.into(),
90            source_envelope_id: source_envelope_id.into(),
91            source_authority: source_authority.into(),
92            scope_namespace: scope_namespace.into(),
93            error_kind: error.kind().into(),
94            error_message: error.to_string(),
95            failed_at: chrono::Utc::now().to_rfc3339(),
96        }
97    }
98}
99
100impl From<ExportEnvelopeError> for BridgeError {
101    fn from(value: ExportEnvelopeError) -> Self {
102        match value {
103            ExportEnvelopeError::InvalidEnvelope { reason } => Self::InvalidEnvelope { reason },
104            ExportEnvelopeError::IncompatibleVersion { expected, actual } => {
105                Self::IncompatibleVersion { expected, actual }
106            }
107            ExportEnvelopeError::DigestMismatch { expected, actual } => {
108                Self::DigestMismatch { expected, actual }
109            }
110            ExportEnvelopeError::DigestComputationFailed { reason } => {
111                Self::DigestComputationFailed { reason }
112            }
113        }
114    }
115}