Context
Surfaced while removing git-cliff and rehoming the release ritual into CONTRIBUTING.md. Documenting how to cut a release is what made it clear there is no documented way to practise cutting one — and that the mechanism the repo already reaches for doesn't work.
Problem
The release workflow has never executed. The repository has no tags and no releases, so every step it performs — cosign signing, SLSA provenance attestation, GitHub Release creation, and the cross-compiled artifact matrix — is unproven.
just release-check cannot close that gap, and says so itself: it builds with --snapshot, which "skips cosign signing and never runs provenance (CI-only, OIDC-bound), so this proves the build, not the hardening." It never runs the workflow at all.
The repo already anticipates this. .goreleaser.yaml sets prerelease: auto for exactly this purpose, its comment naming v0.0.0-test as "the end-to-end hardening dry-run", and commit 7549c86's message states that "a throwaway prerelease tag remains the only real proof."
That tag cannot get through the workflow. release.yml strips the leading v from the tag, extracts the matching ## [<version>] section from CHANGELOG.md, and fails hard when nothing matches — and a throwaway tag has no changelog section by definition. Traced with the workflow's own extraction logic: v0.0.0-test resolves to version 0.0.0-test, matches no heading, yields an empty notes file, and exits 1 at the "No release notes found" guard.
The guard is not the defect. It is what prevents publishing a release with an empty body — one of the two failures fixed in 7549c86. So the two requirements are in genuine tension: the check that makes a real release correct is the same check that makes a rehearsal impossible.
The consequence is that the repository's highest-consequence workflow — the only one holding contents: write and id-token: write, and the one that publishes and signs public artifacts — is also its only completely unexercised one. Both defects found in it so far were found by reading it, not by running it, and the next one will surface on a real tag or not at all.
Suggested approaches
Options, not a prescription:
- Let the notes extraction supply a placeholder body when the tag carries a prerelease component, keeping the hard failure for a release tag.
- Document a dry-run procedure under
CONTRIBUTING.md § Releases that adds a temporary matching changelog section, and names the cleanup it leaves behind (the tag, the published prerelease, the section).
- Give the workflow an explicit dry-run trigger that runs build, sign, and attest without publishing, so no tag or release needs cleaning up afterward.
The first two keep the proof honest — they exercise the real tag-push path. The third is safer to repeat but proves slightly less, since the trigger differs from the one a real release uses.
Context
Surfaced while removing
git-cliffand rehoming the release ritual intoCONTRIBUTING.md. Documenting how to cut a release is what made it clear there is no documented way to practise cutting one — and that the mechanism the repo already reaches for doesn't work.Problem
The release workflow has never executed. The repository has no tags and no releases, so every step it performs — cosign signing, SLSA provenance attestation, GitHub Release creation, and the cross-compiled artifact matrix — is unproven.
just release-checkcannot close that gap, and says so itself: it builds with--snapshot, which "skips cosign signing and never runs provenance (CI-only, OIDC-bound), so this proves the build, not the hardening." It never runs the workflow at all.The repo already anticipates this.
.goreleaser.yamlsetsprerelease: autofor exactly this purpose, its comment namingv0.0.0-testas "the end-to-end hardening dry-run", and commit 7549c86's message states that "a throwaway prerelease tag remains the only real proof."That tag cannot get through the workflow.
release.ymlstrips the leadingvfrom the tag, extracts the matching## [<version>]section fromCHANGELOG.md, and fails hard when nothing matches — and a throwaway tag has no changelog section by definition. Traced with the workflow's own extraction logic:v0.0.0-testresolves to version0.0.0-test, matches no heading, yields an empty notes file, and exits 1 at the "No release notes found" guard.The guard is not the defect. It is what prevents publishing a release with an empty body — one of the two failures fixed in 7549c86. So the two requirements are in genuine tension: the check that makes a real release correct is the same check that makes a rehearsal impossible.
The consequence is that the repository's highest-consequence workflow — the only one holding
contents: writeandid-token: write, and the one that publishes and signs public artifacts — is also its only completely unexercised one. Both defects found in it so far were found by reading it, not by running it, and the next one will surface on a real tag or not at all.Suggested approaches
Options, not a prescription:
CONTRIBUTING.md§ Releases that adds a temporary matching changelog section, and names the cleanup it leaves behind (the tag, the published prerelease, the section).The first two keep the proof honest — they exercise the real tag-push path. The third is safer to repeat but proves slightly less, since the trigger differs from the one a real release uses.