Skip to main content

arcp_core/messages/
session.rs

1//! Session lifecycle messages (RFC §8).
2
3use serde::{Deserialize, Serialize};
4
5use crate::error::ErrorCode;
6use crate::ids::SessionId;
7use crate::messages::Capabilities;
8
9/// `auth.scheme` discriminator (RFC §8.2).
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12#[non_exhaustive]
13pub enum AuthScheme {
14    /// Opaque token validated against the runtime trust store.
15    Bearer,
16    /// Signed JWT with `aud` set to the runtime identity.
17    SignedJwt,
18    /// Anonymous; only valid when `capabilities.anonymous: true` is negotiated.
19    None,
20    /// Mutual TLS established at the transport (v0.2).
21    Mtls,
22    /// `OAuth2` access token (v0.2).
23    Oauth2,
24}
25
26/// Credentials block carried in `session.open`.
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct Credentials {
29    /// Auth scheme.
30    pub scheme: AuthScheme,
31    /// Token payload (omitted for `mtls` and `none`).
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub token: Option<String>,
34}
35
36/// Client identity attestation block (RFC §8.2).
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct ClientIdentity {
39    /// Implementation kind, e.g. `"example-client"`.
40    pub kind: String,
41    /// Implementation version.
42    pub version: String,
43    /// Optional fingerprint (REQUIRED for `mtls`).
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub fingerprint: Option<String>,
46    /// Optional principal identifier.
47    #[serde(default, skip_serializing_if = "Option::is_none")]
48    pub principal: Option<String>,
49}
50
51/// Runtime identity block emitted in `session.accepted` (RFC §8.3).
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct RuntimeIdentity {
54    /// Implementation kind, e.g. `"arcp-rs"`.
55    pub kind: String,
56    /// Implementation version.
57    pub version: String,
58    /// Optional fingerprint clients can pin.
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub fingerprint: Option<String>,
61    /// Trust level, one of `untrusted`/`constrained`/`trusted`/`privileged`.
62    #[serde(default, skip_serializing_if = "Option::is_none")]
63    pub trust_level: Option<String>,
64}
65
66/// Session lease information surfaced in `session.accepted`.
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub struct SessionLease {
69    /// Absolute expiry time.
70    pub expires_at: chrono::DateTime<chrono::Utc>,
71}
72
73/// Payload for `session.open` (RFC §8.1 step 1).
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75pub struct SessionOpenPayload {
76    /// Credentials.
77    pub auth: Credentials,
78    /// Client identity attestation.
79    pub client: ClientIdentity,
80    /// Proposed capability set.
81    #[serde(default)]
82    pub capabilities: Capabilities,
83}
84
85/// Payload for `session.challenge` (RFC §8.1 step 2).
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct SessionChallengePayload {
88    /// Free-form challenge nonce / instructions.
89    pub challenge: String,
90}
91
92/// Payload for `session.authenticate` (RFC §8.1 step 3).
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct SessionAuthenticatePayload {
95    /// Response to the challenge.
96    pub response: String,
97}
98
99/// Payload for `session.accepted` (RFC §8.1 step 4).
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct SessionAcceptedPayload {
102    /// Newly minted session id.
103    pub session_id: SessionId,
104    /// Runtime identity block.
105    pub runtime: RuntimeIdentity,
106    /// Negotiated capability set.
107    pub capabilities: Capabilities,
108    /// Session lease (optional but recommended).
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub lease: Option<SessionLease>,
111}
112
113/// Payload for `session.unauthenticated` — emitted before authentication
114/// completes when the runtime requires re-attempt with corrected creds.
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct SessionUnauthenticatedPayload {
117    /// Reason code.
118    pub code: ErrorCode,
119    /// Human-readable detail.
120    pub message: String,
121}
122
123/// Payload for `session.rejected` (terminal handshake failure).
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct SessionRejectedPayload {
126    /// Reason code.
127    pub code: ErrorCode,
128    /// Human-readable detail.
129    pub message: String,
130}
131
132/// Payload for `session.refresh` — runtime asks the client to re-authenticate.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct SessionRefreshPayload {
135    /// Absolute deadline by which a fresh `session.authenticate` is expected.
136    pub deadline: chrono::DateTime<chrono::Utc>,
137    /// Optional new challenge.
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub challenge: Option<String>,
140}
141
142/// Payload for `session.evicted` — runtime ended the session.
143#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct SessionEvictedPayload {
145    /// Reason code (drawn from §18 taxonomy).
146    pub code: ErrorCode,
147    /// Free-form reason text.
148    pub reason: String,
149}
150
151/// Payload for `session.close` — graceful close from either side.
152#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
153pub struct SessionClosePayload {
154    /// Optional human-readable reason.
155    #[serde(default, skip_serializing_if = "Option::is_none")]
156    pub reason: Option<String>,
157}
158
159/// Payload for `session.ping` (ARCP v1.1 §6.4).
160///
161/// Either peer MAY emit `session.ping` if idle and expect a prompt
162/// `session.pong` echoing the same `nonce`.
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct SessionPingPayload {
165    /// Opaque nonce; the corresponding `session.pong` echoes it as
166    /// `ping_nonce`.
167    pub nonce: String,
168    /// Sender timestamp (RFC 3339).
169    pub sent_at: chrono::DateTime<chrono::Utc>,
170}
171
172/// Payload for `session.pong` (ARCP v1.1 §6.4).
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174pub struct SessionPongPayload {
175    /// Echoed `nonce` from the inciting `session.ping`.
176    pub ping_nonce: String,
177    /// Receiver timestamp (RFC 3339).
178    pub received_at: chrono::DateTime<chrono::Utc>,
179}
180
181/// Payload for `session.ack` (ARCP v1.1 §6.5).
182///
183/// The client periodically informs the runtime of its highest processed
184/// event sequence; the runtime MAY free buffered events with
185/// `seq <= last_processed_seq` and MAY use the lag between the latest
186/// emitted seq and `last_processed_seq` to detect slow consumers.
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct SessionAckPayload {
189    /// Highest event sequence the client has processed.
190    pub last_processed_seq: u64,
191}
192
193/// Optional filter for `session.list_jobs` (ARCP v1.1 §6.6).
194#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
195pub struct SessionListJobsFilter {
196    /// Match jobs whose current status is one of these values.
197    #[serde(default, skip_serializing_if = "Vec::is_empty")]
198    pub status: Vec<String>,
199    /// Match jobs whose agent identifier (or `agent@version`) equals
200    /// this value.
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub agent: Option<String>,
203    /// Match jobs created strictly after this instant.
204    #[serde(default, skip_serializing_if = "Option::is_none")]
205    pub created_after: Option<chrono::DateTime<chrono::Utc>>,
206    /// Match jobs created strictly before this instant.
207    #[serde(default, skip_serializing_if = "Option::is_none")]
208    pub created_before: Option<chrono::DateTime<chrono::Utc>>,
209}
210
211/// Payload for `session.list_jobs` (ARCP v1.1 §6.6).
212#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
213pub struct SessionListJobsPayload {
214    /// Optional filter.
215    #[serde(default, skip_serializing_if = "Option::is_none")]
216    pub filter: Option<SessionListJobsFilter>,
217    /// Maximum number of entries to return.
218    #[serde(default, skip_serializing_if = "Option::is_none")]
219    pub limit: Option<u32>,
220    /// Opaque pagination cursor returned by a previous response's
221    /// `next_cursor`.
222    #[serde(default, skip_serializing_if = "Option::is_none")]
223    pub cursor: Option<String>,
224}
225
226/// One entry in `session.jobs.payload.jobs` (ARCP v1.1 §6.6).
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub struct JobListEntry {
229    /// Job identifier.
230    pub job_id: crate::ids::JobId,
231    /// Resolved `name@version` (or bare `name` if no version was pinned).
232    pub agent: String,
233    /// Current status (e.g. `running`, `completed`).
234    pub status: String,
235    /// Optional parent job id for delegated/child jobs.
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub parent_job_id: Option<crate::ids::JobId>,
238    /// Job submission timestamp.
239    pub created_at: chrono::DateTime<chrono::Utc>,
240    /// Optional trace identifier.
241    #[serde(default, skip_serializing_if = "Option::is_none")]
242    pub trace_id: Option<String>,
243    /// Last event sequence emitted for the job in this session.
244    pub last_event_seq: u64,
245}
246
247/// Payload for `session.jobs` (ARCP v1.1 §6.6).
248#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
249pub struct SessionJobsPayload {
250    /// Correlated request id from the matching `session.list_jobs`.
251    pub request_id: String,
252    /// Job summaries.
253    pub jobs: Vec<JobListEntry>,
254    /// Opaque continuation cursor; `None` if there are no further pages.
255    pub next_cursor: Option<String>,
256}