1#![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
21fn 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", EvidenceKind::RuntimeInvariant => "continuous", EvidenceKind::AutomatedArtifact => "per-release",
41 EvidenceKind::TestSuite => "per-commit",
42 EvidenceKind::ManualPolicy => "annual_review",
43 EvidenceKind::ExternalOperational => "per-release",
44 }
45}
46
47#[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
82fn 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
104pub 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}