Skip to main content

profile_runtime/
adapters.rs

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
151/// Projects an effect policy profile into obligation contributions for composition.
152pub 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
212/// Projects a delegation policy profile into obligation contributions for composition.
213pub 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
268/// Projects a release policy profile into obligation contributions for composition.
269pub 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
321/// Projects a continuity policy profile into obligation contributions for composition.
322pub 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
373/// Projects a privacy and retention profile into obligation contributions for composition.
374pub 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
434/// Projects a redaction rule set into obligation contributions for composition.
435pub 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
485/// Projects an access-purpose matrix into obligation contributions for composition.
486pub 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
538/// Projects an audit extraction policy into obligation contributions for composition.
539pub 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
610/// Projects a residency policy profile into obligation contributions for composition.
611pub 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
671/// Projects a tenant-boundary profile into obligation contributions for composition.
672pub 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
730/// Projects a cross-boundary transfer class into obligation contributions for composition.
731pub 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
784/// Projects a role catalog into obligation contributions for composition.
785pub 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
836/// Projects a delegation matrix into obligation contributions for composition.
837pub 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
890/// Projects an approval matrix into obligation contributions for composition.
891pub 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
942/// Projects a conflict-class catalog into obligation contributions for composition.
943pub 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
1003/// Projects a regulatory-regime profile into obligation contributions for composition.
1004pub 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
1053/// Projects a requirement-to-control map into obligation contributions for composition.
1054pub 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
1100/// Projects an evidence-collection plan into obligation contributions for composition.
1101pub 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
1152/// Projects a recertification schedule into obligation contributions for composition.
1153pub 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
1203/// Projects a hazard library into obligation contributions for composition.
1204pub 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
1240/// Projects a hazard scenario into obligation contributions for composition.
1241pub 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
1287/// Projects a monitor catalog into obligation contributions for composition.
1288pub 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
1338/// Projects a mitigation playbook into obligation contributions for composition.
1339pub 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
1395/// Projects a vendor certification adapter into obligation contributions for composition.
1396pub 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
1444/// Projects a vendor evidence translation policy into obligation contributions for composition.
1445pub 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
1493/// Projects a vendor trust-root binding into obligation contributions for composition.
1494pub 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
1542/// Projects a vendor revocation-handling policy into obligation contributions for composition.
1543pub 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
1591/// Projects an incident taxonomy into obligation contributions for composition.
1592pub 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
1636/// Projects a severity matrix into obligation contributions for composition.
1637pub 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
1687/// Projects a pager-route profile into obligation contributions for composition.
1688pub 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
1736/// Projects an escalation-clock policy into obligation contributions for composition.
1737pub 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}