appcore_contracts/policy/
profile.rs1use super::*;
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum CoreRole {
17 GeneralPurpose,
19 Control,
21 Worker,
23 Storage,
25 Compute,
27 Custom(String),
29}
30
31#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
33pub struct ResourceProfile {
34 cpu_cores: Option<u16>,
35 memory_bytes: Option<u64>,
36 gpu_count: u16,
37}
38
39impl ResourceProfile {
40 pub fn new(cpu_cores: Option<u16>, memory_bytes: Option<u64>, gpu_count: u16) -> Self {
42 Self {
43 cpu_cores,
44 memory_bytes,
45 gpu_count,
46 }
47 }
48
49 pub fn cpu_cores(&self) -> Option<u16> {
51 self.cpu_cores
52 }
53
54 pub fn memory_bytes(&self) -> Option<u64> {
56 self.memory_bytes
57 }
58
59 pub fn gpu_count(&self) -> u16 {
61 self.gpu_count
62 }
63
64 fn validate(&self) -> ContractResult<()> {
65 if self.cpu_cores == Some(0) || self.memory_bytes == Some(0) {
66 return Err(ContractError::InvalidValue {
67 field: "resources",
68 reason: "known CPU and memory values must be greater than zero",
69 });
70 }
71 Ok(())
72 }
73}
74
75#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum WorkloadClass {
79 #[default]
81 General,
82 Interactive,
84 Batch,
86 Compute,
88 Io,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94pub struct SchedulingProfile {
95 weight: u16,
96 priority: i16,
97 max_concurrency: u32,
98 available: bool,
99 workload: WorkloadClass,
100 affinity: BTreeSet<String>,
101}
102
103impl SchedulingProfile {
104 pub fn new(
106 weight: u16,
107 priority: i16,
108 max_concurrency: u32,
109 workload: WorkloadClass,
110 ) -> ContractResult<Self> {
111 if weight == 0 || max_concurrency == 0 {
112 return Err(ContractError::InvalidValue {
113 field: "scheduling",
114 reason: "weight and max concurrency must be greater than zero",
115 });
116 }
117 Ok(Self {
118 weight,
119 priority,
120 max_concurrency,
121 available: true,
122 workload,
123 affinity: BTreeSet::new(),
124 })
125 }
126
127 pub fn with_availability(mut self, available: bool) -> Self {
129 self.available = available;
130 self
131 }
132
133 pub fn with_affinity(mut self, affinity: impl Into<String>) -> ContractResult<Self> {
135 let affinity = affinity.into();
136 validate_text("scheduling.affinity", &affinity, 128)?;
137 self.affinity.insert(affinity);
138 Ok(self)
139 }
140
141 pub fn weight(&self) -> u16 {
143 self.weight
144 }
145
146 pub fn priority(&self) -> i16 {
148 self.priority
149 }
150
151 pub fn max_concurrency(&self) -> u32 {
153 self.max_concurrency
154 }
155
156 pub fn is_available(&self) -> bool {
158 self.available
159 }
160
161 pub fn workload(&self) -> WorkloadClass {
163 self.workload
164 }
165
166 pub fn affinity(&self) -> &BTreeSet<String> {
168 &self.affinity
169 }
170
171 pub(crate) fn validate(&self) -> ContractResult<()> {
172 if self.weight == 0 || self.max_concurrency == 0 {
173 return Err(ContractError::InvalidValue {
174 field: "scheduling",
175 reason: "weight and max concurrency must be greater than zero",
176 });
177 }
178 for affinity in &self.affinity {
179 validate_text("scheduling.affinity", affinity, 128)?;
180 }
181 Ok(())
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
187pub struct CoreProfile {
188 role: CoreRole,
189 service_id: ServiceId,
190 capabilities: BTreeSet<CapabilityId>,
191 leadership: LeadershipRequirement,
192 resources: ResourceProfile,
193 scheduling: SchedulingProfile,
194}
195
196impl CoreProfile {
197 pub fn new(
199 role: CoreRole,
200 service_id: ServiceId,
201 capabilities: impl IntoIterator<Item = CapabilityId>,
202 leadership: LeadershipRequirement,
203 resources: ResourceProfile,
204 scheduling: SchedulingProfile,
205 ) -> ContractResult<Self> {
206 if leadership.service_id() != &service_id {
207 return Err(ContractError::InvalidValue {
208 field: "core_profile.leadership",
209 reason: "leadership must be scoped to the profile service",
210 });
211 }
212 let profile = Self {
213 role,
214 service_id,
215 capabilities: capabilities.into_iter().collect(),
216 leadership,
217 resources,
218 scheduling,
219 };
220 profile.validate()?;
221 Ok(profile)
222 }
223
224 pub fn role(&self) -> &CoreRole {
226 &self.role
227 }
228
229 pub fn service_id(&self) -> &ServiceId {
231 &self.service_id
232 }
233
234 pub fn capabilities(&self) -> &BTreeSet<CapabilityId> {
236 &self.capabilities
237 }
238
239 pub fn leadership(&self) -> &LeadershipRequirement {
241 &self.leadership
242 }
243
244 pub fn resources(&self) -> &ResourceProfile {
246 &self.resources
247 }
248
249 pub fn scheduling(&self) -> &SchedulingProfile {
251 &self.scheduling
252 }
253
254 pub(crate) fn validate(&self) -> ContractResult<()> {
255 if let CoreRole::Custom(role) = &self.role {
256 validate_text("core_profile.role", role, 128)?;
257 }
258 self.leadership.validate()?;
259 self.resources.validate()?;
260 self.scheduling.validate()?;
261 if self.leadership.service_id() != &self.service_id {
262 return Err(ContractError::InvalidValue {
263 field: "core_profile.leadership",
264 reason: "leadership must be scoped to the profile service",
265 });
266 }
267 Ok(())
268 }
269}