Skip to main content

axon/esk/audit_engine/
control_statements.rs

1//! AXON Audit Evidence Engine — ControlImplementationStatement
2//!
3//! Direct port of `axon/runtime/esk/audit_engine/control_statements.py`.
4//!
5//! For every control in a framework, produce the auditor-ready
6//! "Implementation Statement" that an organization typically writes
7//! by hand during the audit-prep cycle. AXON can pre-populate most of
8//! these from the framework catalog + IR program inspection.
9
10#![allow(dead_code)]
11
12use std::collections::HashMap;
13
14use serde_json::{Map, Value};
15
16use crate::ir_nodes::IRProgram;
17
18use super::frameworks::{Control, EvidenceKind, FrameworkId, controls_for};
19use super::gap_analyzer::analyze_gaps;
20
21// ═══════════════════════════════════════════════════════════════════
22//  Owner + frequency defaults per evidence kind
23// ═══════════════════════════════════════════════════════════════════
24
25fn owner_for_kind(kind: EvidenceKind) -> &'static str {
26    match kind {
27        EvidenceKind::CompileTime         => "Engineering (Language Team)",
28        EvidenceKind::RuntimeInvariant    => "Engineering (Runtime Team)",
29        EvidenceKind::AutomatedArtifact   => "Engineering (CI/CD)",
30        EvidenceKind::TestSuite           => "Engineering (QA)",
31        EvidenceKind::ManualPolicy        => "Security / GRC",
32        EvidenceKind::ExternalOperational => "Operations / SRE",
33    }
34}
35
36fn frequency_for_kind(kind: EvidenceKind) -> &'static str {
37    match kind {
38        EvidenceKind::CompileTime         => "continuous",    // every commit via axon check
39        EvidenceKind::RuntimeInvariant    => "continuous",    // every request
40        EvidenceKind::AutomatedArtifact   => "per-release",
41        EvidenceKind::TestSuite           => "per-commit",
42        EvidenceKind::ManualPolicy        => "annual_review",
43        EvidenceKind::ExternalOperational => "per-release",
44    }
45}
46
47// ═══════════════════════════════════════════════════════════════════
48//  Implementation statement
49// ═══════════════════════════════════════════════════════════════════
50
51#[derive(Debug, Clone)]
52pub struct ControlImplementationStatement {
53    pub control_id: String,
54    pub control_title: String,
55    pub status: String,
56    pub implementation_detail: String,
57    pub evidence: Vec<String>,
58    pub owner_role: String,
59    pub test_frequency: String,
60}
61
62impl ControlImplementationStatement {
63    pub fn to_value(&self) -> Value {
64        let mut m = Map::new();
65        m.insert("control_id".into(), self.control_id.clone().into());
66        m.insert("control_title".into(), self.control_title.clone().into());
67        m.insert("status".into(), self.status.clone().into());
68        m.insert(
69            "implementation_detail".into(),
70            self.implementation_detail.clone().into(),
71        );
72        m.insert(
73            "evidence".into(),
74            Value::Array(self.evidence.iter().cloned().map(Value::String).collect()),
75        );
76        m.insert("owner_role".into(), self.owner_role.clone().into());
77        m.insert("test_frequency".into(), self.test_frequency.clone().into());
78        Value::Object(m)
79    }
80}
81
82// ═══════════════════════════════════════════════════════════════════
83//  Status mapping
84// ═══════════════════════════════════════════════════════════════════
85
86fn status_from_analysis(assessment_status: &str) -> &'static str {
87    match assessment_status {
88        "ready"            => "implemented",
89        "pending_code"     => "partially_implemented",
90        "pending_external" => "planned",
91        _                  => "not_applicable",
92    }
93}
94
95fn implementation_detail(control: &Control) -> String {
96    format!(
97        "{}. Evidence kind: {}. Verification locus: {}.",
98        control.axon_primitive,
99        control.evidence_kind.as_str(),
100        control.evidence_locator,
101    )
102}
103
104// ═══════════════════════════════════════════════════════════════════
105//  Public API
106// ═══════════════════════════════════════════════════════════════════
107
108pub fn generate_control_statements(
109    program: &IRProgram,
110    framework: FrameworkId,
111) -> Vec<ControlImplementationStatement> {
112    let analysis = analyze_gaps(program, framework);
113    let by_id: HashMap<String, String> = analysis
114        .assessments
115        .iter()
116        .map(|a| (a.control_id.clone(), a.status.clone()))
117        .collect();
118
119    let mut statements: Vec<ControlImplementationStatement> = Vec::new();
120    for control in controls_for(framework) {
121        let status_raw = by_id
122            .get(control.control_id)
123            .cloned()
124            .unwrap_or_else(|| "pending_code".to_string());
125        statements.push(ControlImplementationStatement {
126            control_id: control.control_id.into(),
127            control_title: control.title.into(),
128            status: status_from_analysis(&status_raw).into(),
129            implementation_detail: implementation_detail(&control),
130            evidence: vec![control.evidence_locator.into()],
131            owner_role: owner_for_kind(control.evidence_kind).into(),
132            test_frequency: frequency_for_kind(control.evidence_kind).into(),
133        });
134    }
135    statements
136}
137
138pub fn statements_to_value(
139    statements: &[ControlImplementationStatement],
140    framework: FrameworkId,
141) -> Value {
142    let mut m = Map::new();
143    m.insert(
144        "schema".into(),
145        "axon.esk.control_implementation_statements.v1".into(),
146    );
147    m.insert("framework".into(), framework.as_str().into());
148    m.insert("total_controls".into(), (statements.len() as i64).into());
149    m.insert(
150        "statements".into(),
151        Value::Array(statements.iter().map(|s| s.to_value()).collect()),
152    );
153    Value::Object(m)
154}
155
156#[cfg(test)]
157mod tests {
158    use super::super::frameworks::{all_frameworks, control_count};
159    use super::*;
160    use crate::ir_generator::IRGenerator;
161    use crate::lexer::Lexer;
162    use crate::parser::Parser;
163
164    fn compile(source: &str) -> IRProgram {
165        let tokens = Lexer::new(source, "t").tokenize().unwrap();
166        let program = Parser::new(tokens).parse().unwrap();
167        IRGenerator::new().generate(&program)
168    }
169
170    fn full_program() -> IRProgram {
171        compile(r#"
172            type R compliance [HIPAA] { x: String }
173            flow F(r: R) -> R { step S { ask: "x" output: R } }
174            shield G {
175                scan: [prompt_injection]
176                on_breach: halt
177                severity: high
178                compliance: [HIPAA]
179            }
180            axonendpoint E {
181                method: POST path: "/p" body: R execute: F output: R
182                shield: G
183                compliance: [HIPAA]
184            }
185            resource Db { kind: postgres lifetime: linear }
186            fabric Vpc { provider: aws }
187            manifest M { resources: [Db] fabric: Vpc compliance: [HIPAA] }
188            observe O from M { sources: [prom] quorum: 1 }
189            reconcile Rec { observe: O }
190            lease L { resource: Db duration: 30m }
191            immune I { watch: [O] scope: tenant }
192            reflex Rf { trigger: I on_level: doubt action: quarantine scope: tenant }
193            heal H { source: I scope: tenant }
194        "#)
195    }
196
197    #[test]
198    fn one_statement_per_control() {
199        let ir = full_program();
200        for f in all_frameworks() {
201            let statements = generate_control_statements(&ir, f);
202            assert_eq!(statements.len(), control_count(f), "framework {:?}", f);
203        }
204    }
205
206    #[test]
207    fn statuses_only_from_canonical_set() {
208        let ir = full_program();
209        let canonical = [
210            "implemented",
211            "partially_implemented",
212            "planned",
213            "not_applicable",
214        ];
215        for f in all_frameworks() {
216            for s in generate_control_statements(&ir, f) {
217                assert!(
218                    canonical.contains(&s.status.as_str()),
219                    "{:?} {} had non-canonical status {}",
220                    f,
221                    s.control_id,
222                    s.status
223                );
224            }
225        }
226    }
227
228    #[test]
229    fn statements_to_value_schema() {
230        let ir = full_program();
231        let stmts = generate_control_statements(&ir, FrameworkId::Soc2TypeII);
232        let v = statements_to_value(&stmts, FrameworkId::Soc2TypeII);
233        assert_eq!(
234            v["schema"],
235            "axon.esk.control_implementation_statements.v1"
236        );
237        assert_eq!(v["framework"], FrameworkId::Soc2TypeII.as_str());
238        assert_eq!(v["total_controls"], stmts.len() as i64);
239    }
240
241    #[test]
242    fn status_mapping_matches_python_reference() {
243        assert_eq!(status_from_analysis("ready"), "implemented");
244        assert_eq!(status_from_analysis("pending_code"), "partially_implemented");
245        assert_eq!(status_from_analysis("pending_external"), "planned");
246        assert_eq!(status_from_analysis("something_else"), "not_applicable");
247    }
248
249    #[test]
250    fn implementation_detail_mentions_primitive_and_locus() {
251        let ir = full_program();
252        let stmts = generate_control_statements(&ir, FrameworkId::Soc2TypeII);
253        assert!(!stmts.is_empty());
254        for s in &stmts {
255            assert!(
256                s.implementation_detail.contains("Evidence kind:"),
257                "detail missing marker: {}",
258                s.implementation_detail
259            );
260            assert!(
261                s.implementation_detail.contains("Verification locus:"),
262                "detail missing locus: {}",
263                s.implementation_detail
264            );
265        }
266    }
267
268    #[test]
269    fn statements_deterministic_on_equal_input() {
270        let ir = full_program();
271        let a = statements_to_value(
272            &generate_control_statements(&ir, FrameworkId::Iso27001),
273            FrameworkId::Iso27001,
274        );
275        let b = statements_to_value(
276            &generate_control_statements(&ir, FrameworkId::Iso27001),
277            FrameworkId::Iso27001,
278        );
279        assert_eq!(
280            serde_json::to_string(&a).unwrap(),
281            serde_json::to_string(&b).unwrap()
282        );
283    }
284}