Skip to main content

made_core/value_objects/execution/
execution_operation.rs

1use serde::{Deserialize, Serialize};
2
3use super::{ExecutionOperationId, ExecutionRequestBytes, ExecutionRequestDigest};
4use crate::error::DomainError;
5use crate::value_objects::ceremony::{
6    CeremonyId, StateIteration, StateVisit, StepId, StepIteration,
7};
8
9/// Semantic operation root shared by every technical claim that may execute it.
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct ExecutionOperation {
12    operation_id: ExecutionOperationId,
13    ceremony_id: CeremonyId,
14    step_id: StepId,
15    state_visit: StateVisit,
16    state_iteration: StateIteration,
17    step_iteration: StepIteration,
18    request: ExecutionRequestBytes,
19    request_digest: ExecutionRequestDigest,
20}
21
22impl ExecutionOperation {
23    #[must_use]
24    pub fn new(
25        ceremony_id: CeremonyId,
26        step_id: StepId,
27        state_visit: StateVisit,
28        state_iteration: StateIteration,
29        step_iteration: StepIteration,
30        request: ExecutionRequestBytes,
31    ) -> Self {
32        let operation_id = ExecutionOperationId::for_step(
33            &ceremony_id,
34            &step_id,
35            state_visit,
36            state_iteration,
37            step_iteration,
38        );
39        let request_digest = request.digest();
40        Self {
41            operation_id,
42            ceremony_id,
43            step_id,
44            state_visit,
45            state_iteration,
46            step_iteration,
47            request,
48            request_digest,
49        }
50    }
51
52    #[must_use]
53    pub const fn operation_id(&self) -> &ExecutionOperationId {
54        &self.operation_id
55    }
56
57    #[must_use]
58    pub const fn ceremony_id(&self) -> &CeremonyId {
59        &self.ceremony_id
60    }
61
62    #[must_use]
63    pub const fn step_id(&self) -> &StepId {
64        &self.step_id
65    }
66
67    #[must_use]
68    pub const fn state_visit(&self) -> StateVisit {
69        self.state_visit
70    }
71
72    #[must_use]
73    pub const fn state_iteration(&self) -> StateIteration {
74        self.state_iteration
75    }
76
77    #[must_use]
78    pub const fn step_iteration(&self) -> StepIteration {
79        self.step_iteration
80    }
81
82    #[must_use]
83    pub const fn request(&self) -> &ExecutionRequestBytes {
84        &self.request
85    }
86
87    #[must_use]
88    pub const fn request_digest(&self) -> &ExecutionRequestDigest {
89        &self.request_digest
90    }
91
92    /// Re-check derived identity and request digest after deserialization.
93    pub fn validate(&self) -> Result<(), DomainError> {
94        let expected_id = ExecutionOperationId::for_step(
95            &self.ceremony_id,
96            &self.step_id,
97            self.state_visit,
98            self.state_iteration,
99            self.step_iteration,
100        );
101        if self.operation_id != expected_id || self.request_digest != self.request.digest() {
102            return Err(DomainError::InvariantViolated {
103                reason: "execution operation derived fields do not match its semantic input",
104            });
105        }
106        Ok(())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use serde_json::json;
113
114    use super::*;
115
116    fn operation() -> ExecutionOperation {
117        ExecutionOperation::new(
118            CeremonyId::new("ceremony").unwrap(),
119            StepId::new("work").unwrap(),
120            StateVisit::FIRST,
121            StateIteration::FIRST,
122            StepIteration::FIRST,
123            ExecutionRequestBytes::new(b"semantic request".to_vec()).unwrap(),
124        )
125    }
126
127    #[test]
128    fn deserialized_derived_fields_are_revalidated_before_use() {
129        let original = operation();
130        original.validate().unwrap();
131        let mut raw = serde_json::to_value(original).unwrap();
132        raw["request_digest"] = json!("0".repeat(64));
133        let corrupted: ExecutionOperation = serde_json::from_value(raw).unwrap();
134        assert!(corrupted.validate().is_err());
135    }
136}