Skip to main content

everruns_core/
execution_context.rs

1//! Transport-neutral context shared by execution phases and emitted events.
2
3use everruns_provider::typed_id::{ExecId, MessageId, SessionId, TurnId, WorkspaceId};
4use serde::{Deserialize, Serialize};
5
6/// Correlation and resource identity for one execution phase within a turn.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ExecutionContext {
9    /// Session that owns the turn.
10    pub session_id: SessionId,
11    /// Turn containing this execution phase.
12    pub turn_id: TurnId,
13    /// Input message that triggered the turn.
14    pub input_message_id: MessageId,
15    /// Unique identifier for this execution phase.
16    pub exec_id: ExecId,
17    /// Workspace used for virtual file operations, when explicitly attached.
18    #[serde(default)]
19    pub workspace_id: Option<WorkspaceId>,
20}
21
22impl ExecutionContext {
23    /// Create a context for the first execution phase in a turn.
24    pub fn new(session_id: SessionId, turn_id: TurnId, input_message_id: MessageId) -> Self {
25        Self {
26            session_id,
27            turn_id,
28            input_message_id,
29            exec_id: ExecId::new(),
30            workspace_id: None,
31        }
32    }
33
34    /// Attach the workspace addressed by this execution.
35    pub fn with_workspace_id(mut self, workspace_id: WorkspaceId) -> Self {
36        self.workspace_id = Some(workspace_id);
37        self
38    }
39
40    /// Create a context for the next phase while preserving turn lineage.
41    pub fn next_exec(&self) -> Self {
42        Self {
43            session_id: self.session_id,
44            turn_id: self.turn_id,
45            input_message_id: self.input_message_id,
46            exec_id: ExecId::new(),
47            workspace_id: self.workspace_id,
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn next_exec_preserves_lineage_and_workspace_and_changes_execution_id() {
58        for workspace in [None, Some(WorkspaceId::from_seed(5))] {
59            let mut context = ExecutionContext::new(
60                SessionId::from_seed(1),
61                TurnId::from_seed(2),
62                MessageId::from_seed(3),
63            );
64            assert_eq!(context.workspace_id, None);
65            if let Some(workspace) = workspace {
66                context = context.with_workspace_id(workspace);
67            }
68            let next = context.next_exec();
69            assert_eq!(next.session_id, SessionId::from_seed(1));
70            assert_eq!(next.turn_id, TurnId::from_seed(2));
71            assert_eq!(next.input_message_id, MessageId::from_seed(3));
72            assert_eq!(next.workspace_id, workspace);
73            assert_ne!(next.exec_id, context.exec_id);
74            assert_ne!(next.next_exec().exec_id, next.exec_id);
75        }
76    }
77
78    #[test]
79    fn wire_shape_preserves_literal_identity_and_missing_workspace_default() {
80        let wire = serde_json::json!({"session_id":"session_00000000000000000000000000000001","turn_id":"turn_00000000000000000000000000000002","input_message_id":"message_00000000000000000000000000000003","exec_id":"exec_00000000000000000000000000000004"});
81        let parsed: ExecutionContext = serde_json::from_value(wire.clone()).unwrap();
82        assert_eq!(parsed.workspace_id, None);
83        assert_eq!(parsed.session_id, SessionId::from_seed(1));
84        assert_eq!(parsed.turn_id, TurnId::from_seed(2));
85        assert_eq!(parsed.input_message_id, MessageId::from_seed(3));
86        assert_eq!(parsed.exec_id, ExecId::from_seed(4));
87        let mut expected = wire;
88        expected["workspace_id"] = serde_json::Value::Null;
89        assert_eq!(serde_json::to_value(&parsed).unwrap(), expected);
90        let attached = parsed
91            .with_workspace_id(WorkspaceId::from_seed(5))
92            .with_workspace_id(WorkspaceId::from_seed(6));
93        expected["workspace_id"] = serde_json::json!("wsp_00000000000000000000000000000006");
94        assert_eq!(serde_json::to_value(&attached).unwrap(), expected);
95        assert_eq!(
96            serde_json::from_value::<ExecutionContext>(expected)
97                .unwrap()
98                .workspace_id,
99            Some(WorkspaceId::from_seed(6))
100        );
101    }
102}