All checks were successful
CI / Format (push) Successful in 4s
CI / Clippy (push) Successful in 4m19s
CI / Security Audit (push) Successful in 1m44s
CI / Detect Changes (push) Successful in 5s
CI / Tests (push) Successful in 5m15s
CI / Deploy Agent (push) Successful in 2s
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Successful in 2s
107 lines
4.1 KiB
Rust
107 lines
4.1 KiB
Rust
use compliance_core::models::Finding;
|
|
|
|
use super::orchestrator::{GraphContext, PipelineOrchestrator};
|
|
use crate::error::AgentError;
|
|
|
|
impl PipelineOrchestrator {
|
|
/// Build the code knowledge graph for a repo and compute impact analyses
|
|
pub(super) async fn build_code_graph(
|
|
&self,
|
|
repo_path: &std::path::Path,
|
|
repo_id: &str,
|
|
findings: &[Finding],
|
|
) -> Result<GraphContext, AgentError> {
|
|
let graph_build_id = uuid::Uuid::new_v4().to_string();
|
|
let engine = compliance_graph::GraphEngine::new(50_000);
|
|
|
|
let (mut code_graph, build_run) =
|
|
engine
|
|
.build_graph(repo_path, repo_id, &graph_build_id)
|
|
.map_err(|e| AgentError::Other(format!("Graph build error: {e}")))?;
|
|
|
|
// Apply community detection
|
|
compliance_graph::graph::community::apply_communities(&mut code_graph);
|
|
|
|
// Store graph in MongoDB
|
|
let store = compliance_graph::graph::persistence::GraphStore::new(self.db.inner());
|
|
store
|
|
.delete_repo_graph(repo_id)
|
|
.await
|
|
.map_err(|e| AgentError::Other(format!("Graph cleanup error: {e}")))?;
|
|
store
|
|
.store_graph(&build_run, &code_graph.nodes, &code_graph.edges)
|
|
.await
|
|
.map_err(|e| AgentError::Other(format!("Graph store error: {e}")))?;
|
|
|
|
// Compute impact analysis for each finding
|
|
let analyzer = compliance_graph::GraphEngine::impact_analyzer(&code_graph);
|
|
let mut impacts = Vec::new();
|
|
|
|
for finding in findings {
|
|
if let Some(file_path) = &finding.file_path {
|
|
let impact = analyzer.analyze(
|
|
repo_id,
|
|
&finding.fingerprint,
|
|
&graph_build_id,
|
|
file_path,
|
|
finding.line_number,
|
|
);
|
|
store
|
|
.store_impact(&impact)
|
|
.await
|
|
.map_err(|e| AgentError::Other(format!("Impact store error: {e}")))?;
|
|
impacts.push(impact);
|
|
}
|
|
}
|
|
|
|
Ok(GraphContext {
|
|
node_count: build_run.node_count,
|
|
edge_count: build_run.edge_count,
|
|
community_count: build_run.community_count,
|
|
impacts,
|
|
})
|
|
}
|
|
|
|
/// Trigger DAST scan if a target is configured for this repo
|
|
pub(super) async fn maybe_trigger_dast(&self, repo_id: &str, scan_run_id: &str) {
|
|
use futures_util::TryStreamExt;
|
|
|
|
let filter = mongodb::bson::doc! { "repo_id": repo_id };
|
|
let targets: Vec<compliance_core::models::DastTarget> =
|
|
match self.db.dast_targets().find(filter).await {
|
|
Ok(cursor) => cursor.try_collect().await.unwrap_or_default(),
|
|
Err(_) => return,
|
|
};
|
|
|
|
if targets.is_empty() {
|
|
tracing::info!("[{repo_id}] No DAST targets configured, skipping");
|
|
return;
|
|
}
|
|
|
|
for target in targets {
|
|
let db = self.db.clone();
|
|
let scan_run_id = scan_run_id.to_string();
|
|
tokio::spawn(async move {
|
|
let orchestrator = compliance_dast::DastOrchestrator::new(100);
|
|
match orchestrator.run_scan(&target, Vec::new()).await {
|
|
Ok((mut scan_run, findings)) => {
|
|
scan_run.sast_scan_run_id = Some(scan_run_id);
|
|
if let Err(e) = db.dast_scan_runs().insert_one(&scan_run).await {
|
|
tracing::error!("Failed to store DAST scan run: {e}");
|
|
}
|
|
for finding in &findings {
|
|
if let Err(e) = db.dast_findings().insert_one(finding).await {
|
|
tracing::error!("Failed to store DAST finding: {e}");
|
|
}
|
|
}
|
|
tracing::info!("DAST scan complete: {} findings", findings.len());
|
|
}
|
|
Err(e) => {
|
|
tracing::error!("DAST scan failed: {e}");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|