Skip to content

feat(verdict): keep the verdict per product, and judge over HTTP too - #59

Closed
maksimtech wants to merge 2 commits into
mainfrom
feat/the-verdict-is-kept-per-product
Closed

maksimtech wants to merge 2 commits into
mainfrom
feat/the-verdict-is-kept-per-product

Conversation

@maksimtech

Copy link
Copy Markdown
Owner

Audit finding PR-01, step 01b-1 (docs/DESIGN.md §5.5, §5.6). It follows #58.

What was wrong after #58

  • The verdict lived only in memory. patchradar status, /api/cves and the web UI went back to CVEs by name.
  • POST /api/scan did not judge, so the CLI and the API gave two different answers for the same machine. That is the failure test_collector_wiring.py exists to prevent.

What changes

  • cve_software gains verdict, verdict_reason, verdict_fixed_version and judged_version, migrated in place.
    • Per product, because one CVE stored once can affect one watched product and not another.
    • The latest scan decides it, unlike the description, where the first writer wins.
  • get_cves shows the verdict only while judged_version is still the installed version. After add --version 8.9.6.4, a verdict about 8.5.0 is not shown until a scan judges the new version.
  • affected.mark: the one place both scans judge.
  • API:
    • POST /api/watchlist/{name}?version=&cpe= runs the CLI's checks and answers 422 with NVD's form of the CPE;
    • GET /api/watchlist adds entries;
    • POST /api/scan returns verdicts per judged product;
    • /api/cves rows carry the four fields, sanitised.
  • status gets a Verdict column when any row has a current verdict.
  • Not here (01b-2): the KEV fields stored, with the database listings ranked by priority (the test_priority_wiring boundary), and KEV or MSRC entries judged through NVD's record of the same CVE.

Tests (strict TDD)

  • test_verdict_stored.py: migration from a hand-written 2026.44 cve_software, round trip, stale version hidden, one verdict per product, the latest scan wins, status.
  • test_api_versioned.py: add, 422s, update, entries, scan split {affected: 10, not evaluable: 0, not affected: 11}, rows read back, entry without a version. NVD replayed from the recording; Debian 503 is listed in errors.
  • Seen failing first (15/15 red). 15/15 mutants killed.
  • Full suite after rebasing on main: 2186 passed; ruff and mypy clean.

Note: feat/the-verdict-is-kept on the remote is the same commit before the rebase. The force-push was refused, so it went to this new branch; the old one can be deleted.

🤖 Generated with Claude Code

PR-01a judged each CVE against the installed version in memory; the next
`status`, /api/cves and the web UI went back to CVEs by name, and a scan
over HTTP did not judge at all, so the two scans disagreed.

- cve_software gains verdict, verdict_reason, verdict_fixed_version and
  judged_version, migrated into existing databases: per product, since
  one CVE can affect one watched product and not another. The latest
  scan decides it.
- get_cves shows a verdict only while the installed version is still the
  one it was given for: a stale "affected" sends someone to patch what
  is patched, a stale "not affected" is worse.
- affected.mark is the one place both scans judge.
- The API takes version and cpe on POST /api/watchlist/{name} with the
  CLI's checks, lists entries on GET /api/watchlist, and returns the
  split per product from POST /api/scan. `status` gets a Verdict column.

Tests first, seen failing; 15 of 15 mutants killed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/patchradar/db/database.py Fixed
Comment thread src/patchradar/db/database.py Fixed
@codspeed

codspeed Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 14.06%

⚠️ 11 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

❌ 4 regressed benchmarks
✅ 13 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ ⚠️ test_api_cves_filtered 49.7 ms 59.2 ms -16.09%
❌ ⚠️ test_api_cves 33.6 ms 39.2 ms -14.13%
❌ ⚠️ test_get_cves_by_software 10.7 ms 12.3 ms -13.07%
❌ ⚠️ test_get_cves 11.4 ms 13.1 ms -12.9%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/the-verdict-is-kept-per-product (284d5de) with main (2e2fdc8)

Open in CodSpeed

@codecov

codecov Bot commented Oct 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…e watchlist

CodSpeed on #59: /api/watchlist 24% slower, get_cves and the /api/cves
listings 14-17%.

- /api/watchlist read the names and the entries in two queries; the names
  now come from the entries.
- get_cves picked the current verdict with a CASE per column; the version
  check is now part of the join, so a stale link simply does not match.
  Measured locally on the benchmark's 500 seeded CVEs: 1.68 ms -> 1.61 ms
  (main 1.45 ms). The rest is the join and four more fields per row, which
  is what reading the verdict costs: the same query with NULL columns was
  already 1.57 ms.

The full listing's stale-verdict case had no test of its own: added, and
it kills the mutant that drops the version check from that join.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +344 to +345
async with db.execute(f"""
SELECT cves.*, {_CURRENT_VERDICT} FROM cves
Comment on lines +356 to +357
async with db.execute(f"""
SELECT cves.*, {_CURRENT_VERDICT} FROM cves
@sonarqubecloud

Copy link
Copy Markdown

@maksimtech

Copy link
Copy Markdown
Owner Author

Folded into #64, which now targets main with every commit of the stack: one review, one merge, no conflicts from squash merges.

@maksimtech maksimtech closed this Oct 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants