Skip to content

Commit 26dc2ff

Browse files
richlanderclaude
andcommitted
Add servicing version diff query to metrics.md
Add query comparing hal-index vs releases-index for calculating the diff between two servicing versions (e.g., 8.0.15 to 8.0.22). The query demonstrates: - Configurable parameters (major version, patch range, severity filter) - CVE severity filtering (CRITICAL, HIGH, MEDIUM, LOW threshold) - Affected products aggregation across the version range - Time gap calculation (days/months behind) The hal-index query is ~15x smaller (~80 KB vs 1,220 KB) and provides severity and affected products data that releases-index cannot offer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a42c521 commit 26dc2ff

File tree

1 file changed

+232
-0
lines changed

1 file changed

+232
-0
lines changed

release-notes/queries/metrics.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ done | sort -u
455455
| CVE details (severity, fixes) ||||
456456
| Timeline navigation ||||
457457
| SDK-first navigation ||||
458+
| Version diff (severity, products) || ⚠️ Partial | 15x smaller |
458459

459460
## Cache Coherency
460461

@@ -484,6 +485,236 @@ Result: Each file is a consistent snapshot
484485

485486
The HAL `_embedded` pattern ensures that any data referenced within a document is included in that document. There are no "dangling pointers" to data that might not exist in a cached copy of another file.
486487

488+
### Servicing Version Diff
489+
490+
#### Query: "What changed between .NET 8.0.15 and 8.0.22?"
491+
492+
| Schema | Files Required | Total Transfer |
493+
|--------|----------------|----------------|
494+
| hal-index | `index.json``8.0/index.json` + 3 patch indexes | **~80 KB** |
495+
| releases-index | `releases-index.json` + `8.0/releases.json` | **1,220 KB** (partial data) |
496+
497+
**hal-index:**
498+
499+
This query requires two passes: first to get the release summaries, then to fetch CVE details for severity filtering and affected products.
500+
501+
```bash
502+
ROOT="https://raw.githubusercontent.com/dotnet/core/release-index/release-notes/index.json"
503+
504+
# Configuration
505+
MAJOR_VERSION="8.0"
506+
FROM_PATCH=15
507+
TO_PATCH=22
508+
SEVERITY_FILTER="CRITICAL" # Minimum severity: CRITICAL, HIGH, MEDIUM, or LOW (all)
509+
510+
# Step 1: Get the major version href
511+
VERSION_HREF=$(curl -s "$ROOT" | jq -r --arg ver "$MAJOR_VERSION" '._embedded.releases[] | select(.version == $ver) | ._links.self.href')
512+
513+
# Step 2: Get release summaries and security release URLs
514+
VERSION_DATA=$(curl -s "$VERSION_HREF")
515+
516+
# Extract security release URLs for CVE detail fetching
517+
SECURITY_HREFS=$(echo "$VERSION_DATA" | jq -r --argjson from "$FROM_PATCH" --argjson to "$TO_PATCH" '
518+
[._embedded.releases[] |
519+
select((.version | split(".")[2] | tonumber) > $from and (.version | split(".")[2] | tonumber) <= $to) |
520+
select(.security) |
521+
._links.self.href] | .[]
522+
')
523+
524+
# Step 3: Fetch CVE details from each security release and aggregate
525+
CVE_DETAILS=$(for HREF in $SECURITY_HREFS; do
526+
curl -s "$HREF" | jq -c '._embedded.disclosures[]? | {id, cvss_severity, affected_products, title}'
527+
done | jq -s 'unique_by(.id)')
528+
529+
# Step 4: Generate the diff report with severity-filtered CVE IDs
530+
echo "$VERSION_DATA" | jq -r --arg major "$MAJOR_VERSION" --argjson from "$FROM_PATCH" --argjson to "$TO_PATCH" \
531+
--arg severity "$SEVERITY_FILTER" --argjson cve_details "$CVE_DETAILS" '
532+
# Filter releases in range (excluding start, including end)
533+
[._embedded.releases[] |
534+
select((.version | split(".")[2] | tonumber) > $from and (.version | split(".")[2] | tonumber) <= $to)
535+
] as $releases |
536+
537+
# Filter CVEs by minimum severity
538+
[$cve_details[] | select(
539+
($severity == "LOW") or
540+
($severity == "MEDIUM" and (.cvss_severity == "MEDIUM" or .cvss_severity == "HIGH" or .cvss_severity == "CRITICAL")) or
541+
($severity == "HIGH" and (.cvss_severity == "HIGH" or .cvss_severity == "CRITICAL")) or
542+
($severity == "CRITICAL" and .cvss_severity == "CRITICAL")
543+
)] as $filtered_cves |
544+
545+
# Aggregate affected products across all CVEs
546+
[$cve_details[].affected_products // [] | .[]] | unique | sort as $all_products |
547+
548+
{
549+
from_version: "\($major).\($from)",
550+
to_version: "\($major).\($to)",
551+
from_date: (._embedded.releases[] | select(.version == "\($major).\($from)") | .date | split("T")[0]),
552+
to_date: (._embedded.releases[] | select(.version == "\($major).\($to)") | .date | split("T")[0]),
553+
total_releases: ($releases | length),
554+
security_releases: ([$releases[] | select(.security)] | length),
555+
non_security_releases: ([$releases[] | select(.security | not)] | length),
556+
total_cves: ([$releases[].cve_records? // [] | .[]] | unique | length),
557+
cve_ids: [$filtered_cves[] | .id],
558+
cve_severity_filter: $severity,
559+
affected_products: $all_products,
560+
releases: [$releases[] | {version, date: (.date | split("T")[0]), security, cve_count}]
561+
}
562+
'
563+
```
564+
565+
**Output:**
566+
567+
```json
568+
{
569+
"from_version": "8.0.15",
570+
"to_version": "8.0.22",
571+
"from_date": "2025-04-08",
572+
"to_date": "2025-11-11",
573+
"total_releases": 7,
574+
"security_releases": 3,
575+
"non_security_releases": 4,
576+
"total_cves": 5,
577+
"cve_ids": [
578+
"CVE-2025-55315"
579+
],
580+
"cve_severity_filter": "CRITICAL",
581+
"affected_products": [
582+
"aspnetcore-runtime",
583+
"dotnet-runtime",
584+
"windowsdesktop-runtime"
585+
],
586+
"releases": [
587+
{ "version": "8.0.22", "date": "2025-11-11", "security": false, "cve_count": 0 },
588+
{ "version": "8.0.21", "date": "2025-10-14", "security": true, "cve_count": 3 },
589+
{ "version": "8.0.20", "date": "2025-09-09", "security": false, "cve_count": 0 },
590+
{ "version": "8.0.19", "date": "2025-08-05", "security": false, "cve_count": 0 },
591+
{ "version": "8.0.18", "date": "2025-07-08", "security": false, "cve_count": 0 },
592+
{ "version": "8.0.17", "date": "2025-06-10", "security": true, "cve_count": 1 },
593+
{ "version": "8.0.16", "date": "2025-05-22", "security": true, "cve_count": 1 }
594+
]
595+
}
596+
```
597+
598+
To include all CVEs regardless of severity:
599+
600+
```bash
601+
SEVERITY_FILTER="LOW" # LOW is the minimum, so this includes all CVEs
602+
```
603+
604+
The script above outputs `from_date` and `to_date`. To calculate the time gap, pipe the output through an additional jq filter:
605+
606+
```bash
607+
# Pipe the output to calculate days between versions
608+
... | jq -r '
609+
# Parse ISO dates and calculate difference
610+
((.to_date | strptime("%Y-%m-%d") | mktime) -
611+
(.from_date | strptime("%Y-%m-%d") | mktime)) / 86400 | floor as $days |
612+
. + {
613+
days_behind: $days,
614+
months_behind: (($days / 30) | floor)
615+
}
616+
'
617+
```
618+
619+
This adds `days_behind` and `months_behind` to the output:
620+
621+
```json
622+
{
623+
"from_version": "8.0.15",
624+
"to_version": "8.0.22",
625+
"from_date": "2025-04-08",
626+
"to_date": "2025-11-11",
627+
"days_behind": 217,
628+
"months_behind": 7,
629+
...
630+
}
631+
```
632+
633+
**releases-index:**
634+
635+
```bash
636+
ROOT="https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json"
637+
638+
# Configuration
639+
MAJOR_VERSION="8.0"
640+
FROM_PATCH=15
641+
TO_PATCH=22
642+
643+
# Step 1: Get the releases.json URL for the major version
644+
RELEASES_URL=$(curl -s "$ROOT" | jq -r --arg ver "$MAJOR_VERSION" '.["releases-index"][] | select(.["channel-version"] == $ver) | .["releases.json"]')
645+
646+
# Step 2: Filter releases in range and aggregate
647+
curl -s "$RELEASES_URL" | jq -r --arg major "$MAJOR_VERSION" --argjson from "$FROM_PATCH" --argjson to "$TO_PATCH" '
648+
# Filter releases in range (excluding start, including end)
649+
[.releases[] | select(
650+
(.["release-version"] | split(".")[2] | tonumber) > $from and
651+
(.["release-version"] | split(".")[2] | tonumber) <= $to
652+
)] as $releases |
653+
654+
{
655+
from_version: "\($major).\($from)",
656+
to_version: "\($major).\($to)",
657+
from_date: ([.releases[] | select(.["release-version"] == "\($major).\($from)")] | .[0] | .["release-date"]),
658+
to_date: ([.releases[] | select(.["release-version"] == "\($major).\($to)")] | .[0] | .["release-date"]),
659+
total_releases: ($releases | length),
660+
security_releases: ([$releases[] | select(.security)] | length),
661+
non_security_releases: ([$releases[] | select(.security | not)] | length),
662+
total_cves: ([$releases[].["cve-list"]? // [] | .[]] | unique | length),
663+
cve_ids: ([$releases[].["cve-list"]? // [] | .[] | .["cve-id"]] | unique | sort),
664+
cve_severity_filter: "(not available)",
665+
affected_products: "(not available)",
666+
releases: [$releases[] | {
667+
version: .["release-version"],
668+
date: .["release-date"],
669+
security,
670+
cve_count: ([.["cve-list"]? // [] | .[]] | length)
671+
}]
672+
}
673+
'
674+
```
675+
676+
**Output:**
677+
678+
```json
679+
{
680+
"from_version": "8.0.15",
681+
"to_version": "8.0.22",
682+
"from_date": "2025-04-08",
683+
"to_date": "2025-11-11",
684+
"total_releases": 7,
685+
"security_releases": 3,
686+
"non_security_releases": 4,
687+
"total_cves": 5,
688+
"cve_ids": [
689+
"CVE-2025-26646",
690+
"CVE-2025-30399",
691+
"CVE-2025-55247",
692+
"CVE-2025-55248",
693+
"CVE-2025-55315"
694+
],
695+
"cve_severity_filter": "(not available)",
696+
"affected_products": "(not available)",
697+
"releases": [
698+
{ "version": "8.0.22", "date": "2025-11-11", "security": false, "cve_count": 0 },
699+
{ "version": "8.0.21", "date": "2025-10-14", "security": true, "cve_count": 3 },
700+
{ "version": "8.0.20", "date": "2025-09-09", "security": false, "cve_count": 0 },
701+
{ "version": "8.0.19", "date": "2025-08-05", "security": false, "cve_count": 0 },
702+
{ "version": "8.0.18", "date": "2025-07-08", "security": false, "cve_count": 0 },
703+
{ "version": "8.0.17", "date": "2025-06-10", "security": true, "cve_count": 1 },
704+
{ "version": "8.0.16", "date": "2025-05-22", "security": true, "cve_count": 1 }
705+
]
706+
}
707+
```
708+
709+
**Analysis:**
710+
711+
- **Completeness:** ⚠️ Partial—the releases-index can count releases and list CVE IDs, but cannot provide CVE severity, affected products, or detailed CVE information without fetching external CVE URLs.
712+
- **Severity filtering:** The hal-index allows filtering `cve_ids` to specific severity levels (CRITICAL, HIGH, etc.) via the `SEVERITY_FILTER` variable, while `total_cves` always shows the complete count.
713+
- **Affected products:** The hal-index aggregates all affected products across the version range (e.g., `dotnet-runtime`, `aspnetcore-runtime`), enabling teams to identify which components need patching.
714+
- **Executive reporting:** For CIO/CTO reporting, the hal-index provides actionable data (severity-filtered CVEs, affected products) while the releases-index only provides CVE IDs that require manual lookup.
715+
716+
**Winner:** hal-index (**15x smaller**, with CVE severity filtering and affected products)
717+
487718
## Summary
488719

489720
| Metric | hal-index | releases-index |
@@ -492,6 +723,7 @@ The HAL `_embedded` pattern ensures that any data referenced within a document i
492723
| CVE queries (latest security patch) | 52 KB | 1,220 KB |
493724
| Recent CVEs (last 2 security releases) | 23 KB | 2.5 MB |
494725
| CVEs in last 12 months | ~90 KB | 2.5 MB |
726+
| Version diff with severity + products | ~80 KB | 1,220 KB (partial—no severity/products) |
495727
| Cache coherency | ✅ Atomic | ❌ TTL mismatch risk |
496728
| Query syntax | snake_case (dot notation) | kebab-case (bracket notation) |
497729
| Link traversal | `._links.self.href` | `.["releases.json"]` |

0 commit comments

Comments
 (0)