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
//! IPC protocol between the daemon and its clients (CLI, TUI, Telegram).
//!
//! Why: A versioned request/response envelope lets clients evolve independently
//! of the daemon and gives a single place to enforce protocol compatibility.
//! What: Defines `Request`, `Response`, and the wire `Envelope` carrying a
//! protocol version. Transport is JSON over a local HTTP API (axum).
//! Test: `cargo test -p trusty-mpm-core` round-trips each variant through JSON.
use serde::{Deserialize, Serialize};
use crate::core::session::{Session, SessionId};
/// Current IPC protocol version. Bump on any breaking envelope change.
pub const PROTOCOL_VERSION: u32 = 1;
/// A command sent from a client to the daemon.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Request {
/// Health/liveness probe.
Ping,
/// List all managed sessions.
ListSessions,
/// Start a new session in the given working directory.
StartSession { workdir: String },
/// Stop a running session.
StopSession { id: SessionId },
/// Approve or deny a pending permission request.
ResolveApproval { id: SessionId, approved: bool },
}
/// A reply sent from the daemon to a client.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Response {
/// Reply to `Ping`.
Pong,
/// Reply carrying a session list.
Sessions { sessions: Vec<Session> },
/// Reply confirming a session was created.
SessionStarted { session: Session },
/// Generic acknowledgement.
Ok,
/// The request failed; carries a human-readable reason.
Error { message: String },
}
/// Versioned wire envelope wrapping any payload `T`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Envelope<T> {
/// Protocol version the sender speaks.
pub version: u32,
/// The wrapped payload.
pub payload: T,
}
impl<T> Envelope<T> {
/// Wrap a payload with the current protocol version.
pub fn new(payload: T) -> Self {
Self {
version: PROTOCOL_VERSION,
payload,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_roundtrip() {
let req = Request::StartSession {
workdir: "/tmp/x".into(),
};
let env = Envelope::new(req);
let json = serde_json::to_string(&env).unwrap();
let back: Envelope<Request> = serde_json::from_str(&json).unwrap();
assert_eq!(back.version, PROTOCOL_VERSION);
assert!(matches!(back.payload, Request::StartSession { .. }));
}
#[test]
fn response_error_roundtrip() {
let resp = Response::Error {
message: "no such session".into(),
};
let json = serde_json::to_string(&resp).unwrap();
let back: Response = serde_json::from_str(&json).unwrap();
assert!(matches!(back, Response::Error { .. }));
}
}