Skip to main content

profile_runtime/
rules.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use stack_ids::CompositionRuleSetId;
4
5pub const COMPOSITION_RULE_SET_V1_SCHEMA: &str = "composition_rule_set_v1";
6pub const REFERENCE_EVALUATOR_VERSION_V1: &str = "profile-runtime/reference-evaluator/v1";
7
8#[derive(
9    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
10)]
11#[serde(rename_all = "snake_case")]
12pub enum FoldClassV1 {
13    Intersection,
14    Union,
15    MinOfMaxima,
16    MaxOfMinima,
17    EarliestExpiry,
18    ConflictIfDifferent,
19    BlockDominant,
20}
21
22#[derive(
23    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
24)]
25#[serde(rename_all = "snake_case")]
26pub enum CompiledObligationKindV1 {
27    Approval,
28    Check,
29    Monitor,
30    Evidence,
31    Disclosure,
32    Residency,
33    Tenancy,
34    Assurance,
35    Continuity,
36    Effect,
37    Delegation,
38    Replay,
39    Rollback,
40    Compensation,
41    PostHocReview,
42    Warning,
43    Block,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
47pub struct ObligationFamilyRuleV1 {
48    pub obligation_family: String,
49    pub fold_class: FoldClassV1,
50    pub output_kind: CompiledObligationKindV1,
51    pub monotone_additions_expected: bool,
52    pub block_on_unresolved_conflict: bool,
53    #[serde(default, skip_serializing_if = "Vec::is_empty")]
54    pub admissible_exception_classes: Vec<String>,
55    pub downgrade_behavior: String,
56    pub explanation: String,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
60pub struct ConflictClassRuleV1 {
61    pub conflict_class: String,
62    pub blocking: bool,
63    pub review_path: String,
64    pub explanation: String,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
68pub struct ExceptionAdmissibilityRuleV1 {
69    pub exception_class: String,
70    #[serde(default, skip_serializing_if = "Vec::is_empty")]
71    pub allowed_obligation_families: Vec<String>,
72    pub requires_approval_refs: bool,
73    pub requires_expiry: bool,
74    pub residual_obligations_mandatory: bool,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78pub struct CompositionRuleSetV1 {
79    pub schema_version: String,
80    pub composition_rule_set_id: CompositionRuleSetId,
81    pub version: String,
82    #[serde(default, skip_serializing_if = "Vec::is_empty")]
83    pub family_rules: Vec<ObligationFamilyRuleV1>,
84    #[serde(default, skip_serializing_if = "Vec::is_empty")]
85    pub conflict_classes: Vec<ConflictClassRuleV1>,
86    #[serde(default, skip_serializing_if = "Vec::is_empty")]
87    pub exception_rules: Vec<ExceptionAdmissibilityRuleV1>,
88    pub downgrade_behavior: String,
89    pub reference_evaluator_version: String,
90}
91
92impl CompositionRuleSetV1 {
93    /// Returns the reference composition rules used by the current closeout lane.
94    pub fn reference_v1() -> Self {
95        Self {
96            schema_version: COMPOSITION_RULE_SET_V1_SCHEMA.into(),
97            composition_rule_set_id: CompositionRuleSetId::generate(),
98            version: "reference-v1".into(),
99            family_rules: vec![
100                ObligationFamilyRuleV1 {
101                    obligation_family: "effect.allowed_run_modes".into(),
102                    fold_class: FoldClassV1::Intersection,
103                    output_kind: CompiledObligationKindV1::Effect,
104                    monotone_additions_expected: true,
105                    block_on_unresolved_conflict: true,
106                    admissible_exception_classes: vec!["break_glass".into()],
107                    downgrade_behavior: "advisory_only".into(),
108                    explanation: "allowed run modes narrow by intersection".into(),
109                },
110                ObligationFamilyRuleV1 {
111                    obligation_family: "effect.required_preflight_checks".into(),
112                    fold_class: FoldClassV1::Union,
113                    output_kind: CompiledObligationKindV1::Check,
114                    monotone_additions_expected: true,
115                    block_on_unresolved_conflict: false,
116                    admissible_exception_classes: Vec::new(),
117                    downgrade_behavior: "accumulate".into(),
118                    explanation: "preflight checks accumulate".into(),
119                },
120                ObligationFamilyRuleV1 {
121                    obligation_family: "residency.allowed_execution_regions".into(),
122                    fold_class: FoldClassV1::Intersection,
123                    output_kind: CompiledObligationKindV1::Residency,
124                    monotone_additions_expected: true,
125                    block_on_unresolved_conflict: true,
126                    admissible_exception_classes: vec!["locality_exception".into()],
127                    downgrade_behavior: "blocked_without_exception".into(),
128                    explanation: "execution region allowances narrow by overlap".into(),
129                },
130                ObligationFamilyRuleV1 {
131                    obligation_family: "residency.forbidden_transfer_classes".into(),
132                    fold_class: FoldClassV1::BlockDominant,
133                    output_kind: CompiledObligationKindV1::Block,
134                    monotone_additions_expected: true,
135                    block_on_unresolved_conflict: true,
136                    admissible_exception_classes: vec!["locality_exception".into()],
137                    downgrade_behavior: "blocked".into(),
138                    explanation: "forbidden transfer classes block unless explicitly excepted".into(),
139                },
140                ObligationFamilyRuleV1 {
141                    obligation_family: "delegation.max_depth".into(),
142                    fold_class: FoldClassV1::MinOfMaxima,
143                    output_kind: CompiledObligationKindV1::Delegation,
144                    monotone_additions_expected: true,
145                    block_on_unresolved_conflict: false,
146                    admissible_exception_classes: vec!["break_glass".into()],
147                    downgrade_behavior: "tighten".into(),
148                    explanation: "maximum delegation depth tightens to the smallest cap".into(),
149                },
150                ObligationFamilyRuleV1 {
151                    obligation_family: "delegation.forbidden_role_combination".into(),
152                    fold_class: FoldClassV1::BlockDominant,
153                    output_kind: CompiledObligationKindV1::Block,
154                    monotone_additions_expected: true,
155                    block_on_unresolved_conflict: true,
156                    admissible_exception_classes: vec!["conflict_override".into(), "break_glass".into()],
157                    downgrade_behavior: "blocked".into(),
158                    explanation: "forbidden role combinations survive into the effective constitution".into(),
159                },
160                ObligationFamilyRuleV1 {
161                    obligation_family: "approval.required".into(),
162                    fold_class: FoldClassV1::Union,
163                    output_kind: CompiledObligationKindV1::Approval,
164                    monotone_additions_expected: true,
165                    block_on_unresolved_conflict: false,
166                    admissible_exception_classes: Vec::new(),
167                    downgrade_behavior: "accumulate".into(),
168                    explanation: "approval obligations accumulate".into(),
169                },
170                ObligationFamilyRuleV1 {
171                    obligation_family: "monitor.required".into(),
172                    fold_class: FoldClassV1::Union,
173                    output_kind: CompiledObligationKindV1::Monitor,
174                    monotone_additions_expected: true,
175                    block_on_unresolved_conflict: false,
176                    admissible_exception_classes: Vec::new(),
177                    downgrade_behavior: "accumulate".into(),
178                    explanation: "monitor obligations accumulate".into(),
179                },
180                ObligationFamilyRuleV1 {
181                    obligation_family: "disclosure.export_format".into(),
182                    fold_class: FoldClassV1::ConflictIfDifferent,
183                    output_kind: CompiledObligationKindV1::Disclosure,
184                    monotone_additions_expected: false,
185                    block_on_unresolved_conflict: true,
186                    admissible_exception_classes: vec!["disclosure_exception".into()],
187                    downgrade_behavior: "blocked_until_reviewed".into(),
188                    explanation: "incompatible export format requirements must surface as a conflict".into(),
189                },
190                ObligationFamilyRuleV1 {
191                    obligation_family: "continuity.exception_ttl_minutes".into(),
192                    fold_class: FoldClassV1::MinOfMaxima,
193                    output_kind: CompiledObligationKindV1::Continuity,
194                    monotone_additions_expected: true,
195                    block_on_unresolved_conflict: false,
196                    admissible_exception_classes: vec!["incident_override".into()],
197                    downgrade_behavior: "tighten".into(),
198                    explanation: "continuity exception TTL uses the smallest cap".into(),
199                },
200                ObligationFamilyRuleV1 {
201                    obligation_family: "review.post_hoc".into(),
202                    fold_class: FoldClassV1::Union,
203                    output_kind: CompiledObligationKindV1::PostHocReview,
204                    monotone_additions_expected: true,
205                    block_on_unresolved_conflict: false,
206                    admissible_exception_classes: Vec::new(),
207                    downgrade_behavior: "accumulate".into(),
208                    explanation: "post-hoc review obligations accumulate".into(),
209                },
210                ObligationFamilyRuleV1 {
211                    obligation_family: "vendor.lossy_fields".into(),
212                    fold_class: FoldClassV1::Union,
213                    output_kind: CompiledObligationKindV1::Warning,
214                    monotone_additions_expected: true,
215                    block_on_unresolved_conflict: false,
216                    admissible_exception_classes: Vec::new(),
217                    downgrade_behavior: "warn".into(),
218                    explanation: "lossy translation caveats accumulate and remain visible".into(),
219                },
220            ],
221            conflict_classes: vec![
222                ConflictClassRuleV1 {
223                    conflict_class: "empty_intersection".into(),
224                    blocking: true,
225                    review_path: "human_constitution_review".into(),
226                    explanation: "intersection fold yielded no lawful overlap".into(),
227                },
228                ConflictClassRuleV1 {
229                    conflict_class: "incomparable_values".into(),
230                    blocking: true,
231                    review_path: "human_constitution_review".into(),
232                    explanation: "conflict-if-different obligations diverged without an admitted exception".into(),
233                },
234                ConflictClassRuleV1 {
235                    conflict_class: "unsupported_numeric_fold".into(),
236                    blocking: false,
237                    review_path: "rule_set_maintenance".into(),
238                    explanation: "the contribution set could not be reduced numerically and requires rule-set maintenance".into(),
239                },
240            ],
241            exception_rules: vec![
242                ExceptionAdmissibilityRuleV1 {
243                    exception_class: "locality_exception".into(),
244                    allowed_obligation_families: vec![
245                        "residency.allowed_execution_regions".into(),
246                        "residency.forbidden_transfer_classes".into(),
247                    ],
248                    requires_approval_refs: true,
249                    requires_expiry: true,
250                    residual_obligations_mandatory: true,
251                },
252                ExceptionAdmissibilityRuleV1 {
253                    exception_class: "break_glass".into(),
254                    allowed_obligation_families: vec![
255                        "effect.allowed_run_modes".into(),
256                        "delegation.max_depth".into(),
257                        "delegation.forbidden_role_combination".into(),
258                    ],
259                    requires_approval_refs: true,
260                    requires_expiry: true,
261                    residual_obligations_mandatory: true,
262                },
263                ExceptionAdmissibilityRuleV1 {
264                    exception_class: "incident_override".into(),
265                    allowed_obligation_families: vec!["continuity.exception_ttl_minutes".into()],
266                    requires_approval_refs: true,
267                    requires_expiry: true,
268                    residual_obligations_mandatory: true,
269                },
270            ],
271            downgrade_behavior: "emit_receipt_and_never_fall_back_to_permissive".into(),
272            reference_evaluator_version: REFERENCE_EVALUATOR_VERSION_V1.into(),
273        }
274    }
275
276    /// Looks up the obligation-family rule that governs a compiled family key.
277    pub fn family_rule(&self, family: &str) -> Option<&ObligationFamilyRuleV1> {
278        self.family_rules
279            .iter()
280            .find(|rule| rule.obligation_family == family)
281    }
282
283    /// Looks up the conflict-class rule that governs a named conflict class.
284    pub fn conflict_class(&self, class: &str) -> Option<&ConflictClassRuleV1> {
285        self.conflict_classes
286            .iter()
287            .find(|rule| rule.conflict_class == class)
288    }
289}