1use super::common::{Description, LifecycleStatus, ObjectRef};
2use super::validation::require_some;
3use crate::text::{normalize_required_text, normalize_required_text_vec};
4use crate::{CoreError, Id, Provenance, Result, ReviewStatus};
5use serde::{Deserialize, Serialize};
6
7#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ScenarioKind {
11 Hypothetical,
13 Reachable,
15 Blocked,
17 Counterfactual,
19 Planned,
21 Refuted,
23 AcceptedOperationalPlan,
25}
26
27#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
29#[serde(deny_unknown_fields)]
30pub struct ScenarioChanges {
31 #[serde(default, skip_serializing_if = "Vec::is_empty")]
33 pub added: Vec<Id>,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub removed: Vec<Id>,
37 #[serde(default, skip_serializing_if = "Vec::is_empty")]
39 pub modified: Vec<Id>,
40}
41
42#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
44#[serde(deny_unknown_fields)]
45pub struct Reachability {
46 #[serde(rename = "ref")]
48 pub reference: Id,
49 #[serde(default, skip_serializing_if = "Vec::is_empty")]
51 pub via_morphisms: Vec<Id>,
52}
53
54#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
56#[serde(deny_unknown_fields)]
57pub struct Scenario {
58 pub id: Id,
60 pub base_space: Id,
62 pub scenario_kind: ScenarioKind,
64 #[serde(default, skip_serializing_if = "Vec::is_empty")]
66 pub assumptions: Vec<Id>,
67 pub changed_structures: ScenarioChanges,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub reachable_from: Option<Reachability>,
72 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub affected_invariants: Vec<Id>,
75 #[serde(default, skip_serializing_if = "Vec::is_empty")]
77 pub expected_obstructions: Vec<Id>,
78 #[serde(default, skip_serializing_if = "Vec::is_empty")]
80 pub required_witnesses: Vec<Id>,
81 #[serde(default, skip_serializing_if = "Vec::is_empty")]
83 pub valuations: Vec<Id>,
84 pub status: ScenarioStatus,
86 pub provenance: Provenance,
88 pub review_status: LifecycleStatus,
90}
91
92#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
94#[serde(rename_all = "snake_case")]
95pub enum ScenarioStatus {
96 Draft,
98 Candidate,
100 UnderReview,
102 Reachable,
104 Blocked,
106 Refuted,
108 Accepted,
110}
111
112impl Scenario {
113 pub fn validate_acceptance(&self) -> Result<()> {
115 match self.scenario_kind {
116 ScenarioKind::Hypothetical if self.status == ScenarioStatus::Accepted => {
117 return Err(CoreError::malformed_field(
118 "scenario_kind",
119 "hypothetical scenario cannot be treated as accepted fact",
120 ));
121 }
122 ScenarioKind::Reachable | ScenarioKind::AcceptedOperationalPlan => {
123 require_some("reachable_from", self.reachable_from.as_ref())?;
124 }
125 _ => {}
126 }
127 if self.affected_invariants.is_empty() {
128 return Err(CoreError::malformed_field(
129 "affected_invariants",
130 "accepted scenario requires invariant checks or affected invariant records",
131 ));
132 }
133 if self.scenario_kind == ScenarioKind::AcceptedOperationalPlan
134 && self.provenance.review_status != ReviewStatus::Accepted
135 {
136 return Err(CoreError::malformed_field(
137 "provenance.review_status",
138 "accepted operational plan requires policy/capability review",
139 ));
140 }
141 Ok(())
142 }
143}
144
145#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
147#[serde(rename_all = "snake_case")]
148pub enum CapabilityOperation {
149 Read,
151 Propose,
153 Modify,
155 Accept,
157 Reject,
159 Project,
161 ExecuteMorphism,
163 MergeEquivalence,
165 CreateScenario,
167 ApprovePolicyException,
169}
170
171#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
173#[serde(deny_unknown_fields)]
174pub struct RequiredReview {
175 pub policy: Id,
177 pub reviewer: Id,
179}
180
181#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
183#[serde(deny_unknown_fields)]
184pub struct ValidityInterval {
185 pub starts_at: String,
187 pub ends_at: String,
189}
190
191impl ValidityInterval {
192 pub fn validate(&self) -> Result<()> {
194 normalize_required_text("validity_interval.starts_at", &self.starts_at)?;
195 normalize_required_text("validity_interval.ends_at", &self.ends_at)?;
196 Ok(())
197 }
198}
199
200#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
202#[serde(rename_all = "snake_case")]
203pub enum CapabilityStatus {
204 Active,
206 Suspended,
208 Expired,
210 Revoked,
212 Candidate,
214}
215
216#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
218#[serde(deny_unknown_fields)]
219pub struct Capability {
220 pub id: Id,
222 pub actor: Id,
224 pub operation: CapabilityOperation,
226 pub target_type: String,
228 #[serde(default, skip_serializing_if = "Vec::is_empty")]
230 pub target_refs: Vec<ObjectRef>,
231 #[serde(default, skip_serializing_if = "Vec::is_empty")]
233 pub contexts: Vec<Id>,
234 #[serde(default, skip_serializing_if = "Vec::is_empty")]
236 pub preconditions: Vec<Id>,
237 #[serde(default, skip_serializing_if = "Vec::is_empty")]
239 pub postconditions: Vec<Id>,
240 #[serde(default, skip_serializing_if = "Vec::is_empty")]
242 pub forbidden_effects: Vec<Description>,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub required_review: Option<RequiredReview>,
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub validity_interval: Option<ValidityInterval>,
249 pub provenance: Provenance,
251 pub status: CapabilityStatus,
253}
254
255impl Capability {
256 pub fn validate_active_use(&self) -> Result<()> {
258 if self.status != CapabilityStatus::Active {
259 return Err(CoreError::malformed_field(
260 "status",
261 "capability must be active before use",
262 ));
263 }
264 normalize_required_text("target_type", &self.target_type)?;
265 if let Some(validity_interval) = &self.validity_interval {
266 validity_interval.validate()?;
267 }
268 if self.operation == CapabilityOperation::Accept
269 && self.required_review.is_none()
270 && self.provenance.review_status != ReviewStatus::Accepted
271 {
272 return Err(CoreError::malformed_field(
273 "required_review",
274 "accept operation requires explicit policy review",
275 ));
276 }
277 Ok(())
278 }
279}
280
281#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
283#[serde(rename_all = "snake_case")]
284pub enum PolicyKind {
285 Permission,
287 Prohibition,
289 Obligation,
291 ReviewRequirement,
293 ProjectionSafety,
295 CandidateAcceptance,
297 DataBoundary,
299 Escalation,
301}
302
303#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
305#[serde(deny_unknown_fields)]
306pub struct PolicyApplicability {
307 #[serde(default, skip_serializing_if = "Vec::is_empty")]
309 pub target_types: Vec<String>,
310 #[serde(default, skip_serializing_if = "Vec::is_empty")]
312 pub contexts: Vec<Id>,
313 #[serde(default, skip_serializing_if = "Vec::is_empty")]
315 pub operations: Vec<String>,
316}
317
318impl PolicyApplicability {
319 fn validate(&self) -> Result<()> {
320 normalize_required_text_vec("target_types", &self.target_types)?;
321 normalize_required_text_vec("operations", &self.operations)?;
322 Ok(())
323 }
324}
325
326#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
328#[serde(deny_unknown_fields)]
329pub struct PolicyRule {
330 pub description: String,
332 #[serde(default, skip_serializing_if = "Vec::is_empty")]
334 pub constraints: Vec<Id>,
335}
336
337impl PolicyRule {
338 pub fn new(description: impl Into<String>) -> Result<Self> {
340 Ok(Self {
341 description: normalize_required_text("policy.rule.description", description)?,
342 constraints: Vec::new(),
343 })
344 }
345}
346
347#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
349#[serde(rename_all = "snake_case")]
350pub enum PolicyStatus {
351 Draft,
353 Active,
355 Deprecated,
357 Revoked,
359}
360
361#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
363#[serde(deny_unknown_fields)]
364pub struct Policy {
365 pub id: Id,
367 pub policy_kind: PolicyKind,
369 pub applies_to: PolicyApplicability,
371 pub rule: PolicyRule,
373 #[serde(default, skip_serializing_if = "Vec::is_empty")]
375 pub required_witnesses: Vec<Id>,
376 #[serde(default, skip_serializing_if = "Vec::is_empty")]
378 pub required_derivations: Vec<Id>,
379 #[serde(default, skip_serializing_if = "Vec::is_empty")]
381 pub escalation_path: Vec<Id>,
382 #[serde(skip_serializing_if = "Option::is_none")]
384 pub violation_obstruction_template: Option<Id>,
385 pub status: PolicyStatus,
387 pub provenance: Provenance,
389 pub review_status: ReviewStatus,
391}
392
393impl Policy {
394 pub fn validate_active(&self) -> Result<()> {
396 self.applies_to.validate()?;
397 normalize_required_text("policy.rule.description", &self.rule.description)?;
398 if self.status == PolicyStatus::Active && self.review_status != ReviewStatus::Accepted {
399 return Err(CoreError::malformed_field(
400 "review_status",
401 "active policy requires accepted review status",
402 ));
403 }
404 if self.status == PolicyStatus::Active
405 && self.provenance.review_status != ReviewStatus::Accepted
406 {
407 return Err(CoreError::malformed_field(
408 "provenance.review_status",
409 "active policy provenance must be accepted",
410 ));
411 }
412 Ok(())
413 }
414}