Skip to content

ci: ship provenance as a release asset, add opt-in Codecov Test Analytics - #333

Merged
CybotTM merged 4 commits into
mainfrom
ci/provenance-asset-and-test-results
Aug 2, 2026
Merged

ci: ship provenance as a release asset, add opt-in Codecov Test Analytics#333
CybotTM merged 4 commits into
mainfrom
ci/provenance-asset-and-test-results

Conversation

@CybotTM

@CybotTM CybotTM commented Aug 2, 2026

Copy link
Copy Markdown
Member

Two gaps surfaced by netresearch/ofelia's dashboards. Both are in shared workflows, so both affect every consumer — the second is opt-in for that reason.

1. Build provenance never reaches the release

actions/attest-build-provenance files the attestation in GitHub's attestation store, where gh attestation verify finds it. Nothing about the release itself says it carries provenance, so a consumer reading the release cannot see it. OpenSSF Scorecard's releasesHaveProvenance probe matches release assets on a suffix:

var provenanceExtensions = []string{".intoto.jsonl"}

so every release this workflow produces scores as having none. Ofelia's scorecard warning — "release artifact v0.28.1 does not have provenance" — is that gap, not a missing attestation: the v0.28.1 linux/amd64 binary does have one, retrievable by digest.

Why not just rename the bundle. The file the action writes is a Sigstore bundle wrapping a DSSE envelope. Naming it .intoto.jsonl would satisfy the probe while misdescribing the format — a consumer expecting JSON Lines would get a Sigstore bundle. The envelope is unwrapped instead, so the extension describes what the file actually is: one DSSE envelope on one line, carrying the same in-toto statement.

Verified against a real bundle (ofelia v0.28.1, linux/amd64):

payloadType    application/vnd.in-toto+json
payload        in-toto Statement/v1
predicateType  https://slsa.dev/provenance/v1
subject        ofelia-linux-amd64

The step fails loudly if the bundle is missing or has no dsseEnvelope, rather than shipping an asset that looks like provenance and contains null.

One thing this change would have broken. The container job drops SBOMs from bin/ because they share the binary's name prefix and shadow the Dockerfile's COPY bin/<app>-linux-*. The new files have exactly the same problem, so that cleanup now covers them too — without it, every consumer's image build would have broken.

No change needed in verify-release.yml: the new asset is signed by the existing per-asset cosign loop and listed in checksums.txt by the existing checksum step, which is what that workflow asserts.

2. Codecov Test Analytics has no data

go-check.yml uploads coverage.out and nothing else, so the Test Analytics page is empty for every consumer: no flaky-test detection, no per-test durations, no failure history.

With enable-test-results: true the unit tests run through gotestsum — which drives go test -json and writes a JUnit report next to the coverage profile — and codecov/test-results-action uploads it.

Off by default, and a separate step. Swapping the go test invocation for everyone changes how every consumer's tests execute in order to gain a reporting feature; that is not a trade a shared workflow should make on their behalf. The existing step is untouched and the gotestsum path is mutually exclusive with it, so a consumer that has not opted in runs exactly the command it runs today.

The upload is gated on !cancelled() rather than success — a run whose tests failed is precisely what Test Analytics is for, and gating on success would upload only green runs and leave the flaky view blind to every failure.

Verified locally that the invocation produces both artifacts from one run: a valid JUnit report (36 tests) and the coverage profile the threshold step and the Codecov upload already consume.

Notes

  • codecov/test-results-action is pinned to the commit v1.2.1 dereferences to (0fa95f0e), resolved via both the tag object and the commits API rather than transcribed from the tag.
  • actionlint reports no findings on either workflow; both parse as YAML.
  • Consumers opt into the second feature with enable-test-results: true; a follow-up PR on netresearch/ofelia will do that once this lands.

CybotTM added 2 commits August 2, 2026 10:07
actions/attest-build-provenance files the attestation in GitHub's
attestation store, where `gh attestation verify` finds it. Nothing about
the release itself says it carries provenance, so a consumer reading the
release — including OpenSSF Scorecard — cannot see it. Scorecard's
releasesHaveProvenance probe matches release assets on a `.intoto.jsonl`
suffix:

    var provenanceExtensions = []string{".intoto.jsonl"}

so every release produced by this workflow scored as having none. The
warning on netresearch/ofelia's scorecard ("release artifact v0.28.1
does not have provenance") is that gap, not a missing attestation: the
v0.28.1 linux/amd64 binary does have one, retrievable by digest.

The bundle the action writes is a Sigstore bundle wrapping a DSSE
envelope. Renaming it to .intoto.jsonl would satisfy the probe while
misdescribing the format — a consumer expecting JSON Lines would get a
Sigstore bundle. The envelope is unwrapped instead, so the extension
describes what the file is: one DSSE envelope on one line, carrying the
same in-toto statement.

Verified against a real bundle (ofelia v0.28.1, linux/amd64): the
transformation yields a single line whose payloadType is
application/vnd.in-toto+json and whose payload decodes to an
in-toto Statement/v1 with predicateType https://slsa.dev/provenance/v1
for subject ofelia-linux-amd64. The step fails loudly when the bundle is
absent or carries no dsseEnvelope, rather than shipping an asset that
looks like provenance and contains "null".

The container job's bin/ preparation now drops these files alongside the
SBOMs. Both share the binary's name prefix, so without that the new file
would shadow the Dockerfile's `COPY bin/<app>-linux-*` — this change
would otherwise have broken every consumer's image build.

No change is needed in verify-release.yml: the new asset is signed by
the existing per-asset cosign loop and listed in checksums.txt by the
existing checksum step, which is what that workflow asserts.

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
The workflow uploads coverage.out and nothing else, so Codecov's Test
Analytics page has no data for any consumer: no flaky-test detection, no
per-test durations, no failure history. That page is what
netresearch/ofelia's /tests/new link shows as unconfigured.

With enable-test-results on, the unit tests run through gotestsum, which
drives `go test -json` and writes a JUnit report alongside the coverage
profile, and codecov/test-results-action uploads it.

It is opt-in and off by default. The alternative — swapping the `go
test` invocation for everyone — changes how every consumer's tests are
executed to gain a reporting feature, and that is not a trade a shared
workflow should make on their behalf. The existing step is left exactly
as it is and the gotestsum path is a second, mutually exclusive step, so
a consumer that has not opted in runs the same command it runs today.

The upload is gated on !cancelled() rather than success: a run whose
tests failed is precisely the one Test Analytics exists to record, and
gating on success would upload only green runs and leave the flaky-test
view blind to every failure. hashFiles() guards the case where the run
died before the report was written.

Verified locally that `gotestsum --junitfile junit.xml --format
standard-verbose -- <the default test-flags> <packages>` produces both
artifacts from one run: a valid JUnit report (36 tests) and the coverage
profile the threshold step and the Codecov upload already consume.

codecov/test-results-action is pinned to the commit v1.2.1 dereferences
to (0fa95f0e), resolved through both the tag object and the commits API
rather than transcribed.

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Copilot AI review requested due to automatic review settings August 2, 2026 08:09
Comment thread .github/workflows/go-check.yml Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates shared GitHub Actions reusable workflows to (1) publish build provenance in a release-consumable format and (2) optionally upload unit test results to Codecov Test Analytics, addressing gaps surfaced across consumer repositories.

Changes:

  • Emit DSSE provenance as a .intoto.jsonl file and include it with binary artifacts for release publishing.
  • Ensure container builds don’t accidentally include SBOM/provenance files that would interfere with Dockerfile COPY bin/<app>-linux-*.
  • Add an opt-in gotestsum/JUnit flow and upload test results to Codecov Test Analytics.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
.github/workflows/release-go-app.yml Emits DSSE provenance as a .intoto.jsonl asset and prevents SBOM/provenance files from shadowing container COPY patterns.
.github/workflows/go-check.yml Adds opt-in gotestsum + JUnit generation and uploads test results to Codecov Test Analytics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/go-check.yml Outdated
Comment thread .github/workflows/go-check.yml
CybotTM added 2 commits August 2, 2026 10:42
Review findings on PR #333.

codecov/test-results-action is deprecated. Its README opens with the
notice and points at codecov/codecov-action with a report-type
parameter, so the JUnit upload now reuses the action — and the SHA —
that the coverage upload already pins. One fewer third-party action, and
no new pin to maintain.

The migration note spells the parameter `report-type`. The action does
not define that name: at the pinned v7.0.0 SHA the input is
`report_type`, with no hyphenated alias, so following the note verbatim
would have passed an unknown input that Actions drops without a word and
uploaded the JUnit report as coverage. Checked against action.yml at
that exact SHA rather than at main.

The gotestsum version is now a literal instead of a workflow input.
SonarCloud is right that a variable version cannot be shown to be
pinned, and a shared workflow should own the version of a tool it
installs rather than leaving each consumer to choose one. The knob was
speculative; the version is bumped here.

Corrects the enable-test-results description as well: it claimed
CODECOV_TOKEN is required, but this workflow declares the secret
optional and Codecov also authenticates through its GitHub App.

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
My previous commit assumed SonarCloud's "Go dependencies should be
locked to verified versions" fired because the version came from a
workflow input. It did not: the rule still fired on the literal
`@v1.13.0`.

This workflow already answers that rule one line further down, where
go-licenses is installed at a full commit SHA with the tag in a comment.
gotestsum now follows the same form. A tag can be repointed at another
commit; a commit cannot, which is what "locked to a verified version"
asks for.

The commit was resolved through the refs API and cross-checked against
the commits API, then verified by running the install: `go install
gotest.tools/gotestsum@c4a0df2e...` produces a binary reporting
"gotestsum version v1.13.0".

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

Comment thread .github/workflows/go-check.yml Dismissed
@CybotTM
CybotTM merged commit 7e1f11e into main Aug 2, 2026
11 checks passed
@CybotTM
CybotTM deleted the ci/provenance-asset-and-test-results branch August 2, 2026 08:59
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.

3 participants