1use assurance_runtime::{
2 EvidenceCollectionPlanV1, HazardLibraryV1, HazardScenarioV1, MitigationPlaybookV1,
3 MonitorCatalogV1, RecertificationScheduleV1, RegulatoryRegimeProfileV1,
4 RequirementControlMapV1,
5};
6use attestation_exchange::{
7 VendorCertificationAdapterV1, VendorEvidenceTranslationV1, VendorRevocationHandlingV1,
8 VendorTrustRootBindingV1,
9};
10use authority_delegation::{
11 ApprovalMatrixV1, ConflictClassCatalogV1, DelegationMatrixV1, RoleCatalogV1,
12};
13use continuity_runtime::{
14 EscalationClockPolicyV1, IncidentTaxonomyV1, PagerRouteProfileV1, SeverityMatrixV1,
15};
16use verification_policy::{
17 AccessPurposeMatrixV1, AuditExtractionPolicyV1, ContinuityPolicyProfileV1,
18 CrossBoundaryTransferClassV1, DelegationPolicyProfileV1, EffectPolicyProfileV1,
19 PrivacyRetentionProfileV1, RedactionRuleSetV1, ReleasePolicyProfileV1,
20 ResidencyPolicyProfileV1, TenantBoundaryProfileV1,
21};
22
23use crate::compose::ObligationContributionV1;
24use crate::rules::{CompiledObligationKindV1, FoldClassV1};
25
26#[allow(clippy::too_many_arguments)]
27fn push_strings(
28 out: &mut Vec<ObligationContributionV1>,
29 family: impl Into<String>,
30 key: impl Into<String>,
31 kind: CompiledObligationKindV1,
32 fold: FoldClassV1,
33 values: Vec<String>,
34 source_profile_ref: &str,
35 explanation: impl Into<String>,
36) {
37 if values.is_empty() {
38 return;
39 }
40 out.push(ObligationContributionV1 {
41 obligation_family: family.into(),
42 obligation_key: key.into(),
43 output_kind: kind,
44 fold_class: fold,
45 string_values: values,
46 numeric_value: None,
47 expiry_at: None,
48 blocking: false,
49 source_profile_ref: source_profile_ref.to_string(),
50 admissible_exception_classes: Vec::new(),
51 explanation: explanation.into(),
52 });
53}
54
55fn to_wire_string<T: serde::Serialize>(value: &T) -> String {
56 serde_json::to_string(value)
57 .unwrap_or_else(|e| {
58 tracing::warn!(error = %e, "failed to serialize constitutional enum; using Debug fallback");
59 format!("{e}")
60 })
61 .trim_matches('"')
62 .to_string()
63}
64
65fn to_wire_strings<T: serde::Serialize>(values: &[T]) -> Vec<String> {
66 values.iter().map(to_wire_string).collect()
67}
68
69#[allow(clippy::too_many_arguments)]
70fn push_numeric(
71 out: &mut Vec<ObligationContributionV1>,
72 family: impl Into<String>,
73 key: impl Into<String>,
74 kind: CompiledObligationKindV1,
75 fold: FoldClassV1,
76 numeric_value: i64,
77 source_profile_ref: &str,
78 explanation: impl Into<String>,
79 admissible_exception_classes: Vec<String>,
80) {
81 out.push(ObligationContributionV1 {
82 obligation_family: family.into(),
83 obligation_key: key.into(),
84 output_kind: kind,
85 fold_class: fold,
86 string_values: Vec::new(),
87 numeric_value: Some(numeric_value),
88 expiry_at: None,
89 blocking: false,
90 source_profile_ref: source_profile_ref.to_string(),
91 admissible_exception_classes,
92 explanation: explanation.into(),
93 });
94}
95
96#[allow(clippy::too_many_arguments)]
97fn push_block(
98 out: &mut Vec<ObligationContributionV1>,
99 family: impl Into<String>,
100 key: impl Into<String>,
101 kind: CompiledObligationKindV1,
102 values: Vec<String>,
103 source_profile_ref: &str,
104 explanation: impl Into<String>,
105 admissible_exception_classes: Vec<String>,
106) {
107 out.push(ObligationContributionV1 {
108 obligation_family: family.into(),
109 obligation_key: key.into(),
110 output_kind: kind,
111 fold_class: FoldClassV1::BlockDominant,
112 string_values: values,
113 numeric_value: None,
114 expiry_at: None,
115 blocking: true,
116 source_profile_ref: source_profile_ref.to_string(),
117 admissible_exception_classes,
118 explanation: explanation.into(),
119 });
120}
121
122#[allow(clippy::too_many_arguments)]
123fn push_conflict(
124 out: &mut Vec<ObligationContributionV1>,
125 family: impl Into<String>,
126 key: impl Into<String>,
127 kind: CompiledObligationKindV1,
128 values: Vec<String>,
129 source_profile_ref: &str,
130 explanation: impl Into<String>,
131 admissible_exception_classes: Vec<String>,
132) {
133 if values.is_empty() {
134 return;
135 }
136 out.push(ObligationContributionV1 {
137 obligation_family: family.into(),
138 obligation_key: key.into(),
139 output_kind: kind,
140 fold_class: FoldClassV1::ConflictIfDifferent,
141 string_values: values,
142 numeric_value: None,
143 expiry_at: None,
144 blocking: false,
145 source_profile_ref: source_profile_ref.to_string(),
146 admissible_exception_classes,
147 explanation: explanation.into(),
148 });
149}
150
151pub fn from_effect_policy_profile(
153 profile: &EffectPolicyProfileV1,
154) -> Vec<ObligationContributionV1> {
155 let src = profile.effect_policy_profile_id.to_string();
156 let mut out = Vec::new();
157 push_strings(
158 &mut out,
159 "effect.allowed_run_modes",
160 "run_modes",
161 CompiledObligationKindV1::Effect,
162 FoldClassV1::Intersection,
163 to_wire_strings(&profile.allowed_run_modes),
164 &src,
165 "effect policy allowed run modes",
166 );
167 push_strings(
168 &mut out,
169 "effect.required_preflight_checks",
170 "preflight_checks",
171 CompiledObligationKindV1::Check,
172 FoldClassV1::Union,
173 to_wire_strings(&profile.required_preflight_checks),
174 &src,
175 "effect policy required preflight checks",
176 );
177 push_strings(
178 &mut out,
179 "monitor.required",
180 "observation_classes",
181 CompiledObligationKindV1::Monitor,
182 FoldClassV1::Union,
183 to_wire_strings(&profile.required_observation_classes),
184 &src,
185 "effect policy required observation classes",
186 );
187 push_strings(
188 &mut out,
189 "compensation.required",
190 "effect_classes",
191 CompiledObligationKindV1::Compensation,
192 FoldClassV1::Union,
193 to_wire_strings(&profile.requires_compensation_plan_for),
194 &src,
195 "effect policy compensation requirements",
196 );
197 if profile.block_live_without_commit {
198 push_block(
199 &mut out,
200 "effect.live_commit_required",
201 "live_without_commit",
202 CompiledObligationKindV1::Block,
203 vec!["live_without_commit".into()],
204 &src,
205 "live execution without commit is blocked",
206 vec!["break_glass".into()],
207 );
208 }
209 out
210}
211
212pub fn from_delegation_policy_profile(
214 profile: &DelegationPolicyProfileV1,
215) -> Vec<ObligationContributionV1> {
216 let src = profile.delegation_policy_profile_id.to_string();
217 let mut out = Vec::new();
218 push_numeric(
219 &mut out,
220 "delegation.max_depth",
221 "delegation_depth",
222 CompiledObligationKindV1::Delegation,
223 FoldClassV1::MinOfMaxima,
224 profile.max_delegation_depth,
225 &src,
226 "delegation depth cap",
227 vec!["break_glass".into()],
228 );
229 if profile.break_glass_requires_post_hoc_review {
230 push_strings(
231 &mut out,
232 "review.post_hoc",
233 "break_glass",
234 CompiledObligationKindV1::PostHocReview,
235 FoldClassV1::Union,
236 vec!["delegation_break_glass_review".into()],
237 &src,
238 "break-glass requires post-hoc review",
239 );
240 }
241 for forbidden in &profile.forbidden_role_combinations {
242 push_block(
243 &mut out,
244 "delegation.forbidden_role_combination",
245 to_wire_string(forbidden),
246 CompiledObligationKindV1::Block,
247 vec![to_wire_string(forbidden)],
248 &src,
249 "forbidden role combination",
250 vec!["conflict_override".into(), "break_glass".into()],
251 );
252 }
253 if profile.require_typed_authority_chain {
254 push_strings(
255 &mut out,
256 "delegation.required_chain_shape",
257 "typed_authority_chain",
258 CompiledObligationKindV1::Delegation,
259 FoldClassV1::Union,
260 vec!["typed_authority_chain".into()],
261 &src,
262 "delegation requires typed authority chain",
263 );
264 }
265 out
266}
267
268pub fn from_release_policy_profile(
270 profile: &ReleasePolicyProfileV1,
271) -> Vec<ObligationContributionV1> {
272 let src = profile.release_policy_profile_id.to_string();
273 let mut out = Vec::new();
274 push_strings(
275 &mut out,
276 "assurance.required_sections",
277 "assurance_sections",
278 CompiledObligationKindV1::Assurance,
279 FoldClassV1::Union,
280 to_wire_strings(&profile.required_assurance_sections),
281 &src,
282 "required assurance sections",
283 );
284 push_strings(
285 &mut out,
286 "monitor.required",
287 "release_monitor_classes",
288 CompiledObligationKindV1::Monitor,
289 FoldClassV1::Union,
290 to_wire_strings(&profile.required_monitor_classes),
291 &src,
292 "release monitor requirements",
293 );
294 if profile.block_on_open_obligations {
295 push_block(
296 &mut out,
297 "release.block_on_open_obligations",
298 "open_obligations",
299 CompiledObligationKindV1::Block,
300 vec!["open_obligations".into()],
301 &src,
302 "release blocks on open obligations",
303 Vec::new(),
304 );
305 }
306 if profile.forbid_score_only_gate {
307 push_block(
308 &mut out,
309 "release.forbid_score_only_gate",
310 "score_only_gate",
311 CompiledObligationKindV1::Block,
312 vec!["score_only_gate".into()],
313 &src,
314 "score-only gate is forbidden",
315 Vec::new(),
316 );
317 }
318 out
319}
320
321pub fn from_continuity_policy_profile(
323 profile: &ContinuityPolicyProfileV1,
324) -> Vec<ObligationContributionV1> {
325 let src = profile.continuity_policy_profile_id.to_string();
326 let mut out = Vec::new();
327 push_strings(
328 &mut out,
329 "continuity.forensic_freeze_surfaces",
330 "freeze_surfaces",
331 CompiledObligationKindV1::Continuity,
332 FoldClassV1::Union,
333 to_wire_strings(&profile.required_forensic_freeze_surfaces),
334 &src,
335 "required forensic freeze surfaces",
336 );
337 push_numeric(
338 &mut out,
339 "continuity.exception_ttl_minutes",
340 "exception_ttl",
341 CompiledObligationKindV1::Continuity,
342 FoldClassV1::MinOfMaxima,
343 profile.continuity_exception_ttl_minutes,
344 &src,
345 "continuity exception ttl cap",
346 vec!["incident_override".into()],
347 );
348 push_strings(
349 &mut out,
350 "review.post_hoc",
351 "postmortem_severity",
352 CompiledObligationKindV1::PostHocReview,
353 FoldClassV1::Union,
354 to_wire_strings(&profile.requires_postmortem_for_severity),
355 &src,
356 "required postmortems by severity",
357 );
358 if profile.require_error_budget_linkage {
359 push_block(
360 &mut out,
361 "continuity.require_error_budget_linkage",
362 "error_budget_linkage",
363 CompiledObligationKindV1::Block,
364 vec!["error_budget_linkage".into()],
365 &src,
366 "continuity paths require error budget linkage",
367 vec!["incident_override".into()],
368 );
369 }
370 out
371}
372
373pub fn from_privacy_retention_profile(
375 profile: &PrivacyRetentionProfileV1,
376) -> Vec<ObligationContributionV1> {
377 let src = profile.privacy_retention_profile_id.clone();
378 let mut out = Vec::new();
379 push_conflict(
380 &mut out,
381 "privacy.retention_class",
382 "default_retention_class",
383 CompiledObligationKindV1::Disclosure,
384 vec![to_wire_string(&profile.default_retention_class)],
385 &src,
386 "default retention class",
387 vec!["privacy_exception".into()],
388 );
389 push_conflict(
390 &mut out,
391 "privacy.archive_restore_expectation",
392 "archive_restore_expectation",
393 CompiledObligationKindV1::Replay,
394 vec![to_wire_string(&profile.archive_restore_expectation)],
395 &src,
396 "archive restore expectation",
397 Vec::new(),
398 );
399 push_conflict(
400 &mut out,
401 "privacy.cross_border_transfer_default",
402 "cross_border_transfer_default",
403 CompiledObligationKindV1::Disclosure,
404 vec![to_wire_string(&profile.cross_border_transfer_default)],
405 &src,
406 "cross-border transfer default",
407 vec!["locality_exception".into()],
408 );
409 push_strings(
410 &mut out,
411 "disclosure.required_redaction_rule_set",
412 "default_redaction_rule_set",
413 CompiledObligationKindV1::Disclosure,
414 FoldClassV1::Union,
415 vec![profile.default_redaction_rule_set_id.clone()],
416 &src,
417 "default redaction rule set",
418 );
419 if profile.compaction_requires_receipt {
420 push_strings(
421 &mut out,
422 "replay.compaction_receipt_required",
423 "compaction_receipt",
424 CompiledObligationKindV1::Replay,
425 FoldClassV1::Union,
426 vec!["compaction_receipt_required".into()],
427 &src,
428 "compaction requires receipt",
429 );
430 }
431 out
432}
433
434pub fn from_redaction_rule_set(rule_set: &RedactionRuleSetV1) -> Vec<ObligationContributionV1> {
436 let src = rule_set.redaction_rule_set_id.clone();
437 let mut out = Vec::new();
438 push_strings(
439 &mut out,
440 "disclosure.redaction_actions",
441 "field_actions",
442 CompiledObligationKindV1::Disclosure,
443 FoldClassV1::Union,
444 rule_set
445 .field_actions
446 .iter()
447 .map(|action| format!("{}:{}", action.field, action.action))
448 .collect(),
449 &src,
450 "redaction actions",
451 );
452 push_conflict(
453 &mut out,
454 "disclosure.redaction_reversibility",
455 "reversibility_class",
456 CompiledObligationKindV1::Disclosure,
457 vec![to_wire_string(&rule_set.reversibility_class)],
458 &src,
459 "redaction reversibility",
460 vec!["disclosure_exception".into()],
461 );
462 push_strings(
463 &mut out,
464 "approval.required",
465 "redaction_approval_requirement",
466 CompiledObligationKindV1::Approval,
467 FoldClassV1::Union,
468 vec![to_wire_string(&rule_set.approval_requirement)],
469 &src,
470 "redaction approval requirement",
471 );
472 push_conflict(
473 &mut out,
474 "disclosure.budget_class",
475 "default_disclosure_budget_class",
476 CompiledObligationKindV1::Disclosure,
477 vec![to_wire_string(&rule_set.default_disclosure_budget_class)],
478 &src,
479 "disclosure budget class",
480 vec!["disclosure_exception".into()],
481 );
482 out
483}
484
485pub fn from_access_purpose_matrix(matrix: &AccessPurposeMatrixV1) -> Vec<ObligationContributionV1> {
487 let src = matrix.access_purpose_matrix_id.clone();
488 let mut out = Vec::new();
489 push_strings(
490 &mut out,
491 "disclosure.access_purpose_rules",
492 "purpose_rules",
493 CompiledObligationKindV1::Disclosure,
494 FoldClassV1::Union,
495 matrix
496 .purpose_rules
497 .iter()
498 .map(|rule| format!("{}:{}", rule.actor_class, rule.purpose))
499 .collect(),
500 &src,
501 "access purpose rules",
502 );
503 push_conflict(
504 &mut out,
505 "disclosure.default_access_decision",
506 "default_decision",
507 CompiledObligationKindV1::Disclosure,
508 vec![to_wire_string(&matrix.default_decision)],
509 &src,
510 "default access decision",
511 vec!["privacy_exception".into()],
512 );
513 push_strings(
514 &mut out,
515 "approval.required",
516 "access_elevation_path",
517 CompiledObligationKindV1::Approval,
518 FoldClassV1::Union,
519 vec![matrix.elevation_path.clone()],
520 &src,
521 "access elevation path",
522 );
523 if matrix.audit_logging_required {
524 push_strings(
525 &mut out,
526 "replay.audit_logging_required",
527 "access_audit_logging",
528 CompiledObligationKindV1::Replay,
529 FoldClassV1::Union,
530 vec!["audit_logging_required".into()],
531 &src,
532 "access requires audit logging",
533 );
534 }
535 out
536}
537
538pub fn from_audit_extraction_policy(
540 policy: &AuditExtractionPolicyV1,
541) -> Vec<ObligationContributionV1> {
542 let src = policy.audit_extraction_policy_id.clone();
543 let mut out = Vec::new();
544 push_strings(
545 &mut out,
546 "disclosure.allowed_audit_artifact_families",
547 "allowed_audit_artifacts",
548 CompiledObligationKindV1::Disclosure,
549 FoldClassV1::Intersection,
550 policy.allowed_artifact_families.clone(),
551 &src,
552 "allowed audit artifact families",
553 );
554 push_strings(
555 &mut out,
556 "disclosure.required_redaction_rule_set",
557 "audit_redaction_rule_set",
558 CompiledObligationKindV1::Disclosure,
559 FoldClassV1::Union,
560 vec![policy.required_redaction_rule_set_id.clone()],
561 &src,
562 "required audit redaction rule set",
563 );
564 push_conflict(
565 &mut out,
566 "disclosure.audit_budget_class",
567 "audit_budget_class",
568 CompiledObligationKindV1::Disclosure,
569 vec![to_wire_string(&policy.disclosure_budget_class)],
570 &src,
571 "audit disclosure budget class",
572 vec!["disclosure_exception".into()],
573 );
574 push_conflict(
575 &mut out,
576 "disclosure.export_format",
577 "export_package_format",
578 CompiledObligationKindV1::Disclosure,
579 vec![to_wire_string(&policy.export_package_format)],
580 &src,
581 "audit export package format",
582 vec!["disclosure_exception".into()],
583 );
584 push_numeric(
585 &mut out,
586 "disclosure.audit_expiry_hours",
587 "audit_expiry_hours",
588 CompiledObligationKindV1::Disclosure,
589 FoldClassV1::MinOfMaxima,
590 policy.expiry_hours,
591 &src,
592 "audit extraction expiry",
593 Vec::new(),
594 );
595 if policy.evidence_preservation_required {
596 push_strings(
597 &mut out,
598 "evidence.preservation_required",
599 "audit_evidence_preservation",
600 CompiledObligationKindV1::Evidence,
601 FoldClassV1::Union,
602 vec!["evidence_preservation_required".into()],
603 &src,
604 "audit evidence preservation required",
605 );
606 }
607 out
608}
609
610pub fn from_residency_policy_profile(
612 profile: &ResidencyPolicyProfileV1,
613) -> Vec<ObligationContributionV1> {
614 let src = profile.residency_policy_profile_id.clone();
615 let mut out = Vec::new();
616 push_strings(
617 &mut out,
618 "residency.allowed_storage_regions",
619 "storage_regions",
620 CompiledObligationKindV1::Residency,
621 FoldClassV1::Intersection,
622 profile.allowed_storage_regions.clone(),
623 &src,
624 "allowed storage regions",
625 );
626 push_strings(
627 &mut out,
628 "residency.allowed_execution_regions",
629 "execution_regions",
630 CompiledObligationKindV1::Residency,
631 FoldClassV1::Intersection,
632 profile.allowed_execution_regions.clone(),
633 &src,
634 "allowed execution regions",
635 );
636 push_strings(
637 &mut out,
638 "residency.allowed_replay_regions",
639 "replay_regions",
640 CompiledObligationKindV1::Replay,
641 FoldClassV1::Intersection,
642 profile.allowed_replay_regions.clone(),
643 &src,
644 "allowed replay regions",
645 );
646 for transfer_class in &profile.forbidden_transfer_classes {
647 push_block(
648 &mut out,
649 "residency.forbidden_transfer_classes",
650 transfer_class.clone(),
651 CompiledObligationKindV1::Block,
652 vec![transfer_class.clone()],
653 &src,
654 "forbidden transfer class",
655 vec!["locality_exception".into()],
656 );
657 }
658 push_strings(
659 &mut out,
660 "approval.required",
661 "default_exception_path",
662 CompiledObligationKindV1::Approval,
663 FoldClassV1::Union,
664 vec![profile.default_exception_path.clone()],
665 &src,
666 "default locality exception path",
667 );
668 out
669}
670
671pub fn from_tenant_boundary_profile(
673 profile: &TenantBoundaryProfileV1,
674) -> Vec<ObligationContributionV1> {
675 let src = profile.tenant_boundary_profile_id.clone();
676 let mut out = Vec::new();
677 push_conflict(
678 &mut out,
679 "tenancy.key_kind",
680 "tenant_key_kind",
681 CompiledObligationKindV1::Tenancy,
682 vec![to_wire_string(&profile.tenant_key_kind)],
683 &src,
684 "tenant key kind",
685 vec!["boundary_exception".into()],
686 );
687 push_conflict(
688 &mut out,
689 "tenancy.isolation_class",
690 "isolation_class",
691 CompiledObligationKindV1::Tenancy,
692 vec![to_wire_string(&profile.isolation_class)],
693 &src,
694 "tenant isolation class",
695 vec!["boundary_exception".into()],
696 );
697 push_strings(
698 &mut out,
699 "tenancy.shared_service_allowances",
700 "shared_service_allowances",
701 CompiledObligationKindV1::Tenancy,
702 FoldClassV1::Intersection,
703 profile.shared_service_allowances.clone(),
704 &src,
705 "shared service allowances",
706 );
707 push_block(
708 &mut out,
709 "tenancy.cross_tenant_query_default",
710 to_wire_string(&profile.cross_tenant_query_default),
711 CompiledObligationKindV1::Block,
712 vec![to_wire_string(&profile.cross_tenant_query_default)],
713 &src,
714 "cross-tenant query default",
715 vec!["boundary_exception".into()],
716 );
717 push_conflict(
718 &mut out,
719 "tenancy.key_management_segregation",
720 "key_management_segregation",
721 CompiledObligationKindV1::Tenancy,
722 vec![profile.key_management_segregation.clone()],
723 &src,
724 "key management segregation",
725 vec!["boundary_exception".into()],
726 );
727 out
728}
729
730pub fn from_cross_boundary_transfer_class(
732 transfer_class: &CrossBoundaryTransferClassV1,
733) -> Vec<ObligationContributionV1> {
734 let src = transfer_class.cross_boundary_transfer_class_id.clone();
735 let mut out = Vec::new();
736 push_strings(
737 &mut out,
738 "residency.allowed_transfer_artifact_families",
739 format!(
740 "{}:{}",
741 transfer_class.source_class, transfer_class.destination_class
742 ),
743 CompiledObligationKindV1::Residency,
744 FoldClassV1::Intersection,
745 transfer_class.allowed_artifact_families.clone(),
746 &src,
747 "allowed artifact families for cross-boundary transfer",
748 );
749 push_strings(
750 &mut out,
751 "evidence.required_attestation",
752 "cross_boundary_attestation",
753 CompiledObligationKindV1::Evidence,
754 FoldClassV1::Union,
755 vec![to_wire_string(&transfer_class.required_attestation)],
756 &src,
757 "required attestation for cross-boundary transfer",
758 );
759 push_strings(
760 &mut out,
761 "disclosure.required_policy_class",
762 "cross_boundary_disclosure_class",
763 CompiledObligationKindV1::Disclosure,
764 FoldClassV1::Union,
765 vec![to_wire_string(
766 &transfer_class.required_disclosure_policy_class,
767 )],
768 &src,
769 "required disclosure policy class",
770 );
771 push_conflict(
772 &mut out,
773 "residency.downgrade_behavior",
774 "cross_boundary_downgrade_behavior",
775 CompiledObligationKindV1::Warning,
776 vec![to_wire_string(&transfer_class.downgrade_behavior)],
777 &src,
778 "downgrade behavior for cross-boundary transfer",
779 vec!["locality_exception".into()],
780 );
781 out
782}
783
784pub fn from_role_catalog(catalog: &RoleCatalogV1) -> Vec<ObligationContributionV1> {
786 let src = catalog.role_catalog_id.clone();
787 let mut out = Vec::new();
788 push_conflict(
789 &mut out,
790 "delegation.default_autonomy_ceiling",
791 "autonomy_ceiling",
792 CompiledObligationKindV1::Delegation,
793 vec![catalog.default_autonomy_ceiling.clone()],
794 &src,
795 "default autonomy ceiling",
796 vec!["break_glass".into()],
797 );
798 push_conflict(
799 &mut out,
800 "delegation.scope_rule",
801 "scope_rule",
802 CompiledObligationKindV1::Delegation,
803 vec![catalog.scope_rule.clone()],
804 &src,
805 "delegation scope rule",
806 Vec::new(),
807 );
808 push_numeric(
809 &mut out,
810 "review.role_catalog_cycle_days",
811 "role_catalog_cycle_days",
812 CompiledObligationKindV1::PostHocReview,
813 FoldClassV1::MinOfMaxima,
814 catalog.review_cycle_days,
815 &src,
816 "role catalog review cycle days",
817 Vec::new(),
818 );
819 push_strings(
820 &mut out,
821 "delegation.role_definitions",
822 "role_definitions",
823 CompiledObligationKindV1::Delegation,
824 FoldClassV1::Union,
825 catalog
826 .role_definitions
827 .iter()
828 .map(|role| format!("{}:{}", role.role, role.capabilities.join("|")))
829 .collect(),
830 &src,
831 "role definitions",
832 );
833 out
834}
835
836pub fn from_delegation_matrix(matrix: &DelegationMatrixV1) -> Vec<ObligationContributionV1> {
838 let src = matrix.delegation_matrix_id.clone();
839 let mut out = Vec::new();
840 push_strings(
841 &mut out,
842 "delegation.allowed_edges",
843 "allowed_edges",
844 CompiledObligationKindV1::Delegation,
845 FoldClassV1::Intersection,
846 matrix
847 .allowed_edges
848 .iter()
849 .map(|edge| format!("{}>{}:{}", edge.from_role, edge.to_role, edge.capability))
850 .collect(),
851 &src,
852 "allowed delegation edges",
853 );
854 push_numeric(
855 &mut out,
856 "delegation.max_depth",
857 "delegation_depth",
858 CompiledObligationKindV1::Delegation,
859 FoldClassV1::MinOfMaxima,
860 matrix.max_delegation_depth,
861 &src,
862 "delegation matrix max depth",
863 vec!["break_glass".into()],
864 );
865 push_strings(
866 &mut out,
867 "delegation.required_lease_classes",
868 "required_lease_classes",
869 CompiledObligationKindV1::Delegation,
870 FoldClassV1::Union,
871 matrix.required_lease_classes.clone(),
872 &src,
873 "required delegation lease classes",
874 );
875 for pattern in &matrix.forbidden_chain_patterns {
876 push_block(
877 &mut out,
878 "delegation.forbidden_chain_pattern",
879 pattern.clone(),
880 CompiledObligationKindV1::Block,
881 vec![pattern.clone()],
882 &src,
883 "forbidden delegation chain pattern",
884 vec!["break_glass".into()],
885 );
886 }
887 out
888}
889
890pub fn from_approval_matrix(matrix: &ApprovalMatrixV1) -> Vec<ObligationContributionV1> {
892 let src = matrix.approval_matrix_id.clone();
893 let mut out = Vec::new();
894 push_strings(
895 &mut out,
896 "approval.required",
897 "approval_rules",
898 CompiledObligationKindV1::Approval,
899 FoldClassV1::Union,
900 matrix
901 .approval_rules
902 .iter()
903 .map(|rule| format!("{}:{}", rule.action_class, rule.required_roles.join("|")))
904 .collect(),
905 &src,
906 "approval rules",
907 );
908 push_conflict(
909 &mut out,
910 "approval.default_quorum",
911 "default_quorum",
912 CompiledObligationKindV1::Approval,
913 vec![matrix.default_quorum.clone()],
914 &src,
915 "default approval quorum",
916 Vec::new(),
917 );
918 push_strings(
919 &mut out,
920 "approval.required",
921 "independent_review",
922 CompiledObligationKindV1::Approval,
923 FoldClassV1::Union,
924 matrix.requires_independent_review_for.clone(),
925 &src,
926 "independent review requirements",
927 );
928 push_numeric(
929 &mut out,
930 "review.post_hoc_window_hours",
931 "break_glass_post_hoc_review_hours",
932 CompiledObligationKindV1::PostHocReview,
933 FoldClassV1::MinOfMaxima,
934 matrix.break_glass_post_hoc_review_hours,
935 &src,
936 "break-glass post-hoc review window",
937 vec!["break_glass".into()],
938 );
939 out
940}
941
942pub fn from_conflict_class_catalog(
944 catalog: &ConflictClassCatalogV1,
945) -> Vec<ObligationContributionV1> {
946 let src = catalog.conflict_class_catalog_id.clone();
947 let mut out = Vec::new();
948 push_strings(
949 &mut out,
950 "warning.conflict_classes",
951 "conflict_classes",
952 CompiledObligationKindV1::Warning,
953 FoldClassV1::Union,
954 catalog
955 .conflict_classes
956 .iter()
957 .map(|rule| {
958 format!(
959 "{}:{}",
960 rule.conflict_class,
961 rule.disallowed_roles.join("|")
962 )
963 })
964 .collect(),
965 &src,
966 "conflict class catalog entries",
967 );
968 push_conflict(
969 &mut out,
970 "approval.default_recusal_behavior",
971 "default_recusal_behavior",
972 CompiledObligationKindV1::Approval,
973 vec![catalog.default_recusal_behavior.clone()],
974 &src,
975 "default recusal behavior",
976 vec!["conflict_override".into()],
977 );
978 push_strings(
979 &mut out,
980 "approval.required",
981 "conflict_override_path",
982 CompiledObligationKindV1::Approval,
983 FoldClassV1::Union,
984 vec![catalog.override_path.clone()],
985 &src,
986 "conflict override path",
987 );
988 if catalog.disclosure_required {
989 push_strings(
990 &mut out,
991 "disclosure.conflict_disclosure_required",
992 "conflict_disclosure",
993 CompiledObligationKindV1::Disclosure,
994 FoldClassV1::Union,
995 vec!["conflict_disclosure_required".into()],
996 &src,
997 "conflict disclosure required",
998 );
999 }
1000 out
1001}
1002
1003pub fn from_regulatory_regime_profile(
1005 profile: &RegulatoryRegimeProfileV1,
1006) -> Vec<ObligationContributionV1> {
1007 let src = profile.regulatory_regime_profile_id.clone();
1008 let mut out = Vec::new();
1009 push_strings(
1010 &mut out,
1011 "assurance.mandatory_control_families",
1012 "control_families",
1013 CompiledObligationKindV1::Assurance,
1014 FoldClassV1::Union,
1015 profile.mandatory_control_families.clone(),
1016 &src,
1017 "mandatory control families",
1018 );
1019 push_strings(
1020 &mut out,
1021 "assurance.covered_products",
1022 "covered_products",
1023 CompiledObligationKindV1::Assurance,
1024 FoldClassV1::Union,
1025 profile.covered_products.clone(),
1026 &src,
1027 "covered products",
1028 );
1029 push_numeric(
1030 &mut out,
1031 "assurance.audit_cycle_days",
1032 "audit_cycle_days",
1033 CompiledObligationKindV1::Assurance,
1034 FoldClassV1::MinOfMaxima,
1035 profile.audit_cycle_days,
1036 &src,
1037 "regime audit cycle days",
1038 Vec::new(),
1039 );
1040 push_conflict(
1041 &mut out,
1042 "assurance.regime_version",
1043 "regime_version",
1044 CompiledObligationKindV1::Assurance,
1045 vec![profile.regime_version.clone()],
1046 &src,
1047 "regime version",
1048 Vec::new(),
1049 );
1050 out
1051}
1052
1053pub fn from_requirement_control_map(
1055 map: &RequirementControlMapV1,
1056) -> Vec<ObligationContributionV1> {
1057 let src = map.requirement_control_map_id.clone();
1058 let mut out = Vec::new();
1059 push_strings(
1060 &mut out,
1061 "assurance.requirement_mappings",
1062 "requirement_control_mappings",
1063 CompiledObligationKindV1::Assurance,
1064 FoldClassV1::Union,
1065 map.mappings
1066 .iter()
1067 .map(|entry| {
1068 format!(
1069 "{}:{}:{}",
1070 entry.requirement, entry.control, entry.evidence_family
1071 )
1072 })
1073 .collect(),
1074 &src,
1075 "requirement-to-control mappings",
1076 );
1077 push_conflict(
1078 &mut out,
1079 "assurance.gap_classification_default",
1080 "gap_classification_default",
1081 CompiledObligationKindV1::Assurance,
1082 vec![map.gap_classification_default.clone()],
1083 &src,
1084 "gap classification default",
1085 Vec::new(),
1086 );
1087 push_strings(
1088 &mut out,
1089 "assurance.owner_ref",
1090 "requirement_control_map_owner",
1091 CompiledObligationKindV1::Assurance,
1092 FoldClassV1::Union,
1093 vec![map.owner_ref.clone()],
1094 &src,
1095 "requirement-control map owner",
1096 );
1097 out
1098}
1099
1100pub fn from_evidence_collection_plan(
1102 plan: &EvidenceCollectionPlanV1,
1103) -> Vec<ObligationContributionV1> {
1104 let src = plan.evidence_collection_plan_id.clone();
1105 let mut out = Vec::new();
1106 push_strings(
1107 &mut out,
1108 "evidence.required_classes",
1109 "required_evidence_classes",
1110 CompiledObligationKindV1::Evidence,
1111 FoldClassV1::Union,
1112 plan.required_evidence_classes
1113 .iter()
1114 .map(|entry| format!("{}:{}", entry.evidence_class, entry.minimum_family))
1115 .collect(),
1116 &src,
1117 "required evidence classes",
1118 );
1119 push_conflict(
1120 &mut out,
1121 "evidence.collection_cadence",
1122 "collection_cadence",
1123 CompiledObligationKindV1::Evidence,
1124 vec![plan.collection_cadence.clone()],
1125 &src,
1126 "evidence collection cadence",
1127 Vec::new(),
1128 );
1129 push_conflict(
1130 &mut out,
1131 "evidence.retention_class",
1132 "retention_class",
1133 CompiledObligationKindV1::Evidence,
1134 vec![plan.retention_class.clone()],
1135 &src,
1136 "evidence retention class",
1137 Vec::new(),
1138 );
1139 push_strings(
1140 &mut out,
1141 "evidence.owner_ref",
1142 "evidence_collection_owner",
1143 CompiledObligationKindV1::Evidence,
1144 FoldClassV1::Union,
1145 vec![plan.owner_ref.clone()],
1146 &src,
1147 "evidence collection owner",
1148 );
1149 out
1150}
1151
1152pub fn from_recertification_schedule(
1154 schedule: &RecertificationScheduleV1,
1155) -> Vec<ObligationContributionV1> {
1156 let src = schedule.recertification_schedule_id.clone();
1157 let mut out = Vec::new();
1158 push_numeric(
1159 &mut out,
1160 "assurance.review_interval_days",
1161 "recertification_review_interval",
1162 CompiledObligationKindV1::Assurance,
1163 FoldClassV1::MinOfMaxima,
1164 schedule.review_interval_days,
1165 &src,
1166 "recertification review interval",
1167 Vec::new(),
1168 );
1169 push_strings(
1170 &mut out,
1171 "assurance.recertification_triggers",
1172 "trigger_classes",
1173 CompiledObligationKindV1::Assurance,
1174 FoldClassV1::Union,
1175 schedule.trigger_classes.clone(),
1176 &src,
1177 "recertification trigger classes",
1178 );
1179 push_numeric(
1180 &mut out,
1181 "assurance.grace_window_days",
1182 "grace_window_days",
1183 CompiledObligationKindV1::Assurance,
1184 FoldClassV1::MinOfMaxima,
1185 schedule.grace_window_days,
1186 &src,
1187 "recertification grace window",
1188 Vec::new(),
1189 );
1190 push_block(
1191 &mut out,
1192 "assurance.blocked_state_on_expiry",
1193 schedule.blocked_state_on_expiry.clone(),
1194 CompiledObligationKindV1::Block,
1195 vec![schedule.blocked_state_on_expiry.clone()],
1196 &src,
1197 "blocked state on recertification expiry",
1198 Vec::new(),
1199 );
1200 out
1201}
1202
1203pub fn from_hazard_library(library: &HazardLibraryV1) -> Vec<ObligationContributionV1> {
1205 let src = library.hazard_library_id.clone();
1206 let mut out = Vec::new();
1207 push_strings(
1208 &mut out,
1209 "warning.hazard_families",
1210 "hazard_families",
1211 CompiledObligationKindV1::Warning,
1212 FoldClassV1::Union,
1213 library.hazard_families.clone(),
1214 &src,
1215 "hazard families",
1216 );
1217 push_conflict(
1218 &mut out,
1219 "warning.hazard_scoring_model",
1220 "scoring_model_ref",
1221 CompiledObligationKindV1::Warning,
1222 vec![library.scoring_model_ref.clone()],
1223 &src,
1224 "hazard scoring model",
1225 Vec::new(),
1226 );
1227 push_strings(
1228 &mut out,
1229 "assurance.linked_operating_envelopes",
1230 "linked_operating_envelopes",
1231 CompiledObligationKindV1::Assurance,
1232 FoldClassV1::Union,
1233 library.linked_operating_envelopes.clone(),
1234 &src,
1235 "linked operating envelopes",
1236 );
1237 out
1238}
1239
1240pub fn from_hazard_scenario(scenario: &HazardScenarioV1) -> Vec<ObligationContributionV1> {
1242 let src = scenario.hazard_scenario_id.clone();
1243 let mut out = Vec::new();
1244 push_strings(
1245 &mut out,
1246 "warning.hazard_trigger_conditions",
1247 "trigger_conditions",
1248 CompiledObligationKindV1::Warning,
1249 FoldClassV1::Union,
1250 scenario.trigger_conditions.clone(),
1251 &src,
1252 "hazard trigger conditions",
1253 );
1254 push_strings(
1255 &mut out,
1256 "continuity.affected_surfaces",
1257 "affected_surfaces",
1258 CompiledObligationKindV1::Continuity,
1259 FoldClassV1::Union,
1260 scenario.affected_surfaces.clone(),
1261 &src,
1262 "hazard affected surfaces",
1263 );
1264 push_conflict(
1265 &mut out,
1266 "warning.hazard_severity_baseline",
1267 "severity_baseline",
1268 CompiledObligationKindV1::Warning,
1269 vec![scenario.severity_baseline.clone()],
1270 &src,
1271 "hazard severity baseline",
1272 Vec::new(),
1273 );
1274 push_strings(
1275 &mut out,
1276 "monitor.required",
1277 "hazard_required_monitor_refs",
1278 CompiledObligationKindV1::Monitor,
1279 FoldClassV1::Union,
1280 scenario.required_monitor_refs.clone(),
1281 &src,
1282 "hazard required monitor refs",
1283 );
1284 out
1285}
1286
1287pub fn from_monitor_catalog(catalog: &MonitorCatalogV1) -> Vec<ObligationContributionV1> {
1289 let src = catalog.monitor_catalog_id.clone();
1290 let mut out = Vec::new();
1291 push_strings(
1292 &mut out,
1293 "monitor.required",
1294 "monitor_definitions",
1295 CompiledObligationKindV1::Monitor,
1296 FoldClassV1::Union,
1297 catalog
1298 .monitor_definitions
1299 .iter()
1300 .map(|entry| format!("{}:{}", entry.monitor, entry.threshold))
1301 .collect(),
1302 &src,
1303 "monitor definitions",
1304 );
1305 push_conflict(
1306 &mut out,
1307 "monitor.evaluation_cadence",
1308 "evaluation_cadence",
1309 CompiledObligationKindV1::Monitor,
1310 vec![catalog.evaluation_cadence.clone()],
1311 &src,
1312 "monitor evaluation cadence",
1313 Vec::new(),
1314 );
1315 push_conflict(
1316 &mut out,
1317 "monitor.false_positive_budget",
1318 "false_positive_budget",
1319 CompiledObligationKindV1::Monitor,
1320 vec![catalog.false_positive_budget.clone()],
1321 &src,
1322 "monitor false-positive budget",
1323 Vec::new(),
1324 );
1325 push_strings(
1326 &mut out,
1327 "monitor.owner_ref",
1328 "monitor_catalog_owner",
1329 CompiledObligationKindV1::Monitor,
1330 FoldClassV1::Union,
1331 vec![catalog.owner_ref.clone()],
1332 &src,
1333 "monitor catalog owner",
1334 );
1335 out
1336}
1337
1338pub fn from_mitigation_playbook(playbook: &MitigationPlaybookV1) -> Vec<ObligationContributionV1> {
1340 let src = playbook.mitigation_playbook_id.clone();
1341 let mut out = Vec::new();
1342 push_strings(
1343 &mut out,
1344 "continuity.containment_steps",
1345 "containment_steps",
1346 CompiledObligationKindV1::Continuity,
1347 FoldClassV1::Union,
1348 playbook.containment_steps.clone(),
1349 &src,
1350 "mitigation containment steps",
1351 );
1352 push_strings(
1353 &mut out,
1354 "continuity.recovery_steps",
1355 "recovery_steps",
1356 CompiledObligationKindV1::Continuity,
1357 FoldClassV1::Union,
1358 playbook.recovery_steps.clone(),
1359 &src,
1360 "mitigation recovery steps",
1361 );
1362 push_strings(
1363 &mut out,
1364 "approval.required",
1365 "mitigation_approvals",
1366 CompiledObligationKindV1::Approval,
1367 FoldClassV1::Union,
1368 playbook.approval_refs.clone(),
1369 &src,
1370 "mitigation approval refs",
1371 );
1372 push_strings(
1373 &mut out,
1374 "continuity.success_criteria",
1375 "success_criteria",
1376 CompiledObligationKindV1::Continuity,
1377 FoldClassV1::Union,
1378 playbook.success_criteria.clone(),
1379 &src,
1380 "mitigation success criteria",
1381 );
1382 push_strings(
1383 &mut out,
1384 "warning.hazard_refs",
1385 "hazard_refs",
1386 CompiledObligationKindV1::Warning,
1387 FoldClassV1::Union,
1388 playbook.hazard_refs.clone(),
1389 &src,
1390 "mitigation hazard refs",
1391 );
1392 out
1393}
1394
1395pub fn from_vendor_certification_adapter(
1397 adapter: &VendorCertificationAdapterV1,
1398) -> Vec<ObligationContributionV1> {
1399 let src = adapter.vendor_certification_adapter_id.clone();
1400 let mut out = Vec::new();
1401 push_conflict(
1402 &mut out,
1403 "vendor.translation_mode",
1404 "translation_mode",
1405 CompiledObligationKindV1::Warning,
1406 vec![adapter.translation_mode.to_string()],
1407 &src,
1408 "vendor translation mode",
1409 Vec::new(),
1410 );
1411 push_strings(
1412 &mut out,
1413 "vendor.covered_artifact_families",
1414 "covered_artifact_families",
1415 CompiledObligationKindV1::Evidence,
1416 FoldClassV1::Intersection,
1417 adapter.covered_artifact_families.clone(),
1418 &src,
1419 "vendor covered artifact families",
1420 );
1421 push_conflict(
1422 &mut out,
1423 "vendor.support_window",
1424 "support_window",
1425 CompiledObligationKindV1::Warning,
1426 vec![adapter.support_window.clone()],
1427 &src,
1428 "vendor support window",
1429 Vec::new(),
1430 );
1431 push_strings(
1432 &mut out,
1433 "vendor.product_surface",
1434 "product_surface",
1435 CompiledObligationKindV1::Evidence,
1436 FoldClassV1::Union,
1437 vec![adapter.product_surface.clone()],
1438 &src,
1439 "vendor product surface",
1440 );
1441 out
1442}
1443
1444pub fn from_vendor_evidence_translation(
1446 translation: &VendorEvidenceTranslationV1,
1447) -> Vec<ObligationContributionV1> {
1448 let src = translation.vendor_evidence_translation_id.clone();
1449 let mut out = Vec::new();
1450 push_strings(
1451 &mut out,
1452 "vendor.source_shapes",
1453 "source_shapes",
1454 CompiledObligationKindV1::Evidence,
1455 FoldClassV1::Union,
1456 translation.source_shapes.clone(),
1457 &src,
1458 "vendor source shapes",
1459 );
1460 push_strings(
1461 &mut out,
1462 "vendor.canonical_targets",
1463 "canonical_targets",
1464 CompiledObligationKindV1::Evidence,
1465 FoldClassV1::Union,
1466 translation.canonical_targets.clone(),
1467 &src,
1468 "vendor canonical targets",
1469 );
1470 push_strings(
1471 &mut out,
1472 "vendor.lossy_fields",
1473 "lossy_fields",
1474 CompiledObligationKindV1::Warning,
1475 FoldClassV1::Union,
1476 translation.lossy_fields.clone(),
1477 &src,
1478 "vendor lossy fields",
1479 );
1480 push_strings(
1481 &mut out,
1482 "disclosure.vendor_required_caveats",
1483 "required_caveats",
1484 CompiledObligationKindV1::Disclosure,
1485 FoldClassV1::Union,
1486 translation.required_caveats.clone(),
1487 &src,
1488 "vendor required caveats",
1489 );
1490 out
1491}
1492
1493pub fn from_vendor_trust_root_binding(
1495 binding: &VendorTrustRootBindingV1,
1496) -> Vec<ObligationContributionV1> {
1497 let src = binding.vendor_trust_root_binding_id.clone();
1498 let mut out = Vec::new();
1499 push_strings(
1500 &mut out,
1501 "evidence.trust_root_refs",
1502 "trust_root_refs",
1503 CompiledObligationKindV1::Evidence,
1504 FoldClassV1::Union,
1505 binding.trust_root_refs.clone(),
1506 &src,
1507 "vendor trust root refs",
1508 );
1509 push_strings(
1510 &mut out,
1511 "evidence.signer_classes",
1512 "signer_classes",
1513 CompiledObligationKindV1::Evidence,
1514 FoldClassV1::Union,
1515 binding.signer_classes.clone(),
1516 &src,
1517 "vendor signer classes",
1518 );
1519 push_conflict(
1520 &mut out,
1521 "vendor.rotation_channel",
1522 "rotation_channel",
1523 CompiledObligationKindV1::Warning,
1524 vec![binding.rotation_channel.to_string()],
1525 &src,
1526 "vendor rotation channel",
1527 Vec::new(),
1528 );
1529 push_conflict(
1530 &mut out,
1531 "vendor.revocation_channel",
1532 "revocation_channel",
1533 CompiledObligationKindV1::Warning,
1534 vec![binding.revocation_channel.to_string()],
1535 &src,
1536 "vendor revocation channel",
1537 Vec::new(),
1538 );
1539 out
1540}
1541
1542pub fn from_vendor_revocation_handling(
1544 handling: &VendorRevocationHandlingV1,
1545) -> Vec<ObligationContributionV1> {
1546 let src = handling.vendor_revocation_handling_id.clone();
1547 let mut out = Vec::new();
1548 push_strings(
1549 &mut out,
1550 "warning.vendor_revocation_inputs",
1551 "revocation_inputs",
1552 CompiledObligationKindV1::Warning,
1553 FoldClassV1::Union,
1554 handling.revocation_inputs.clone(),
1555 &src,
1556 "vendor revocation inputs",
1557 );
1558 push_strings(
1559 &mut out,
1560 "rollback.vendor_invalidation_actions",
1561 "local_invalidation_actions",
1562 CompiledObligationKindV1::Rollback,
1563 FoldClassV1::Union,
1564 handling.local_invalidation_actions.clone(),
1565 &src,
1566 "vendor local invalidation actions",
1567 );
1568 push_conflict(
1569 &mut out,
1570 "vendor.replay_impact",
1571 "replay_impact",
1572 CompiledObligationKindV1::Warning,
1573 vec![handling.replay_impact.to_string()],
1574 &src,
1575 "vendor replay impact",
1576 Vec::new(),
1577 );
1578 push_block(
1579 &mut out,
1580 "vendor.admission_impact",
1581 handling.admission_impact.to_string(),
1582 CompiledObligationKindV1::Block,
1583 vec![handling.admission_impact.to_string()],
1584 &src,
1585 "vendor admission impact",
1586 vec!["vendor_override".into()],
1587 );
1588 out
1589}
1590
1591pub fn from_incident_taxonomy(taxonomy: &IncidentTaxonomyV1) -> Vec<ObligationContributionV1> {
1593 let src = taxonomy.incident_taxonomy_id.clone();
1594 let mut out = Vec::new();
1595 push_strings(
1596 &mut out,
1597 "continuity.incident_classes",
1598 "incident_classes",
1599 CompiledObligationKindV1::Continuity,
1600 FoldClassV1::Union,
1601 taxonomy
1602 .incident_classes
1603 .iter()
1604 .map(|rule| format!("{}:{}", rule.incident_class, rule.default_severity))
1605 .collect(),
1606 &src,
1607 "incident classes",
1608 );
1609 push_strings(
1610 &mut out,
1611 "continuity.default_routes",
1612 "default_routes",
1613 CompiledObligationKindV1::Continuity,
1614 FoldClassV1::Union,
1615 taxonomy
1616 .default_routes
1617 .iter()
1618 .map(|route| format!("{}:{}", route.incident_class, route.route))
1619 .collect(),
1620 &src,
1621 "default incident routes",
1622 );
1623 push_strings(
1624 &mut out,
1625 "evidence.required_artifact_families",
1626 "required_artifact_families",
1627 CompiledObligationKindV1::Evidence,
1628 FoldClassV1::Union,
1629 taxonomy.required_artifact_families.clone(),
1630 &src,
1631 "required incident artifact families",
1632 );
1633 out
1634}
1635
1636pub fn from_severity_matrix(matrix: &SeverityMatrixV1) -> Vec<ObligationContributionV1> {
1638 let src = matrix.severity_matrix_id.clone();
1639 let mut out = Vec::new();
1640 push_strings(
1641 &mut out,
1642 "continuity.severity_rules",
1643 "severity_rules",
1644 CompiledObligationKindV1::Continuity,
1645 FoldClassV1::Union,
1646 matrix
1647 .severity_rules
1648 .iter()
1649 .map(|rule| format!("{}:{}", rule.condition, rule.severity))
1650 .collect(),
1651 &src,
1652 "severity rules",
1653 );
1654 push_conflict(
1655 &mut out,
1656 "continuity.customer_impact_rubric",
1657 "customer_impact_rubric",
1658 CompiledObligationKindV1::Continuity,
1659 vec![matrix.customer_impact_rubric.clone()],
1660 &src,
1661 "customer impact rubric",
1662 Vec::new(),
1663 );
1664 push_conflict(
1665 &mut out,
1666 "continuity.internal_impact_rubric",
1667 "internal_impact_rubric",
1668 CompiledObligationKindV1::Continuity,
1669 vec![matrix.internal_impact_rubric.clone()],
1670 &src,
1671 "internal impact rubric",
1672 Vec::new(),
1673 );
1674 push_conflict(
1675 &mut out,
1676 "continuity.override_rule",
1677 "override_rule",
1678 CompiledObligationKindV1::Continuity,
1679 vec![matrix.override_rule.clone()],
1680 &src,
1681 "severity override rule",
1682 vec!["incident_override".into()],
1683 );
1684 out
1685}
1686
1687pub fn from_pager_route_profile(profile: &PagerRouteProfileV1) -> Vec<ObligationContributionV1> {
1689 let src = profile.pager_route_profile_id.clone();
1690 let mut out = Vec::new();
1691 push_strings(
1692 &mut out,
1693 "continuity.rotation_refs",
1694 "rotation_refs",
1695 CompiledObligationKindV1::Continuity,
1696 FoldClassV1::Union,
1697 profile.rotation_refs.clone(),
1698 &src,
1699 "pager rotation refs",
1700 );
1701 push_strings(
1702 &mut out,
1703 "continuity.handoff_rules",
1704 "handoff_rules",
1705 CompiledObligationKindV1::Continuity,
1706 FoldClassV1::Union,
1707 profile.handoff_rules.clone(),
1708 &src,
1709 "pager handoff rules",
1710 );
1711 push_numeric(
1712 &mut out,
1713 "continuity.ack_timeout_minutes",
1714 "ack_timeout_minutes",
1715 CompiledObligationKindV1::Continuity,
1716 FoldClassV1::MinOfMaxima,
1717 profile.ack_timeout_minutes,
1718 &src,
1719 "pager acknowledgement timeout",
1720 vec!["incident_override".into()],
1721 );
1722 push_numeric(
1723 &mut out,
1724 "continuity.max_escalation_levels",
1725 "max_levels",
1726 CompiledObligationKindV1::Continuity,
1727 FoldClassV1::MinOfMaxima,
1728 profile.max_levels,
1729 &src,
1730 "pager escalation level cap",
1731 vec!["incident_override".into()],
1732 );
1733 out
1734}
1735
1736pub fn from_escalation_clock_policy(
1738 policy: &EscalationClockPolicyV1,
1739) -> Vec<ObligationContributionV1> {
1740 let src = policy.escalation_clock_policy_id.clone();
1741 let mut out = Vec::new();
1742 push_strings(
1743 &mut out,
1744 "continuity.response_clocks",
1745 "response_clock_minutes",
1746 CompiledObligationKindV1::Continuity,
1747 FoldClassV1::Union,
1748 policy
1749 .response_clock_minutes
1750 .iter()
1751 .map(|clock| format!("{}:{}", clock.severity, clock.minutes))
1752 .collect(),
1753 &src,
1754 "response clocks by severity",
1755 );
1756 push_strings(
1757 &mut out,
1758 "review.post_hoc",
1759 "postmortem_clock_hours",
1760 CompiledObligationKindV1::PostHocReview,
1761 FoldClassV1::Union,
1762 policy
1763 .postmortem_clock_hours
1764 .iter()
1765 .map(|clock| format!("{}:{}", clock.severity, clock.hours))
1766 .collect(),
1767 &src,
1768 "postmortem clocks by severity",
1769 );
1770 push_strings(
1771 &mut out,
1772 "continuity.pause_rules",
1773 "pause_rules",
1774 CompiledObligationKindV1::Continuity,
1775 FoldClassV1::Union,
1776 policy.pause_rules.clone(),
1777 &src,
1778 "continuity pause rules",
1779 );
1780 push_strings(
1781 &mut out,
1782 "approval.required",
1783 "continuity_exception_path",
1784 CompiledObligationKindV1::Approval,
1785 FoldClassV1::Union,
1786 vec![policy.exception_path.clone()],
1787 &src,
1788 "continuity exception path",
1789 );
1790 out
1791}