Skip to content

feat(priority): store what decides a rank, and rank the stored listings - #61

Closed
maksimtech wants to merge 1 commit into
feat/kev-entries-judged-through-nvdfrom
feat/the-database-ranks-by-priority
Closed

maksimtech wants to merge 1 commit into
feat/kev-entries-judged-through-nvdfrom
feat/the-database-ranks-by-priority

Conversation

@maksimtech

Copy link
Copy Markdown
Owner

Audit finding PR-01, step 01b-3 (docs/DESIGN.md §5.5). Stacked on #60, which is stacked on #59: review in order #59 → #60 → this. After each squash merge I'll retarget the next one to main.

What was wrong

known_exploited, kev_ransomware, the KEV dates and the CVSS v4.0 exploit maturity travelled in the scan's records and were not columns. The next patchradar status, and /api/cves, listed by publication date, so an exploited CVE from 2023 sat under every new 5.3. test_priority_wiring.py held that boundary on purpose, to say "now rank the database paths too" the day the columns arrived.

What changes

  • cves gains known_exploited, kev_ransomware, kev_date_added, kev_due_date and cvss_exploit_maturity, migrated in place.
    • Every scan that brings them refreshes them with COALESCE: a row NVD stored on Monday learns on Wednesday that CISA listed it, and a scan whose keyword KEV does not match does not erase the fact.
    • CISA's "Unknown" is stored as no.
  • get_cves orders by priority.sql_order, an ORDER BY built from the constants priority() decides with, then by date and id as tie-breaks. The order stays in SQLite, so a listing of 50 is still a query for 50.
  • Index cves_by_priority on that expression. On the benchmark's 500 CVEs the full listing takes 1.0 ms, against 1.45 ms on main (date order) and 1.9 ms without the index. The listing filtered by product does not use it: ~1.7 ms, against 1.41 ms on main.
  • /api/cves rows carry priority, priority_reason, known_exploited, kev_ransomware, kev_date_added and kev_due_date. status is titled "Most urgent CVEs" and has a Priority column.
  • test_priority_wiring.py now holds the other side: the facts are stored, and get_cves orders by sql_order, not by date.

Tests (strict TDD)

tests/test_priority_stored.py, 10 tests:

  • migration from a hand-written pre-change cves;
  • storing the facts: round trip, CISA's "Unknown", KEV arriving later, a scan without KEV not forgetting;
  • order: one set of rows covering every rank and edge goes through SQL and through sort_by_priority and must come out identical. The edges are EPSS exactly at 0.1, EPSS 1.5, maturity E:P and E:U, ransomware "Unknown", CVSS 0.0 next to no score, and score ties. The same check runs on the per-product path and on a short LIMIT;
  • readers: /api/cves and status.

Seen failing first (10/10 red). 15 of 16 mutants killed. The survivor removes the "scored" branch of the CASE, which is equivalent: COALESCE(score, -1) already puts scored rows above unscored ones within the same rank. I kept the branch because it mirrors priority()'s ranks. Full suite: 2210 passed; ruff and mypy clean.

🤖 Generated with Claude Code

CISA's exploitation facts and the CVSS v4.0 exploit maturity travelled
in the scan's records and were not columns: the next `status` listed by
publication date, and an exploited CVE from 2023 sat under every new
5.3. test_priority_wiring held that boundary to say "now rank the
database paths too" the day the columns arrived.

- cves gains known_exploited, kev_ransomware, kev_date_added,
  kev_due_date and cvss_exploit_maturity, migrated in place and
  refreshed by every scan that brings them: CISA lists a CVE long after
  NVD publishes it. CISA's "Unknown" is stored as no.
- get_cves orders by priority.sql_order, built from the constants
  priority() decides with; an index on that order makes the full
  listing 1.0 ms on the benchmark's 500 CVEs, against 1.45 ms by date.
- /api/cves rows carry priority, priority_reason and the KEV fields;
  status has a Priority column and lists most urgent first.
- test_priority_wiring now holds the other side of the boundary.

Tests first, seen failing; the SQL order is checked against
sort_by_priority on rows covering every rank and edge. 15 of 16 mutants
killed; the survivor drops the "scored" branch, which COALESCE on the
score already orders the same way (equivalent).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@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
@maksimtech
maksimtech deleted the feat/the-database-ranks-by-priority branch October 17, 2026 20:51
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.

1 participant