Skip to main content

ldp_protocol/types/
messages.rs

1//! LDP message types.
2//!
3//! The LDP message envelope wraps all protocol messages with routing,
4//! session context, and provenance metadata.
5
6use crate::types::error::LdpError;
7use crate::types::payload::PayloadMode;
8use crate::types::provenance::Provenance;
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12/// LDP message envelope — wraps every protocol message.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct LdpEnvelope {
15    /// Message ID (UUID).
16    pub message_id: String,
17
18    /// Session ID this message belongs to.
19    pub session_id: String,
20
21    /// Sender delegate ID.
22    pub from: String,
23
24    /// Recipient delegate ID.
25    pub to: String,
26
27    /// Message body.
28    pub body: LdpMessageBody,
29
30    /// Payload mode used for this message.
31    pub payload_mode: PayloadMode,
32
33    /// ISO 8601 timestamp.
34    pub timestamp: String,
35
36    /// Optional provenance (attached to results).
37    pub provenance: Option<Provenance>,
38
39    /// HMAC signature of the message (hex-encoded).
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub signature: Option<String>,
42
43    /// Signature algorithm (e.g., "hmac-sha256").
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub signature_algorithm: Option<String>,
46}
47
48/// LDP message body variants.
49///
50/// Maps to the LDP RFC message types. DCI interaction moves are carried
51/// as TASK_SUBMIT payloads (no new message types needed per integration spec).
52#[derive(Debug, Clone, Serialize, Deserialize)]
53#[serde(tag = "type", rename_all = "SCREAMING_SNAKE_CASE")]
54pub enum LdpMessageBody {
55    /// Initial handshake.
56    Hello {
57        delegate_id: String,
58        supported_modes: Vec<PayloadMode>,
59    },
60
61    /// Capability manifest response.
62    CapabilityManifest { capabilities: Value },
63
64    /// Propose a session with configuration.
65    SessionPropose { config: Value },
66
67    /// Accept a proposed session.
68    SessionAccept {
69        session_id: String,
70        negotiated_mode: PayloadMode,
71    },
72
73    /// Reject a proposed session.
74    SessionReject {
75        reason: String,
76        #[serde(skip_serializing_if = "Option::is_none")]
77        error: Option<LdpError>,
78    },
79
80    /// Submit a task within a session.
81    TaskSubmit {
82        task_id: String,
83        skill: String,
84        input: Value,
85    },
86
87    /// Task progress update.
88    TaskUpdate {
89        task_id: String,
90        progress: Option<f32>,
91        message: Option<String>,
92    },
93
94    /// Task result.
95    TaskResult {
96        task_id: String,
97        output: Value,
98        provenance: Provenance,
99    },
100
101    /// Task failure.
102    TaskFailed { task_id: String, error: LdpError },
103
104    /// Task cancellation request.
105    TaskCancel { task_id: String },
106
107    /// Attestation (trust signal).
108    Attestation { claim: Value, evidence: Value },
109
110    /// Session close.
111    SessionClose { reason: Option<String> },
112}
113
114impl LdpEnvelope {
115    /// Create a new envelope with auto-generated message ID and timestamp.
116    pub fn new(
117        session_id: impl Into<String>,
118        from: impl Into<String>,
119        to: impl Into<String>,
120        body: LdpMessageBody,
121        payload_mode: PayloadMode,
122    ) -> Self {
123        Self {
124            message_id: uuid::Uuid::new_v4().to_string(),
125            session_id: session_id.into(),
126            from: from.into(),
127            to: to.into(),
128            body,
129            payload_mode,
130            timestamp: chrono::Utc::now().to_rfc3339(),
131            provenance: None,
132            signature: None,
133            signature_algorithm: None,
134        }
135    }
136}