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 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 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 pub fn conflict_class(&self, class: &str) -> Option<&ConflictClassRuleV1> {
285 self.conflict_classes
286 .iter()
287 .find(|rule| rule.conflict_class == class)
288 }
289}