Compare commits

..
Author SHA1 Message Date
Sharang ParnerkarandClaude Opus 4.8 731f84d2e5 fix(orchestrator): refresh control_refs on PLC re-scans
CI / Check (push) Skipped
CI / Check (pull_request) Successful in 5m40s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
run_plc_scan's persist block was insert-only, so a PLC re-scan never
updated control_refs on findings first seen before the semantic mapping
pass ran (or before it was enabled) — mappings could only be picked up by
deleting and re-adding the target. Port the refresh branch run_pipeline
already has (#228): when a finding already exists and now carries
control_refs, $set them onto the existing row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 18:40:12 +02:00
sharang 51bab61f77 fix(orchestrator): run semantic control mapping on PLC findings (#229)
CI / Deploy MCP (push) Skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Dashboard (push) Skipped
CI / Deploy Docs (push) Skipped
CI / Check (push) Skipped
CI / Deploy Agent (push) Successful in 4m3s
2026-07-22 13:16:53 +00:00
sharang 5bdc35ee92 fix(orchestrator): refresh control_refs on existing findings during re-scan (#228)
CI / Check (push) Skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 4m13s
CI / Deploy Dashboard (push) Skipped
CI / Deploy Docs (push) Skipped
CI / Deploy MCP (push) Skipped
2026-07-22 12:24:36 +00:00
+35 -1
View File
@@ -586,7 +586,21 @@ impl PipelineOrchestrator {
let Some(path) = ingest_set.get(&a.id).and_then(|ia| ia.working_path.clone()) else {
continue;
};
all_findings.extend(crate::pipeline::plc::analyze_tree(&path, target_id));
let mut source_findings = crate::pipeline::plc::analyze_tree(&path, target_id);
// Control mapping for the PLC path (run_plc_scan is separate from
// run_pipeline, which does its own mapping). PLC findings carry
// file_path/line/cwe, so the semantic pass reads each region under this
// source's `path` and stamps master-control refs. The LUT + grounded
// surface passes are code-pattern / CRA-specific and don't apply to
// IEC 61131-3 control logic, so only the semantic pass runs here.
crate::controls::semantic_stamp_findings(
&self.config,
self.llm.clone(),
&path,
&mut source_findings,
)
.await;
all_findings.extend(source_findings);
// Control-application SBOM: CODESYS libraries + runtime from a
// `.projectarchive` (uploaded, or committed in the working tree).
let archive = a
@@ -611,6 +625,7 @@ impl PipelineOrchestrator {
);
let mut new_count = 0u32;
let mut refreshed_count = 0u32;
for mut finding in all_findings {
finding.scan_run_id = Some(scan_run_id.to_string());
if self
@@ -622,8 +637,27 @@ impl PipelineOrchestrator {
{
self.db.findings().insert_one(&finding).await?;
new_count += 1;
} else if !finding.control_refs.is_empty() {
// Re-scan refresh: mirror run_pipeline — persist newly-computed
// control_refs onto a PLC finding first seen before the semantic
// pass ran. The insert path alone never would, so without this a
// PLC re-scan can only pick up mappings via a delete + re-add.
self.db
.findings()
.update_one(
doc! { "fingerprint": &finding.fingerprint },
doc! { "$set": { "control_refs": finding.control_refs.clone() } },
)
.await?;
refreshed_count += 1;
}
}
if refreshed_count > 0 {
tracing::info!(
target_id,
"Refreshed control_refs on {refreshed_count} existing PLC findings"
);
}
if !all_sbom.is_empty() {
if let Err(e) = self