//! `control-map` — the deterministic control → scan lookup table (LUT). //! //! The "transcribing" layer: it maps each compliance control to the static-scan //! step(s) that check it, or marks it as needing custom tooling, or as not //! code-checkable at all. The map is **authored and human-reviewed** — no LLM //! decides coverage. The LLM only enters later, downstream, to triage/ground the //! *tool's* findings (that lives in the agent, not here). //! //! This crate is intentionally tiny and standalone: types + an embedded JSON LUT //! + query helpers. use serde::{Deserialize, Serialize}; /// Coverage bucket for a control under static (SAST-family) scanning. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Coverage { /// An existing tool's scan surfaces findings for this control. Covered, /// Code-checkable, but no existing tool digs it out — we must write tooling. NeedsTooling, /// Process / document control — out of static-scan scope. NotCodeCheckable, } /// One tool binding: a scan step that (at least partially) checks a control. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScanBinding { /// Tool name, e.g. `"semgrep"`, `"gitleaks"`, `"syft"`, `"osv"`. pub tool: String, /// Scan family, e.g. `"sast"`, `"secret_detection"`, `"sbom"`, `"cve"`. pub scan_type: String, /// CWEs whose findings map to this control (used to attach findings back). #[serde(default)] pub cwe: Vec, /// Optional specific rule ids this control keys on. #[serde(default)] pub rules: Vec, } /// One control's entry in the LUT. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ControlEntry { /// Control id, e.g. `"cra-ai-8"`. pub control: String, /// Human-readable title (for the reviewable view). #[serde(default)] pub title: String, /// Coverage bucket. pub status: Coverage, /// Tool bindings (empty unless `status == Covered`). #[serde(default)] pub scans: Vec, /// Reviewer note — why it needs tooling / isn't code-checkable. #[serde(default)] pub note: Option, } /// The control → scan lookup table for one framework. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ControlMap { pub version: String, pub framework: String, pub controls: Vec, } const CRA_MAP_JSON: &str = include_str!("../data/cra_control_map.json"); /// Whether an authored rule id `bound` matches a scanner's emitted rule id /// `actual`. semgrep prefixes local-rule check_ids with a path /// (`tmp.compliance-cra-semgrep.cra-ai-1-flask-debug-enabled`), so match the final /// id segment rather than requiring exact equality. fn rule_id_matches(bound: &str, actual: &str) -> bool { actual == bound || actual.ends_with(&format!(".{bound}")) } impl ControlMap { /// Load the built-in CRA control map (the embedded, authored LUT). pub fn cra() -> Result { Ok(serde_json::from_str(CRA_MAP_JSON)?) } /// The coverage entry for a control id, if present. pub fn coverage(&self, control_id: &str) -> Option<&ControlEntry> { self.controls.iter().find(|c| c.control == control_id) } /// Controls whose bindings include the given `tool` + `cwe` — used to attach a /// raw tool finding back to the control(s) it's evidence for. pub fn controls_for(&self, tool: &str, cwe: &str) -> Vec<&ControlEntry> { self.controls_for_finding(tool, Some(cwe), None) } /// Controls a tool finding is evidence for, matched by CWE and/or the specific /// rule id that fired. Off-the-shelf findings bind by CWE; our custom detectors /// bind by rule id (precise — a broad CWE would over-attribute and then the /// grounded judge could drop a genuine finding as a control false positive). pub fn controls_for_finding( &self, tool: &str, cwe: Option<&str>, rule_id: Option<&str>, ) -> Vec<&ControlEntry> { self.controls .iter() .filter(|c| { c.scans.iter().any(|s| { s.tool == tool && (cwe.is_some_and(|w| s.cwe.iter().any(|x| x == w)) || rule_id .is_some_and(|r| s.rules.iter().any(|b| rule_id_matches(b, r)))) }) }) .collect() } /// Count of controls in each coverage bucket. pub fn summary(&self) -> CoverageSummary { let mut s = CoverageSummary::default(); for c in &self.controls { match c.status { Coverage::Covered => s.covered += 1, Coverage::NeedsTooling => s.needs_tooling += 1, Coverage::NotCodeCheckable => s.not_code_checkable += 1, } } s } } /// Coverage bucket counts. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] pub struct CoverageSummary { pub covered: usize, pub needs_tooling: usize, pub not_code_checkable: usize, } impl CoverageSummary { pub fn total(&self) -> usize { self.covered + self.needs_tooling + self.not_code_checkable } } /// Errors loading a control map. #[derive(Debug, thiserror::Error)] pub enum MapError { #[error("failed to parse control map: {0}")] Parse(#[from] serde_json::Error), } #[cfg(test)] mod tests { use super::*; #[test] fn cra_map_loads_all_40_controls() { let map = ControlMap::cra().expect("CRA map should parse"); assert_eq!(map.framework, "cra"); assert_eq!(map.controls.len(), 40); assert_eq!(map.summary().total(), 40); } #[test] fn hardcoded_password_control_is_tool_covered() { let map = ControlMap::cra().unwrap(); let c = map.coverage("cra-ai-8").expect("cra-ai-8 present"); assert_eq!(c.status, Coverage::Covered); assert!(c.scans.iter().any(|s| s.tool == "semgrep")); assert!(c.scans.iter().any(|s| s.tool == "gitleaks")); } #[test] fn finding_attaches_back_to_control_via_tool_and_cwe() { let map = ControlMap::cra().unwrap(); let hits = map.controls_for("semgrep", "CWE-798"); assert!(hits.iter().any(|c| c.control == "cra-ai-8")); } #[test] fn covered_and_not_checkable_are_populated() { let s = ControlMap::cra().unwrap().summary(); assert!(s.covered > 0); assert!(s.not_code_checkable > 0); // needs_tooling is now empty: every code-checkable control is either // tool-covered or covered by the grounded surface pass. assert_eq!(s.needs_tooling, 0); } #[test] fn rule_id_matching_handles_semgrep_path_prefix() { let bound = "cra-ai-1-flask-debug-enabled"; assert!(rule_id_matches(bound, bound)); // exact assert!(rule_id_matches( bound, "tmp.compliance-cra-semgrep.cra-ai-1-flask-debug-enabled" )); // semgrep path prefix assert!(!rule_id_matches( bound, "cra-ai-1-flask-debug-enabled-extra" )); // not a suffix segment assert!(!rule_id_matches( bound, "python.lang.security.exec-detected" )); // unrelated } #[test] fn custom_rule_finding_attaches_to_control_by_rule_id() { let map = ControlMap::cra().unwrap(); // cra-ai-1 is now tool-covered by custom rules. assert_eq!(map.coverage("cra-ai-1").unwrap().status, Coverage::Covered); // A prefixed check_id still maps back to cra-ai-1 by rule id. let hits = map.controls_for_finding("semgrep", None, Some("tmp.x.cra-ai-1-tls-verify-disabled")); assert!(hits.iter().any(|c| c.control == "cra-ai-1")); } #[test] fn coverage_after_grounded_promotion() { let s = ControlMap::cra().unwrap().summary(); // 9 off-the-shelf + 4 custom-semgrep + 8 grounded surface controls (promoted // after the grounded path was validated live). assert_eq!(s.covered, 21); // Nothing left as needs_tooling — every code-checkable control is covered. assert_eq!(s.needs_tooling, 0); // The 4 pure-architectural controls remain not code-checkable. assert_eq!(s.not_code_checkable, 19); assert_eq!(s.total(), 40); } #[test] fn architectural_controls_are_not_code_checkable() { let map = ControlMap::cra().unwrap(); for id in ["cra-ai-2", "cra-ai-3", "cra-ai-4", "cra-ai-5"] { let c = map.coverage(id).unwrap(); assert_eq!(c.status, Coverage::NotCodeCheckable, "{id}"); assert!(c.scans.is_empty(), "{id} should carry no scan bindings"); } } #[test] fn custom_rule_controls_do_not_bind_by_broad_cwe() { let map = ControlMap::cra().unwrap(); // cra-ai-1 rules emit CWE-489 in metadata, but the LUT binds by rule id // only (cwe: []) — so a stray CWE-489 finding must NOT attach to it. assert!(map.controls_for("semgrep", "CWE-489").is_empty()); // The CWE path for off-the-shelf findings is unchanged. assert!(map .controls_for("semgrep", "CWE-798") .iter() .any(|c| c.control == "cra-ai-8")); } }