feat(controls): B2 — grounded surface checks for absence-based CRA controls (#219)
CI / Check (push) Skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Docs (push) Skipped
CI / Deploy Agent (push) Failing after 5s
CI / Deploy Dashboard (push) Failing after 4s
CI / Deploy MCP (push) Failing after 5s

This commit was merged in pull request #219.
This commit is contained in:
2026-07-21 12:48:12 +00:00
parent 38fedc661b
commit 4ef257bfe2
7 changed files with 336 additions and 10 deletions
+47 -2
View File
@@ -16,9 +16,10 @@ use compliance_core::models::onboarding::ComplianceFramework;
use compliance_core::AgentConfig;
use control_map::ControlMap;
use super::surface;
use super::{
ControlIndex, ControlTriage, LlmControlJudge, OscalControlsProvider, SemanticControlChecker,
TriageOutcome,
ControlIndex, ControlTriage, GroundedControlChecker, LlmControlJudge, OscalControlsProvider,
SemanticControlChecker, TriageOutcome,
};
use crate::llm::LlmClient;
@@ -105,6 +106,50 @@ async fn build_specs(provider: &OscalControlsProvider) -> HashMap<String, Contro
specs
}
/// Absence-based control pass (the grounded half of the hybrid coverage): for each
/// control with a [`surface`] definition, deterministically retrieve the code
/// surfaces it governs (login routes, logging setup, update/download code) and have
/// the grounded judge decide whether the control holds there. Returns net-new
/// findings, each already tagged with its control and grounded to a real snippet.
///
/// Gated: the orchestrator runs this only when `breakpilot.grounded_control_checks`
/// is set. Absence detection is the least deterministic path (the judge decides
/// presence/absence, not a syntactic pattern), so it stays off until tuned live.
pub async fn grounded_surface_findings(
config: &AgentConfig,
llm: Arc<LlmClient>,
repo_path: &Path,
repo_id: &str,
) -> Vec<Finding> {
let Some(base_url) = config.breakpilot.base_url.clone() else {
return Vec::new();
};
let provider = OscalControlsProvider::new(
reqwest::Client::new(),
base_url,
config.breakpilot.token.clone(),
&config.breakpilot.snapshot_dir,
);
let specs = build_specs(&provider).await;
if specs.is_empty() {
return Vec::new();
}
let checker = GroundedControlChecker::new(LlmControlJudge::new(llm));
let mut out = Vec::new();
for surf in surface::SURFACES {
let Some(spec) = specs.get(surf.control_id) else {
continue; // catalog doesn't carry this control
};
let regions = surface::retrieve(repo_path, surf.terms);
if regions.is_empty() {
continue;
}
out.extend(checker.check(spec, &regions, repo_id).await);
}
out
}
/// Read a window of lines around `line` (1-based) from `repo_path/file`.
fn fetch_region(repo_path: &Path, file: &str, line: u32) -> Option<CandidateRegion> {
let content = std::fs::read_to_string(repo_path.join(file)).ok()?;