Skip to content
This repository was archived by the owner on May 30, 2026. It is now read-only.

fix(attest): pin attestations list API version to 2026-03-10 - #143

Merged
C. Spencer Beggs (spencerbeggs) merged 1 commit into
mainfrom
fix/attest-list-api-version
May 30, 2026
Merged

C. Spencer Beggs (spencerbeggs) merged 1 commit into
mainfrom
fix/attest-list-api-version

Conversation

@spencerbeggs

Copy link
Copy Markdown
Contributor

Summary

Attest.listForSubject (the attestation idempotency probe in the Phase-3 publish chain) called GET /repos/{owner}/{repo}/attestations/{subject_digest} under Octokit's default REST API version. GitHub deprecated that version's list-response shape (Sunset 2028-03-10), so Octokit logged a deprecation warning on every call — twice per build digest (provenance + SBOM).

What changed

  • Pins X-GitHub-Api-Version: 2026-03-10 on the list request (and on the new bundle_url fetch) — the supported version.
  • That version removes the inline Sigstore bundle from list responses, so predicateType is now recovered two ways:
    • Filtered path (the orchestrator's path — once for SLSA provenance, once for CycloneDX SBOM): uses the server-side predicate_type query parameter, no per-entry bundle fetch.
    • No-filter path: fetches each entry's bundle_url and decodes the in-toto predicateType; undecodable/failed fetches drop just that entry.
  • The public AttestationListEntry type ({ attestationUrl, predicateType }) and the listForSubject signature are unchanged. No breaking API change.

Tests

  • Rewrote Attest.list.test.ts to simulate the 2026-03-10 API faithfully (honors predicate_type, serves bundle_url).
  • Added coverage asserting the request pins 2026-03-10 and forwards predicate_type, and that the filtered path fires no bundle fetch.

Verification

typecheck, lint, full test (1251 passing), and ci:build (TSDoc valid) all green. Changeset added (patch). Design docs + user docs updated to match.

Closes #131

Signed-off-by: C. Spencer Beggs spencer@savvyweb.systems

@savvy-web-bot savvy-web-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Current Commit: e96a1ba

Summary

Clean fix for a real operational problem — every publish was logging a deprecation warning twice (once for provenance, once for SBOM), and this silences both without touching the public API. The implementation is correct, well-documented, and the test rewrite is the most impressive part of the PR.

Checks

Check Status
Tests ✅ success
Code Quality ✅ success
Conventional Commits ✅ success
PR Title Validation ✅ success
Markdown ✅ success
Build ⚠️ not present in CI output

The Build check (rslib, api-extractor, TSDoc) didn't appear in the CI results for this commit. The PR description says ci:build passed locally — if the Build job simply wasn't triggered on this branch that's fine, but worth a look if it's expected to run.

What's well done

Separation of concerns in AttestLive.ts — the refactor cleanly splits what was a monolithic parseListedAttestations into three focused helpers: parseRawAttestations (data extraction), attestationUrlFor (URL construction), and predicateTypeFromBundleUrl (lazy fetch + decode). Each one does exactly one thing.

ATTESTATIONS_API_VERSION constant — the TSDoc block is exactly the right amount of explanation: it names the deprecated shape, quotes the Sunset date, explains what the new shape drops, and links to the GitHub breaking-changes page. Future readers won't need to go spelunking.

FETCH_BUNDLE_REQUEST = "GET {bundle_url}" comment — flagging this explicitly is the right call; the Octokit template-expansion trick for absolute URLs is non-obvious and the inline doc prevents future confusion.

Test rewrite — attestationServer is a genuinely faithful simulator of the 2026-03-10 API: it honors predicate_type server-side, omits the inline bundle, serves bundles at bundle_url, and records every request for protocol-level assertions. The new tests cover all four behavioral commitments — API version pinning, filter forwarding, zero bundle fetches on the filtered path, and bundle fetches on the no-filter path. That's exactly the coverage this kind of API-version migration needs.

Dual payload shape in predicateTypeFromBundleUrl — checking both data.dsseEnvelope.payload and data.bundle.dsseEnvelope.payload is good defensive coding for a response shape that could vary by caller or future API iteration.

Observations

Sequential bundle fetches (no-filter path) — the for loop in the no-filter path fetches bundle_urls one at a time:

for (const entry of raw) {
  const predicateType = await predicateTypeFromBundleUrl(octokit, entry.bundle_url);

For the orchestrator's filtered path (the hot path — twice per publish) this code never runs. For the no-filter case — likely manual/diagnostic use — the sequential fetch adds latency linear in the number of attestations. A Promise.all would parallelize them if this path ever ends up under performance scrutiny, but it's not a problem for the current use case.

Server filter trusted without client-side sanity check — the filtered path assigns predicateType: predicateTypeFilter to every returned entry without verifying the server actually honored the filter. The old code had a client-side re-filter as a backstop (though the PR notes that backstop was motivated by a now-resolved short-alias mismatch concern). Trusting the server here is a reasonable design choice — the freeform URI is now passed directly so the aliasing problem is gone — just worth having on record in case GitHub ever returns mismatched entries.

Neither observation is blocking. The PR is correct and the tradeoffs are well-understood.

listForSubject called the repository attestations endpoint under Octokit's default API version, whose list-response shape GitHub deprecated (Sunset 2028-03-10), logging a deprecation warning on every publish.

The request now pins X-GitHub-Api-Version 2026-03-10, which drops the inline Sigstore bundle. predicateType is recovered via the server-side predicate_type query when a filter is supplied, otherwise by fetching each entry's bundle_url; the AttestationListEntry type is unchanged.

Closes #131
Signed-off-by: C. Spencer Beggs <spencer@savvyweb.systems>
@savvy-web-bot

savvy-web-bot Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Code Review

Current Commit: 576f668

Summary

Clean, well-scoped fix. The implementation is correct, the tests are comprehensive, and all checks pass. No blocking issues.


Checks

Check Result
PR Title Validation ✅ success
Conventional Commits ✅ success
Code Quality ✅ success
Markdown ✅ success
Tests ✅ success

What the PR does

listForSubject was hitting the attestations endpoint under Octokit's default API version, whose list-response shape GitHub deprecated (Sunset 2028-03-10). This logged a deprecation warning twice per publish (once for provenance, once for SBOM). The fix pins X-GitHub-Api-Version: 2026-03-10, adapts to the new response shape (no inline bundle, only bundle_url), and recovers predicateType via the server-side predicate_type query parameter on the filtered path or by fetching each entry's bundle_url on the unfiltered path.


Correctness

Two-path split is sound. The filtered path trusts server narrowing and assigns predicateTypeFilter directly to each entry — no bundle fetch. The unfiltered path fires Promise.all over the bundle_url fetches, each inside predicateTypeFromBundleUrl which swallows its own errors (catch { return null; }), so one bad bundle never rejects the batch. The outer try/catch around the list request handles 404/422 (isEmptyListError) and still returns [] for never-attested subjects.

Removal of client-side re-filter is correct. The old code re-filtered because the legacy API version's predicate_type query was treated as advisory (short aliases might not match freeform URIs). The 2026-03-10 endpoint accepts the full freeform URI, so every returned entry is guaranteed to match the requested type. The comment at line 422–426 makes this explicit.

Dual-shape handling in predicateTypeFromBundleUrl (lines 163–166) covers both data.dsseEnvelope.payload (raw DSSE envelope) and data.bundle.dsseEnvelope.payload (wrapped bundle shape). The test exercises the first shape; the second is a reasonable defensive fallback.

FETCH_BUNDLE_REQUEST = "GET {bundle_url}" is a valid Octokit pattern. Octokit expands {bundle_url} to the absolute URL value and issues the request to that host. The API version header is forwarded to the bundle endpoint too, which is intentional per the PR description.


Tests

The attestationServer helper is a genuinely faithful simulation of the 2026-03-10 API: it honors the server-side predicate_type filter, returns entries without an inline bundle (only bundle_url), and serves the full Sigstore bundle on the follow-up fetch. The new call-capture tests directly assert:

  • X-GitHub-Api-Version: 2026-03-10 on every request
  • predicate_type forwarded on the filtered path
  • Zero bundle fetches on the filtered path (exactly one total request)
  • Two requests (list + bundle) on the unfiltered path

This level of test coverage is exactly what a behavioral change at an API boundary warrants.


Minor observations (non-blocking)

  • reason: "upload" for list errors (AttestLive.ts:482) is a pre-existing oddity — listForSubject maps its errors to AttestError({ reason: "upload", ... }). Not introduced here and out of scope.
  • Promise.all concurrency on the unfiltered path fires all bundle fetches simultaneously. In practice subjects have 1–2 attestations (the orchestrator always uses the filtered path), so this isn't a rate-limit concern in the current usage pattern. Worth keeping in mind if listForSubject is ever called without a filter on a subject with many entries.

✅ Approving.

@savvy-web-bot savvy-web-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues addressed at 576f668; approving.

@spencerbeggs
C. Spencer Beggs (spencerbeggs) merged commit 364c7a9 into main May 30, 2026
20 checks passed
@spencerbeggs
C. Spencer Beggs (spencerbeggs) deleted the fix/attest-list-api-version branch May 30, 2026 02:55
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attest.listForSubject uses a deprecated GitHub attestations REST endpoint (sunset 2028-03-10)

1 participant