Skip to content

fix(ImagesCatalog): latestAttestationForProduct should filter by stream, not just org/package #26

@castrojo

Description

@castrojo

Summary

CodeRabbit finding in src/components/ImagesCatalog.tsx. Deferred from the SBOM pipeline PR (upstream-pr/sbom-pipeline).


The Bug

File: src/components/ImagesCatalog.tsx:154-171

function latestAttestationForProduct(
  productOrg: string,
  productPackage: string,
) {
  const cache = sbomData as unknown as SbomAttestationsData;
  if (!cache?.streams) return null;

  for (const stream of Object.values(cache.streams)) {
    if (stream.org !== productOrg || stream.package !== productPackage)
      continue;
    // Takes the FIRST matching stream regardless of which stream it is
    const keys = Object.keys(stream.releases || {}).sort().reverse();
    if (keys.length === 0) continue;
    return stream.releases[keys[0]];
  }
  return null;
}

The function matches on org + package only. For most products, org and package together uniquely identify one stream. However:

  • ublue-os/bluefin exists in multiple streams: stable, gts, beta, latest
  • The function returns the latest release from whichever matching stream it encounters first (iteration order of Object.values is insertion order, which is deterministic but arbitrary)
  • This means the attestation shown on the images page for a product card may come from a different stream than the product's primary stream

For example, a product card for Bluefin (stable) could display an attestation entry from the gts stream, which may have a different verified/failed state.


Fix

Add a streamId parameter to latestAttestationForProduct:

function latestAttestationForProduct(
  productOrg: string,
  productPackage: string,
  streamId?: string,  // optional — fall back to first match if not provided
) {
  const cache = sbomData as unknown as SbomAttestationsData;
  if (!cache?.streams) return null;

  for (const [sid, stream] of Object.entries(cache.streams)) {
    if (stream.org !== productOrg || stream.package !== productPackage) continue;
    if (streamId && sid !== streamId) continue;
    const keys = Object.keys(stream.releases || {}).sort().reverse();
    if (keys.length === 0) continue;
    return stream.releases[keys[0]];
  }
  return null;
}

Then pass the appropriate stream identifier from the product data when calling the function (line 255).


Acceptance Criteria

  • Each product card shows an attestation from its own primary stream
  • The function signature accepts an optional streamId parameter
  • Products whose stream cannot be determined fall back to first-match behavior (no regression)

Context

Found in CodeRabbit review of the SBOM pipeline PR. Pre-existing issue in ImagesCatalog.tsx, deferred to keep the SBOM PR scoped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingtask/p2Medium priority task

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions