go-track CI papercuts: coverage-badge dilution by generated files + bootstrap-v0.1.0 gets no CI

Two go-track CI papercuts surfaced repeatedly across the module-extraction programme (27+ modules, Jul 2026). Both are small, well-understood cicd component fixes; this issue captures the full investigation so the work can be picked up without re-deriving it. Per the repo's spec-first rule, each part likely wants a short spec — but everything needed to write and implement them is below.


Part A — Generated files dilute the coverage badge (go-test)

Problem

go-test runs go test -race -coverprofile=cover.out $[[ inputs.paths ]] then drives the badge off go tool cover -func=cover.out | tail -1. When paths is ./..., mockery-generated mocks and other generated code are included in the coverage denominator. Generated files have ~0% test coverage, so the badge reads far below the real figure for hand-written code.

Evidence (from the extraction programme)

  • go/config: real coverage 92.2%, but ./... including the generated configmock/ package dilutes it to ~55%.
  • go/forge: real 95.9%, ./... including mocks/ + doubles dilutes to 46.8%.

Current workaround (fragile, per-repo)

Each module hand-scopes the paths input to exclude generated dirs, e.g. paths: ". ./direct/... ./forgetest/..." or paths: ".". This is manual, easy to get wrong, has to be maintained as packages are added, and means the badge silently misreports if someone forgets.

Proposed fix

Have go-test strip generated files from cover.out before computing the badge, so paths can stay ./... and the badge reflects hand-written code without per-repo bookkeeping. Go's own generated-file convention is the reliable signal: the first line matches ^// Code generated .* DO NOT EDIT\.$ (mockery, protoc, stringer, etc. all emit it). Sketch:

  1. After go test -coverprofile=cover.out …, enumerate .go files whose first line matches the generated marker.
  2. Drop those files' entries from cover.out (their lines are path.go:from.col,to.col stmts count).
  3. Run go tool cover -func=cover.out | tail -1 on the filtered profile for the badge.

Gate behind an input, e.g. exclude_generated (default true), so a consumer can opt out. Keep paths as-is for what gets tested; this only changes what's counted.

Acceptance

  • With exclude_generated: true and paths: "./...", a module with a mocks/*mock package reports the same badge % as scoping paths by hand does today.
  • Turning it off reproduces current behaviour.
  • Self-test (tests/go-test/) fixture gains a generated file (with the marker) and asserts it is absent from the counted profile.

Open questions

  • A1: Filter in shell (grep/awk over cover.out), or via a small tool already in the dev-tools image? Shell keeps the image lean.
  • A2: Match only the exact Go marker, or also honour an explicit exclude-glob input for non-marked generated dirs?

Part B — Bootstrap v0.1.0 releases get no CI (go-test + go-lint)

Problem

A brand-new module's first release runs no go-test/go-lint in CI, and its coverage badge reads unknown until some later code-touching MR.

Root cause

go-test and go-lint gate on a changes: rule, default:

["**/*.go", "**/go.mod", "**/go.sum", ".golangci.*", ".gitlab-ci.yml"]

A releaser-pleaser Release MR for a bootstrap v0.1.0 changes only CHANGELOG.md (the tag/release is cut from it). That matches nothing in the changes list, so test+lint are skipped on the Release MR — and main-push workflow:rules already exclude them — so v0.1.0 is tagged having only ever been validated locally. (Security jobs do run on the Release MR, so the govulncheck/osv/trivy signal is covered; only test+lint+coverage are missing.) Confirmed identical on multiple bootstrap Release MRs (e.g. aferobilly).

Proposed fix

Add CHANGELOG.md (and **/CHANGELOG.md if nested changelogs are ever used) to the default changes array of both go-test and go-lint, so a CHANGELOG-only Release MR triggers test+lint and the bootstrap release gets a real CI pass + a coverage badge.

Trade-off (acceptable / desired)

Every Release MR (not just v0.1.0) will now also run test+lint. That is the point — a release should be validated before it is tagged — and the extra cost is one test+lint run per release MR. No effect on ordinary code MRs (they already match **/*.go).

Acceptance

  • A CHANGELOG-only MR triggers go-test and go-lint.
  • A bootstrap v0.1.0 Release MR shows a passing test+lint pipeline and publishes a coverage badge before the tag is cut.
  • Ordinary MRs are unaffected.

Open question

  • B1: Add CHANGELOG.md to the component defaults (fixes the whole fleet at once, the intent here), or document it as a per-repo override? Defaults are preferred — every toolkit module has this gap.

Notes

  • Parts A and B are independent; they can ship as separate specs/MRs. They are bundled here because both are go-test-adjacent papercuts from the same programme and share the same investigation context.
  • Both are feat/fix on the go track → a cicd minor. Follow the standard component-authoring flow (spec → template + self-test + reference page → decision-log row).