OSV-by-purl can't match `pkg:codesys/*` (OSV has no codesys ecosystem), so the
CODESYS control-app SBOM produced zero CVE alerts even when the runtime was
affected — "No CVE alerts" read as safe when it just meant "not checked" (found
live during the demo).
Add CveScanner::scan_codesys: find the CODESYS **runtime** component (the version
advisories key off), pull `cpe:2.3:a:codesys:*` CVEs from NVD, and keep the ones
whose affected-version range (versionStart/End Including/Excluding, or an exact
CPE version) covers the runtime version. Wired into persist_control_app_sbom
alongside the OSV scan (best-effort, timeout-bounded).
Pure logic unit-tested (runtime detection, dotted-version compare, CPE range
matching, NVD-config parsing against a fixture): a 4.17.0.0 runtime matches a
`<=4.20` CVE but not an old `<4.9` one.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>