made_core/value_objects/execution/
execution_intent.rs1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use super::{ExecutionConnectorId, ExecutionOperation, ExecutionRecoveryCapability};
5use crate::error::DomainError;
6use crate::value_objects::artifact::ArtifactSourceKind;
7use crate::value_objects::audit::AuditActorKind;
8use crate::value_objects::ceremony::StepClaimFence;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub struct ExecutionIntent {
13 operation: ExecutionOperation,
14 claim_fence: StepClaimFence,
15 connector_id: ExecutionConnectorId,
16 recovery_capability: ExecutionRecoveryCapability,
17 source_kind: ArtifactSourceKind,
18 actor_kind: AuditActorKind,
19 #[serde(with = "time::serde::rfc3339")]
20 recorded_at: OffsetDateTime,
21}
22
23impl ExecutionIntent {
24 pub fn new(
25 operation: ExecutionOperation,
26 claim_fence: StepClaimFence,
27 connector_id: ExecutionConnectorId,
28 recovery_capability: ExecutionRecoveryCapability,
29 source_kind: ArtifactSourceKind,
30 actor_kind: AuditActorKind,
31 recorded_at: OffsetDateTime,
32 ) -> Result<Self, crate::error::DomainError> {
33 if !source_kind.is_execution_source() {
34 return Err(crate::error::DomainError::InvariantViolated {
35 reason: "execution intent requires an execution source kind",
36 });
37 }
38 Ok(Self {
39 operation,
40 claim_fence,
41 connector_id,
42 recovery_capability,
43 source_kind,
44 actor_kind,
45 recorded_at,
46 })
47 }
48
49 #[must_use]
50 pub const fn operation(&self) -> &ExecutionOperation {
51 &self.operation
52 }
53
54 #[must_use]
55 pub const fn claim_fence(&self) -> &StepClaimFence {
56 &self.claim_fence
57 }
58
59 #[must_use]
60 pub const fn connector_id(&self) -> &ExecutionConnectorId {
61 &self.connector_id
62 }
63
64 #[must_use]
65 pub const fn recovery_capability(&self) -> ExecutionRecoveryCapability {
66 self.recovery_capability
67 }
68
69 #[must_use]
70 pub const fn source_kind(&self) -> ArtifactSourceKind {
71 self.source_kind
72 }
73
74 #[must_use]
75 pub const fn actor_kind(&self) -> AuditActorKind {
76 self.actor_kind
77 }
78
79 #[must_use]
80 pub const fn recorded_at(&self) -> OffsetDateTime {
81 self.recorded_at
82 }
83
84 pub fn validate(&self) -> Result<(), DomainError> {
86 self.operation.validate()?;
87 if !self.source_kind.is_execution_source() {
88 return Err(DomainError::InvariantViolated {
89 reason: "execution intent requires an execution source kind",
90 });
91 }
92 Ok(())
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use serde_json::json;
99
100 use super::*;
101 use crate::value_objects::{CeremonyId, StateIteration, StateVisit, StepId, StepIteration};
102
103 #[test]
104 fn deserialized_intent_rejects_a_non_execution_source() {
105 let operation = ExecutionOperation::new(
106 CeremonyId::new("ceremony").unwrap(),
107 StepId::new("work").unwrap(),
108 StateVisit::FIRST,
109 StateIteration::FIRST,
110 StepIteration::FIRST,
111 super::super::ExecutionRequestBytes::new(b"request".to_vec()).unwrap(),
112 );
113 let mut raw = serde_json::to_value(
114 ExecutionIntent::new(
115 operation,
116 StepClaimFence::new("1".repeat(64)).unwrap(),
117 ExecutionConnectorId::new("test").unwrap(),
118 ExecutionRecoveryCapability::IdempotentByOperationId,
119 ArtifactSourceKind::NoOp,
120 AuditActorKind::Engine,
121 OffsetDateTime::UNIX_EPOCH,
122 )
123 .unwrap(),
124 )
125 .unwrap();
126 raw["source_kind"] = json!("generated_report");
127 let corrupted: ExecutionIntent = serde_json::from_value(raw).unwrap();
128 assert!(corrupted.validate().is_err());
129 }
130}