Skip to main content

arbiter/
formation.rs

1//! Formation-facing discovery metadata for Arbiter capabilities.
2//!
3//! These descriptors make Arbiter's Cedar gates easy for products and
4//! formation builders to find without turning Arbiter into a generic solver
5//! pack. Runtime gates remain Suggestors; offline analysis remains explicit
6//! evidence.
7
8use serde::Serialize;
9
10/// Stable identifier for the Arbiter Cedar capability family.
11pub const ARBITER_FORMATION_PACK_ID: &str = "arbiter.cedar";
12
13/// Stable identifier for the generic Cedar policy suggestor.
14pub const POLICY_GATE_CAPABILITY_ID: &str = "arbiter.cedar.policy_gate";
15
16/// Stable identifier for the strict Cedar-backed HITL suggestor.
17pub const HITL_GATE_CAPABILITY_ID: &str = "arbiter.cedar.hitl_gate";
18
19/// Stable identifier for offline Cedar SymCC analysis evidence.
20pub const ANALYSIS_EVIDENCE_CAPABILITY_ID: &str = "arbiter.cedar.analysis_evidence";
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
23#[serde(rename_all = "snake_case")]
24pub enum ArbiterFormationCapabilityKind {
25    RuntimeGate,
26    AnalysisEvidence,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
30pub struct ArbiterFormationCapability {
31    pub id: &'static str,
32    pub pack_id: &'static str,
33    pub kind: ArbiterFormationCapabilityKind,
34    pub suggestor: Option<&'static str>,
35    pub feature: Option<&'static str>,
36    pub input: &'static str,
37    pub output: &'static str,
38    pub evidence_tier: &'static str,
39    pub description: &'static str,
40}
41
42pub const ARBITER_FORMATION_CAPABILITIES: &[ArbiterFormationCapability] = &[
43    ArbiterFormationCapability {
44        id: POLICY_GATE_CAPABILITY_ID,
45        pack_id: ARBITER_FORMATION_PACK_ID,
46        kind: ArbiterFormationCapabilityKind::RuntimeGate,
47        suggestor: Some("policy-gate"),
48        feature: None,
49        input: "arbiter::DecideRequest",
50        output: "arbiter::PolicyDecision",
51        evidence_tier: "decided",
52        description: "Runtime Cedar policy gate over DecideRequest.",
53    },
54    ArbiterFormationCapability {
55        id: HITL_GATE_CAPABILITY_ID,
56        pack_id: ARBITER_FORMATION_PACK_ID,
57        kind: ArbiterFormationCapabilityKind::RuntimeGate,
58        suggestor: Some("cedar-hitl-gate"),
59        feature: None,
60        input: "converge_core::FlowGateInput",
61        output: "converge_core::FlowGateDecision",
62        evidence_tier: "decided",
63        description: "Strict Cedar-backed HITL gate that escalates only when Cedar allows the approved request.",
64    },
65    ArbiterFormationCapability {
66        id: ANALYSIS_EVIDENCE_CAPABILITY_ID,
67        pack_id: ARBITER_FORMATION_PACK_ID,
68        kind: ArbiterFormationCapabilityKind::AnalysisEvidence,
69        suggestor: Some("cedar-analysis"),
70        feature: Some("analysis"),
71        input: "arbiter::CedarAnalysisInput",
72        output: "arbiter::CedarAnalysisReport",
73        evidence_tier: "searched",
74        description: "Cedar SymCC analysis suggestor for searched policy-invariant evidence; not a runtime authorization gate.",
75    },
76];
77
78#[must_use]
79pub fn formation_capabilities() -> &'static [ArbiterFormationCapability] {
80    ARBITER_FORMATION_CAPABILITIES
81}
82
83#[must_use]
84pub fn find_formation_capability(id: &str) -> Option<&'static ArbiterFormationCapability> {
85    ARBITER_FORMATION_CAPABILITIES
86        .iter()
87        .find(|capability| capability.id == id)
88}
89
90#[cfg(test)]
91mod tests {
92    use std::collections::HashSet;
93
94    use super::*;
95
96    #[test]
97    fn exposes_cedar_hitl_gate_for_formations() {
98        let capability = find_formation_capability(HITL_GATE_CAPABILITY_ID)
99            .expect("HITL capability should be registered");
100
101        assert_eq!(capability.pack_id, ARBITER_FORMATION_PACK_ID);
102        assert_eq!(capability.kind, ArbiterFormationCapabilityKind::RuntimeGate);
103        assert_eq!(capability.suggestor, Some("cedar-hitl-gate"));
104        assert_eq!(capability.evidence_tier, "decided");
105    }
106
107    #[test]
108    fn exposes_cedar_analysis_suggestor_for_formations() {
109        let capability = find_formation_capability(ANALYSIS_EVIDENCE_CAPABILITY_ID)
110            .expect("analysis capability should be registered");
111
112        assert_eq!(capability.pack_id, ARBITER_FORMATION_PACK_ID);
113        assert_eq!(
114            capability.kind,
115            ArbiterFormationCapabilityKind::AnalysisEvidence
116        );
117        assert_eq!(capability.suggestor, Some("cedar-analysis"));
118        assert_eq!(capability.evidence_tier, "searched");
119    }
120
121    #[test]
122    fn capability_ids_are_unique() {
123        let mut ids = HashSet::new();
124
125        for capability in formation_capabilities() {
126            assert!(
127                ids.insert(capability.id),
128                "duplicate capability id: {}",
129                capability.id
130            );
131        }
132    }
133}