The control-app SBOM CVE path (#166) stored alerts but created no notifications,
so CODESYS runtime/library CVEs never rang the dashboard bell.
Factor the alert-upsert + notification-creation out of run_pipeline into a shared
persist_cve_alerts(repo_id, repo_name, alerts) helper and call it from both the
SAST pipeline and persist_control_app_sbom. Now every SBOM source — source deps,
firmware, and CODESYS libraries/runtime — raises the same dedup'd CVE notifications.
Behavior for the existing SAST path is unchanged (same logic, extracted); the PLC
path additionally gains notifications. Completes the CVE loop for #166. Tracker #167.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>