1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// =============================================================================
// #######
// ### ### F: envelope.rs
// ## ## ## ## P: AppCore-Runtime
// ## ##
// C: 2026/05/29 20:47:35 by dnettoRaw
// ## ## ## ## U: 2026/06/04 11:51:29 by dnettoRaw
// ########### S: 0.6.0
// =============================================================================
//! Transport-neutral command/event envelope contracts.
//! These are passed to command handlers and returned in command results.
use crate::error::RuntimeResult;
use crate::ids::{validate_identifier, AppId, CommandName, EventName, NodeId};
use crate::trace::TraceContext;
/// Immutable command envelope.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandEnvelope {
/// Stable command name.
pub command_name: CommandName,
/// Unique command identity.
pub command_id: String,
/// Application issuing the command.
pub app_id: AppId,
/// Node issuing the command.
pub node_id: NodeId,
/// Issue timestamp in Unix milliseconds.
pub issued_at_ms: u64,
/// Optional key used to deduplicate mutating commands.
pub idempotency_key: Option<String>,
/// Opaque application-owned payload.
pub payload: Vec<u8>,
/// Optional distributed trace context.
pub trace: Option<TraceContext>,
}
impl CommandEnvelope {
/// Creates and validates a command envelope.
pub fn new(
command_name: CommandName,
command_id: String,
app_id: AppId,
node_id: NodeId,
issued_at_ms: u64,
idempotency_key: Option<String>,
payload: Vec<u8>,
) -> RuntimeResult<Self> {
validate_identifier("CommandId", &command_id)?;
if let Some(key) = &idempotency_key {
validate_identifier("IdempotencyKey", key)?;
}
command_name.validate()?;
app_id.validate()?;
node_id.validate()?;
Ok(Self {
command_name,
command_id,
app_id,
node_id,
issued_at_ms,
idempotency_key,
payload,
trace: None,
})
}
/// Attaches distributed trace context.
pub fn with_trace(mut self, trace: TraceContext) -> Self {
self.trace = Some(trace);
self
}
/// Returns the command name.
pub fn command_name(&self) -> &CommandName {
&self.command_name
}
/// Returns opaque command payload bytes.
pub fn payload(&self) -> &[u8] {
&self.payload
}
}
/// Immutable event envelope.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct EventEnvelope {
/// Stable event name.
pub event_name: EventName,
/// Unique event identity.
pub event_id: String,
/// Application that emitted the event.
pub app_id: AppId,
/// Node that emitted the event.
pub node_id: NodeId,
/// Occurrence timestamp in Unix milliseconds.
pub occurred_at_ms: u64,
/// Opaque application-owned payload.
pub payload: Vec<u8>,
/// Optional distributed trace context.
pub trace: Option<TraceContext>,
}
impl EventEnvelope {
/// Creates and validates an event envelope.
pub fn new(
event_name: EventName,
event_id: String,
app_id: AppId,
node_id: NodeId,
occurred_at_ms: u64,
payload: Vec<u8>,
) -> RuntimeResult<Self> {
validate_identifier("EventId", &event_id)?;
event_name.validate()?;
app_id.validate()?;
node_id.validate()?;
Ok(Self {
event_name,
event_id,
app_id,
node_id,
occurred_at_ms,
payload,
trace: None,
})
}
/// Attaches distributed trace context.
pub fn with_trace(mut self, trace: TraceContext) -> Self {
self.trace = Some(trace);
self
}
/// Returns the event name.
pub fn event_name(&self) -> &EventName {
&self.event_name
}
/// Returns opaque event payload bytes.
pub fn payload(&self) -> &[u8] {
&self.payload
}
}
#[cfg(test)]
#[path = "envelope_tests.rs"]
mod tests;