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