Goal
Make static/data/sbom-attestations.json — populated nightly by the SBOM pipeline — the single, authoritative source of truth for all package version data shown on the site. All scripts that currently scrape GitHub release body HTML or use stale fallback data must instead read from this cache.
Problem
The documentation site currently has two competing version data sources:
-
SBOM cache (sbom-attestations.json) — accurate, cryptographically verified, full RPM manifest (2,676 packages per release). But it is always empty in production because the nightly pipeline uses a stale PAT (PROJECT_READ_TOKEN) that returns HTTP 403.
-
GitHub release body HTML scraping (fetch-github-driver-versions.js) — fragile regex matching against Markdown tables in release bodies. The SBOM overlay in this script is listed as optional and only applies when the cache is populated — which it never is.
The result: the driver versions page, changelogs, and images catalog all show version data scraped from release bodies, not from the verified SBOM.
Root Cause
fetch-github-sbom.js uses the GitHub Packages API (/orgs/ublue-os/packages/container/bluefin/versions) to enumerate image tags. This API requires packages:read scope on a cross-org PAT. The PAT expired/was rotated and the pipeline has been silently failing ever since.
The PAT is not needed. Public GHCR images are accessible anonymously. The GitHub Releases API (/repos/ublue-os/bluefin/releases) provides tag names without any elevated scope. The GHCR manifest API provides digests via an anonymous bearer token.
Product Catalog and SBOM Coverage
GTS is retired — it is not present in any streamOrder in PRODUCT_SPECS and must not appear in STREAM_SPECS.
The correct stream catalog (derived from live PRODUCT_SPECS in fetch-github-images.js):
| SBOM Stream ID |
Package |
Org |
Stream |
SBOM Status |
| bluefin-stable |
bluefin |
ublue-os |
stable |
Has SBOMs (keyless) |
| bluefin-latest |
bluefin |
ublue-os |
latest |
Has SBOMs (keyless) |
| bluefin-lts |
bluefin |
ublue-os |
lts |
No SBOMs yet (key-based) |
| bluefin-dx-stable |
bluefin-dx |
ublue-os |
stable |
Has SBOMs (keyless) |
| bluefin-dx-latest |
bluefin-dx |
ublue-os |
latest |
Has SBOMs (keyless) |
| bluefin-dx-lts |
bluefin-dx |
ublue-os |
lts |
No SBOMs yet (key-based) |
| bluefin-gdx-lts |
bluefin-gdx |
ublue-os |
lts |
No SBOMs yet (key-based) |
| bluefin-gdx-latest |
bluefin-gdx |
ublue-os |
latest |
Unknown |
| projectbluefin-dakota |
dakota |
projectbluefin |
latest |
No signing pipeline |
Note: stable-daily and beta are build stages, not separate SBOM streams.
Child Issues
- castrojo/documentation issue 36 — Replace Packages API with Releases API and anonymous GHCR token in fetch-github-sbom.js
- castrojo/documentation issue 37 — Remove PAT from update-sbom-cache.yml — use github.token only
- castrojo/documentation issue 38 — Make fetch-github-driver-versions.js use SBOM as primary source, HTML scraping as NVIDIA-only fallback
- castrojo/documentation issue 39 — Update AGENTS.md to remove stale packages:read PAT documentation
Success Criteria
update-sbom-cache.yml runs nightly with zero PAT dependencies and produces a non-empty sbom-attestations.json
fetch-github-driver-versions.js reads kernel, gnome, mesa, podman, systemd, bootc from SBOM; only NVIDIA falls back to release body
- All version data on driver-versions page, changelogs, and images catalog comes from the SBOM cache
npm run typecheck and npm run lint pass with zero errors
- AGENTS.md no longer documents packages:read as a required scope for SBOM
- STREAM_SPECS in
fetch-github-sbom.js matches the product catalog above — no GTS stream
Branch
feature/firehose-changelogs in castrojo/documentation
Assisted-by: Claude Sonnet 4.6 via GitHub Copilot
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
Architecture Review
Verdict: Architecturally sound. Four clarifications needed before implementation begins.
Strengths
The epic correctly identifies the root failure mode (silent PAT expiry producing an always-empty cache) and proposes an approach with the right properties: replace an elevated credential with a credential-free path, elevate the verified source (SBOM) to primary, and demote the fragile source (release body scraping) to NVIDIA-only fallback. The data quality improvement is real — 2,676 RPM packages from a cryptographically attested SBOM versus regex over Markdown tables. The atomic-write pattern already in fetch-github-sbom.js (write to .tmp then rename) is correct and must be preserved across the rewrite.
Finding 1: The manifest index / multi-arch digest problem (medium severity)
The proposed anonymous GHCR bearer token path fetches the manifest and uses the Docker-Content-Digest header. This returns the image index digest (multi-arch manifest list), not the amd64 platform digest. SBOM attestations are attached to the platform-specific manifest digest. Passing the index digest to cosign or oras will silently find no attestations. Implementation of issue 36 must resolve the amd64 child manifest digest, not the index digest.
Finding 2: Silent success on empty output (medium severity)
If the Releases API returns an empty list due to a temporary outage, the workflow will write a structurally valid but stream-empty JSON and exit zero. The workflow must add a validation step that exits non-zero if all streams produce zero releases.
Finding 3: Implementation sequencing dependency (low severity)
Issue 36 (rewrite the script) must merge and be verified working before issue 37 (remove PAT from workflow) is implemented. If the workflow PAT is removed while the script still calls the Packages API, the next nightly run fails with HTTP 403.
Finding 4: oras login can be eliminated entirely (low severity)
For public GHCR images, oras falls back to anonymous bearer token negotiation automatically when not logged in. The oras login step should be deleted entirely from the workflow, not just have its credential swapped.
QA Checklist
Partial-Cache Boundary Cases
CI Signal for Empty Cache
LTS Streams With No SBOMs
GTS Retirement
Cross-Stream Data Isolation
Error Propagation
Goal
Make
static/data/sbom-attestations.json— populated nightly by the SBOM pipeline — the single, authoritative source of truth for all package version data shown on the site. All scripts that currently scrape GitHub release body HTML or use stale fallback data must instead read from this cache.Problem
The documentation site currently has two competing version data sources:
SBOM cache (
sbom-attestations.json) — accurate, cryptographically verified, full RPM manifest (2,676 packages per release). But it is always empty in production because the nightly pipeline uses a stale PAT (PROJECT_READ_TOKEN) that returns HTTP 403.GitHub release body HTML scraping (
fetch-github-driver-versions.js) — fragile regex matching against Markdown tables in release bodies. The SBOM overlay in this script is listed as optional and only applies when the cache is populated — which it never is.The result: the driver versions page, changelogs, and images catalog all show version data scraped from release bodies, not from the verified SBOM.
Root Cause
fetch-github-sbom.jsuses the GitHub Packages API (/orgs/ublue-os/packages/container/bluefin/versions) to enumerate image tags. This API requirespackages:readscope on a cross-org PAT. The PAT expired/was rotated and the pipeline has been silently failing ever since.The PAT is not needed. Public GHCR images are accessible anonymously. The GitHub Releases API (
/repos/ublue-os/bluefin/releases) provides tag names without any elevated scope. The GHCR manifest API provides digests via an anonymous bearer token.Product Catalog and SBOM Coverage
GTS is retired — it is not present in any
streamOrderin PRODUCT_SPECS and must not appear in STREAM_SPECS.The correct stream catalog (derived from live PRODUCT_SPECS in
fetch-github-images.js):Note:
stable-dailyandbetaare build stages, not separate SBOM streams.Child Issues
Success Criteria
update-sbom-cache.ymlruns nightly with zero PAT dependencies and produces a non-emptysbom-attestations.jsonfetch-github-driver-versions.jsreads kernel, gnome, mesa, podman, systemd, bootc from SBOM; only NVIDIA falls back to release bodynpm run typecheckandnpm run lintpass with zero errorsfetch-github-sbom.jsmatches the product catalog above — no GTS streamBranch
feature/firehose-changelogsincastrojo/documentationAssisted-by: Claude Sonnet 4.6 via GitHub Copilot
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
Architecture Review
Verdict: Architecturally sound. Four clarifications needed before implementation begins.
Strengths
The epic correctly identifies the root failure mode (silent PAT expiry producing an always-empty cache) and proposes an approach with the right properties: replace an elevated credential with a credential-free path, elevate the verified source (SBOM) to primary, and demote the fragile source (release body scraping) to NVIDIA-only fallback. The data quality improvement is real — 2,676 RPM packages from a cryptographically attested SBOM versus regex over Markdown tables. The atomic-write pattern already in
fetch-github-sbom.js(write to.tmpthen rename) is correct and must be preserved across the rewrite.Finding 1: The manifest index / multi-arch digest problem (medium severity)
The proposed anonymous GHCR bearer token path fetches the manifest and uses the
Docker-Content-Digestheader. This returns the image index digest (multi-arch manifest list), not the amd64 platform digest. SBOM attestations are attached to the platform-specific manifest digest. Passing the index digest to cosign or oras will silently find no attestations. Implementation of issue 36 must resolve the amd64 child manifest digest, not the index digest.Finding 2: Silent success on empty output (medium severity)
If the Releases API returns an empty list due to a temporary outage, the workflow will write a structurally valid but stream-empty JSON and exit zero. The workflow must add a validation step that exits non-zero if all streams produce zero releases.
Finding 3: Implementation sequencing dependency (low severity)
Issue 36 (rewrite the script) must merge and be verified working before issue 37 (remove PAT from workflow) is implemented. If the workflow PAT is removed while the script still calls the Packages API, the next nightly run fails with HTTP 403.
Finding 4:
oras logincan be eliminated entirely (low severity)For public GHCR images,
orasfalls back to anonymous bearer token negotiation automatically when not logged in. Theoras loginstep should be deleted entirely from the workflow, not just have its credential swapped.QA Checklist
Partial-Cache Boundary Cases
bluefin-stableandbluefin-latestare populated insbom-attestations.jsonand all lts/gdx streams are absent, the driver-versions page renders correctly for the populated streams and shows graceful fallback (not blank or crashed) for unpopulated streams{ "generatedAt": null, "streams": {} }(the committed seed), downstream scripts detect the empty state and fall back to release-body scraping — noCannot read properties of nullerrorssbom-attestations.jsonis missing entirely from the static data directory at build time,npm run buildfails with a clear error rather than silently building with no version dataCI Signal for Empty Cache
update-sbom-cache.ymlwrites a cache entry where every stream has zero releases, the workflow exits non-zero — there is no silent successLTS Streams With No SBOMs
bluefin-lts,bluefin-dx-lts, andbluefin-gdx-ltsentries insbom-attestations.jsonhaveattestation.present: false— not a missing key, notnull, not an error objectfetch-github-driver-versions.jsdoes not attempt to readpackageVersionsfor an lts stream whenattestation.presentisfalse— it falls back to release-body scraping without logging an errorattestation.presentflips totrue, the script picks them up automatically on the next nightly run — no code change is neededGTS Retirement
STREAM_SPECSinfetch-github-sbom.jscontains exactly 8 stream entries — nobluefin-gtsentrysbom-attestations.jsonoutput from the rewritten pipeline contains nobluefin-gtskey instreamsgtsappear in any stream mapping, fallback table, or comment in any modified scriptCross-Stream Data Isolation
bluefin-dx-stablenever reads SBOM data from thebluefin-stablecache entry, even if the cacheKey prefix (stable-YYYYMMDD) matches — the fullstreamId + cacheKeymust be the lookup keyprojectbluefin/dakota(no signing pipeline) produces no SBOM lookup attempt — nooras discoverorcosigncalls are made for dakota streamsError Propagation