1use crate::error::{
7 require_id, require_non_empty, require_non_empty_slice, require_schema_version,
8 EffectRuntimeValidationError, EffectValidationResult,
9};
10use crate::v25::V25ConstitutionCitation;
11use crate::vocab::{CompensationClassV1, ExecutionStateV1};
12use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14use stack_ids::{CompensationExecutionReceiptId, CompensationPlanId, EffectIntentId};
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
20pub struct CompensationPlanV1 {
21 pub schema_version: String,
22 pub compensation_plan_id: CompensationPlanId,
23 pub effect_intent_id: EffectIntentId,
24 #[serde(flatten)]
26 pub citation: V25ConstitutionCitation,
27 pub compensation_required: bool,
28 pub compensation_class: CompensationClassV1,
29 pub compensation_steps: Vec<String>,
30 pub success_criteria: Vec<String>,
31 pub owner_ref: String,
32 pub latest_start: String,
33 pub advisory_only: bool,
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
40pub struct CompensationExecutionReceiptV1 {
41 pub schema_version: String,
42 pub compensation_execution_receipt_id: CompensationExecutionReceiptId,
43 pub compensation_plan_id: CompensationPlanId,
44 #[serde(flatten)]
46 pub citation: V25ConstitutionCitation,
47 pub execution_state: ExecutionStateV1,
48 pub completed_steps: Vec<String>,
49 pub failed_steps: Vec<String>,
50 pub residual_risk_summary: String,
51 pub generated_at: String,
52}
53
54impl CompensationPlanV1 {
55 pub const SCHEMA_VERSION: &'static str = "CompensationPlanV1";
56
57 #[allow(clippy::too_many_arguments)]
59 pub fn builder(
60 compensation_plan_id: CompensationPlanId,
61 effect_intent_id: EffectIntentId,
62 citation: V25ConstitutionCitation,
63 compensation_required: bool,
64 compensation_class: CompensationClassV1,
65 compensation_steps: Vec<String>,
66 success_criteria: Vec<String>,
67 owner_ref: impl Into<String>,
68 latest_start: impl Into<String>,
69 advisory_only: bool,
70 ) -> CompensationPlanV1Builder {
71 CompensationPlanV1Builder::new(
72 compensation_plan_id,
73 effect_intent_id,
74 citation,
75 compensation_required,
76 compensation_class,
77 compensation_steps,
78 success_criteria,
79 owner_ref,
80 latest_start,
81 advisory_only,
82 )
83 }
84
85 pub fn validate(&self) -> EffectValidationResult {
87 require_schema_version(
88 "CompensationPlanV1",
89 Self::SCHEMA_VERSION,
90 &self.schema_version,
91 )?;
92 require_id(&self.compensation_plan_id, "compensation_plan_id")?;
93 require_id(&self.effect_intent_id, "effect_intent_id")?;
94 require_non_empty_slice(&self.success_criteria, "success_criteria")?;
95 require_non_empty(&self.owner_ref, "owner_ref")?;
96 require_non_empty(&self.latest_start, "latest_start")?;
97 if self.compensation_required {
98 require_non_empty_slice(&self.compensation_steps, "compensation_steps")?;
99 }
100 Ok(())
101 }
102}
103
104#[derive(Debug, Clone)]
106pub struct CompensationPlanV1Builder {
107 compensation_plan_id: CompensationPlanId,
108 effect_intent_id: EffectIntentId,
109 citation: V25ConstitutionCitation,
110 compensation_required: bool,
111 compensation_class: CompensationClassV1,
112 compensation_steps: Vec<String>,
113 success_criteria: Vec<String>,
114 owner_ref: String,
115 latest_start: String,
116 advisory_only: bool,
117}
118
119impl CompensationPlanV1Builder {
120 #[allow(clippy::too_many_arguments)]
122 pub fn new(
123 compensation_plan_id: CompensationPlanId,
124 effect_intent_id: EffectIntentId,
125 citation: V25ConstitutionCitation,
126 compensation_required: bool,
127 compensation_class: CompensationClassV1,
128 compensation_steps: Vec<String>,
129 success_criteria: Vec<String>,
130 owner_ref: impl Into<String>,
131 latest_start: impl Into<String>,
132 advisory_only: bool,
133 ) -> Self {
134 Self {
135 compensation_plan_id,
136 effect_intent_id,
137 citation,
138 compensation_required,
139 compensation_class,
140 compensation_steps,
141 success_criteria,
142 owner_ref: owner_ref.into(),
143 latest_start: latest_start.into(),
144 advisory_only,
145 }
146 }
147
148 pub fn build(self) -> Result<CompensationPlanV1, EffectRuntimeValidationError> {
150 let value = CompensationPlanV1 {
151 schema_version: CompensationPlanV1::SCHEMA_VERSION.to_string(),
152 compensation_plan_id: self.compensation_plan_id,
153 effect_intent_id: self.effect_intent_id,
154 citation: self.citation,
155 compensation_required: self.compensation_required,
156 compensation_class: self.compensation_class,
157 compensation_steps: self.compensation_steps,
158 success_criteria: self.success_criteria,
159 owner_ref: self.owner_ref,
160 latest_start: self.latest_start,
161 advisory_only: self.advisory_only,
162 };
163 value.validate()?;
164 Ok(value)
165 }
166}
167
168impl CompensationExecutionReceiptV1 {
169 pub const SCHEMA_VERSION: &'static str = "CompensationExecutionReceiptV1";
170
171 #[allow(clippy::too_many_arguments)]
173 pub fn builder(
174 compensation_execution_receipt_id: CompensationExecutionReceiptId,
175 compensation_plan_id: CompensationPlanId,
176 citation: V25ConstitutionCitation,
177 execution_state: ExecutionStateV1,
178 completed_steps: Vec<String>,
179 failed_steps: Vec<String>,
180 residual_risk_summary: impl Into<String>,
181 generated_at: impl Into<String>,
182 ) -> CompensationExecutionReceiptV1Builder {
183 CompensationExecutionReceiptV1Builder::new(
184 compensation_execution_receipt_id,
185 compensation_plan_id,
186 citation,
187 execution_state,
188 completed_steps,
189 failed_steps,
190 residual_risk_summary,
191 generated_at,
192 )
193 }
194
195 pub fn validate(&self) -> EffectValidationResult {
197 require_schema_version(
198 "CompensationExecutionReceiptV1",
199 Self::SCHEMA_VERSION,
200 &self.schema_version,
201 )?;
202 require_id(
203 &self.compensation_execution_receipt_id,
204 "compensation_execution_receipt_id",
205 )?;
206 require_id(&self.compensation_plan_id, "compensation_plan_id")?;
207 require_non_empty(&self.residual_risk_summary, "residual_risk_summary")?;
208 require_non_empty(&self.generated_at, "generated_at")?;
209 if matches!(self.execution_state, ExecutionStateV1::Completed)
210 && !self.failed_steps.is_empty()
211 {
212 return Err(EffectRuntimeValidationError::InvalidState(
213 "completed compensation receipts must not list failed_steps",
214 ));
215 }
216 Ok(())
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct CompensationExecutionReceiptV1Builder {
223 compensation_execution_receipt_id: CompensationExecutionReceiptId,
224 compensation_plan_id: CompensationPlanId,
225 citation: V25ConstitutionCitation,
226 execution_state: ExecutionStateV1,
227 completed_steps: Vec<String>,
228 failed_steps: Vec<String>,
229 residual_risk_summary: String,
230 generated_at: String,
231}
232
233impl CompensationExecutionReceiptV1Builder {
234 #[allow(clippy::too_many_arguments)]
236 pub fn new(
237 compensation_execution_receipt_id: CompensationExecutionReceiptId,
238 compensation_plan_id: CompensationPlanId,
239 citation: V25ConstitutionCitation,
240 execution_state: ExecutionStateV1,
241 completed_steps: Vec<String>,
242 failed_steps: Vec<String>,
243 residual_risk_summary: impl Into<String>,
244 generated_at: impl Into<String>,
245 ) -> Self {
246 Self {
247 compensation_execution_receipt_id,
248 compensation_plan_id,
249 citation,
250 execution_state,
251 completed_steps,
252 failed_steps,
253 residual_risk_summary: residual_risk_summary.into(),
254 generated_at: generated_at.into(),
255 }
256 }
257
258 pub fn build(self) -> Result<CompensationExecutionReceiptV1, EffectRuntimeValidationError> {
260 let value = CompensationExecutionReceiptV1 {
261 schema_version: CompensationExecutionReceiptV1::SCHEMA_VERSION.to_string(),
262 compensation_execution_receipt_id: self.compensation_execution_receipt_id,
263 compensation_plan_id: self.compensation_plan_id,
264 citation: self.citation,
265 execution_state: self.execution_state,
266 completed_steps: self.completed_steps,
267 failed_steps: self.failed_steps,
268 residual_risk_summary: self.residual_risk_summary,
269 generated_at: self.generated_at,
270 };
271 value.validate()?;
272 Ok(value)
273 }
274}