made_core/entities/
agentic_system_execution.rs1use std::collections::BTreeMap;
15
16use serde::{Deserialize, Serialize};
17use time::OffsetDateTime;
18
19use crate::error::DomainError;
20use crate::value_objects::{
21 AgenticSystemExecutionId, CeremonyExecutionLink, ExecutionState, IntegratorBindingId,
22 LinkStatus, ParticipantId, ParticipantMaterialization, RequestedExecutionProfile,
23 SystemCeremonyId, SystemPin, SystemRoleId,
24};
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct AgenticSystemExecution {
29 id: AgenticSystemExecutionId,
30 system: SystemPin,
31 ceremonies: BTreeMap<SystemCeremonyId, CeremonyExecutionLink>,
32 resolved_profiles: BTreeMap<SystemRoleId, RequestedExecutionProfile>,
33 participants: BTreeMap<ParticipantId, ParticipantMaterialization>,
34 #[serde(default, skip_serializing_if = "Option::is_none")]
35 integrator_binding: Option<IntegratorBindingId>,
36 state: ExecutionState,
37 #[serde(with = "time::serde::rfc3339")]
38 created_at: OffsetDateTime,
39 #[serde(with = "time::serde::rfc3339")]
40 updated_at: OffsetDateTime,
41}
42
43impl AgenticSystemExecution {
44 pub fn plan(
46 id: AgenticSystemExecutionId,
47 system: SystemPin,
48 ceremonies: impl IntoIterator<Item = (SystemCeremonyId, CeremonyExecutionLink)>,
49 resolved_profiles: impl IntoIterator<Item = (SystemRoleId, RequestedExecutionProfile)>,
50 participants: impl IntoIterator<Item = (ParticipantId, ParticipantMaterialization)>,
51 now: OffsetDateTime,
52 ) -> Result<Self, DomainError> {
53 let ceremonies: BTreeMap<_, _> = ceremonies.into_iter().collect();
54 if ceremonies.is_empty() {
55 return Err(DomainError::EmptyCollection {
56 field: "agentic_system_execution.ceremonies",
57 });
58 }
59 let mut execution = Self {
60 id,
61 system,
62 ceremonies,
63 resolved_profiles: resolved_profiles.into_iter().collect(),
64 participants: participants.into_iter().collect(),
65 integrator_binding: None,
66 state: ExecutionState::Planned,
67 created_at: now,
68 updated_at: now,
69 };
70 execution.state = execution.observed_state();
71 Ok(execution)
72 }
73
74 #[must_use]
75 pub const fn id(&self) -> &AgenticSystemExecutionId {
76 &self.id
77 }
78
79 #[must_use]
81 pub const fn system(&self) -> &SystemPin {
82 &self.system
83 }
84
85 #[must_use]
86 pub const fn ceremonies(&self) -> &BTreeMap<SystemCeremonyId, CeremonyExecutionLink> {
87 &self.ceremonies
88 }
89
90 #[must_use]
92 pub const fn resolved_profiles(&self) -> &BTreeMap<SystemRoleId, RequestedExecutionProfile> {
93 &self.resolved_profiles
94 }
95
96 #[must_use]
97 pub const fn participants(&self) -> &BTreeMap<ParticipantId, ParticipantMaterialization> {
98 &self.participants
99 }
100
101 #[must_use]
102 pub const fn integrator_binding(&self) -> Option<&IntegratorBindingId> {
103 self.integrator_binding.as_ref()
104 }
105
106 #[must_use]
107 pub const fn state(&self) -> ExecutionState {
108 self.state
109 }
110
111 #[must_use]
112 pub const fn created_at(&self) -> OffsetDateTime {
113 self.created_at
114 }
115
116 #[must_use]
117 pub const fn updated_at(&self) -> OffsetDateTime {
118 self.updated_at
119 }
120
121 #[must_use]
122 pub fn link(&self, ceremony: &SystemCeremonyId) -> Option<&CeremonyExecutionLink> {
123 self.ceremonies.get(ceremony)
124 }
125
126 pub fn with_link(
132 &self,
133 ceremony: &SystemCeremonyId,
134 link: CeremonyExecutionLink,
135 now: OffsetDateTime,
136 ) -> Result<Self, DomainError> {
137 if !self.ceremonies.contains_key(ceremony) {
138 return Err(DomainError::NotFound {
139 what: "agentic_system_execution.ceremony",
140 });
141 }
142 let mut next = self.clone();
143 next.ceremonies.insert(ceremony.clone(), link);
144 next.updated_at = now;
145 next.state = next.observed_state();
146 Ok(next)
147 }
148
149 #[must_use]
151 pub fn with_integrator_binding(
152 &self,
153 binding: IntegratorBindingId,
154 now: OffsetDateTime,
155 ) -> Self {
156 Self {
157 integrator_binding: Some(binding),
158 updated_at: now,
159 ..self.clone()
160 }
161 }
162
163 #[must_use]
171 pub fn ready(
172 &self,
173 blocking: &BTreeMap<SystemCeremonyId, Vec<SystemCeremonyId>>,
174 ) -> Vec<&SystemCeremonyId> {
175 self.ceremonies
176 .iter()
177 .filter(|(id, link)| {
178 link.status() == LinkStatus::Pending
179 && blocking
180 .get(*id)
181 .is_none_or(|waiting| waiting.iter().all(|other| self.is_completed(other)))
182 })
183 .map(|(id, _)| id)
184 .collect()
185 }
186
187 fn is_completed(&self, ceremony: &SystemCeremonyId) -> bool {
188 self.ceremonies
189 .get(ceremony)
190 .is_some_and(|link| link.status().releases_dependants())
191 }
192
193 fn observed_state(&self) -> ExecutionState {
194 ExecutionState::of(self.ceremonies.values().map(CeremonyExecutionLink::status))
195 }
196}