Skip to content

Commit dff0ee3

Browse files
committed
Release v0.7.1
1 parent a1a0fac commit dff0ee3

3 files changed

Lines changed: 134 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,95 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.7.1] — 2026-04-23
11+
12+
### Changed
13+
- **Release workflow gated on full CI.** `release.yml` now runs the
14+
`ci.yml` job as a prerequisite (via `workflow_call`) on the tagged
15+
SHA before GoReleaser starts. If the tag SHA fails lint, tests,
16+
staticcheck, gosec, or govulncheck, no artifacts publish, no tap
17+
PR opens, no half-released state to clean up. Adds a two-layer
18+
quality gate: ruleset-enforced CI on `main` pre-tag, plus a
19+
re-run on the tag SHA itself at release time.
20+
- **Release pre-flight steps in GoReleaser job.** Before the real
21+
`goreleaser release`, the workflow now runs `goreleaser check`
22+
(validates `.goreleaser.yaml` syntax — catches a malformed config
23+
in under a second) and `goreleaser release --snapshot --clean`
24+
(full cross-compile dry-run: SBOM generation, archive creation,
25+
cask template rendering — surfaces platform-specific build
26+
breakage before anything touches GitHub Release or cosign).
27+
Tag-push → published release now takes ~7-9 min instead of ~4,
28+
in exchange for a half-publish-proof pipeline.
29+
- **CI adds `staticcheck` and `gosec` steps.** Previously only run
30+
locally via `task lint`; now enforced on every PR and every
31+
`main` push. gosec exclusion list is kept in sync with
32+
`Taskfile.yml`'s `task lint` (same 11 exclusions, same
33+
justifications). Adds ~15s to CI runtime.
34+
- **`ci.yml` is a reusable workflow.** Added `workflow_call:` to
35+
its triggers so `release.yml` can invoke it as a gate job.
36+
External-facing behavior on `push: main` and `pull_request`
37+
events is unchanged.
38+
39+
### Fixed
40+
- **Stale flag references in docs.** `README.md:168` used
41+
`--output silent`; the actual flag is `--silent` / `-s`. Fixed.
42+
`Taskfile.yml`'s `task run` description hinted `[--verbose]` as
43+
an example `verify` flag; no such flag exists. Replaced with
44+
`[--skip-external] [--skip-signatures] [--json]`.
45+
- **`task release-snapshot` fails offline.** The task ran
46+
`goreleaser release --snapshot --clean` without the `--skip=sign,publish`
47+
flags that the `.goreleaser.yaml` header comment documents as the
48+
correct local dry-run invocation. Without the skips, cosign tries
49+
keyless signing, which requires a GitHub OIDC token only the
50+
release workflow has — the local task sat waiting for a device-flow
51+
OAuth code it couldn't receive, then failed after five minutes.
52+
Fixed: task now matches the documented dry-run.
53+
- **`CONTRIBUTING.md` release procedure updated for the
54+
`protect-main` ruleset.** The documented `jj git push --bookmark main`
55+
flow was written before the ruleset existed and is now blocked by
56+
the required-status-checks rule. Procedure rewritten to route the
57+
release commit through a PR (`release-vX.Y.Z` branch → rebase-merge)
58+
so CI runs on the exact SHA before it lands on `main` and then
59+
gets tagged. Added prose explaining the two-layer
60+
tests-green-on-this-SHA guarantee (ruleset at merge time +
61+
`release.yml` CI gate at tag time), a `git tag -v` verification
62+
step, and the expected shape of `release.yml`'s two-job flow.
63+
- **Bug-report issue template.** `.github/ISSUE_TEMPLATE/bug_report.yml`
64+
was carrying a stale recommendation to pass `--output debug`
65+
(doesn't exist anywhere in the CLI). Template overhauled: added
66+
a subcommand dropdown, an install-method dropdown covering all
67+
six real install paths, an OS/arch dropdown, a subject-ID text
68+
input (ULID / UUIDv7) so maintainers can correlate bugs with
69+
server logs, a steps-to-reproduce field, and guidance on which
70+
values to redact before submitting. The stale `--output debug`
71+
hint is replaced with `--json` (which does exist).
72+
73+
### Security
74+
- **Branch-protection ruleset (`protect-main`) active on `main`.**
75+
Not a code change — this is a repo-settings change — but
76+
documenting it here because it's load-bearing for the release
77+
flow: pushes to `main` (direct or PR-merged) now require the
78+
`Test (ubuntu-latest)` and `Test (macos-latest)` checks to be
79+
green on the exact SHA. Linear history is enforced; deletion and
80+
non-fast-forward pushes are blocked; no bypass actors. Pairs
81+
with the `release.yml` CI gate so a release tag can only point
82+
at a commit whose CI went green twice (once at merge time, once
83+
at tag time).
84+
85+
### Developer experience
86+
- **Copyright / SPDX headers on every source file.** Previously
87+
only `.go` files carried the canonical two-line header; 11
88+
non-Go source files (GitHub workflow YAMLs, issue templates,
89+
`dependabot.yml`, `.goreleaser.yaml`, `Taskfile.yml`, the
90+
install shell scripts, and `defaults/config.toml`) did not.
91+
Now uniform across the tree — SBOM / license scanners see a
92+
consistent per-file license declaration. The TOML header
93+
propagates into user configs generated by `truestamp config init`.
94+
- **Copyright year range rolled to 2019-2026.** `2021-2026` was
95+
the stale start-of-coverage in the canonical header; 2019 is
96+
the correct founding year. Applied uniformly across all 151
97+
header-carrying files.
98+
1099
## [0.7.0] — 2026-04-23
11100

12101
### Added

CONTRIBUTING.md

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -126,29 +126,32 @@ The PR flow (introduced in 0.3.0) preserves an audit trail of every cask update;
126126

127127
- Repository secret `HOMEBREW_TAP_GITHUB_TOKEN` on `truestamp/truestamp-cli`. **This must be a fine-grained PAT scoped to `truestamp/homebrew-tap` only, with `Contents: Read and write` + `Pull requests: Read and write`.** Do not use a classic `repo`-scoped PAT — the classic scope is broader than the release pipeline needs and should not be reintroduced. The `Pull requests` scope is what lets GoReleaser open the cask update PR and what lets the follow-up step merge it.
128128
- `mise install` locally so `task release-check` and `task release-snapshot` work for pre-flight testing.
129+
- `protect-main` ruleset (repo Settings → Rules → Rulesets). Enforces linear history, blocks force-pushes and deletions, and **requires `Test (ubuntu-latest)` + `Test (macos-latest)` green on the exact SHA** before anything merges to `main`. The release flow below routes the CHANGELOG commit through a PR specifically to satisfy that rule. Release tags then trigger a second CI re-run (via `release.yml`'s `workflow_call` into `ci.yml`) before GoReleaser starts — two layers of "tests green on this SHA" before artifacts publish.
129130

130131
### Pre-flight checklist
131132

132133
```sh
133134
# Working copy is clean and on top of the latest origin/main.
134135
jj git fetch
135-
jj log -r 'main@origin..@' # expect 0 commits not on origin
136+
jj log -r 'main@origin..@' # expect the empty WC change, nothing else
136137

137138
# Full quality gate — race detector + active fuzz + vuln scan + all-platform build.
138139
# Takes ~3-5 minutes; use this at the release boundary, not for every commit.
139140
task precommit-full
140141

141142
# GoReleaser can build the full artifact set with ldflags intact.
142-
task release-check # validates .goreleaser.yaml
143-
task release-snapshot # rm -rf dist/ && goreleaser release --snapshot --clean
143+
task release-check # validates .goreleaser.yaml (<1s)
144+
task release-snapshot # rm -rf dist/ && goreleaser release --snapshot --clean --skip=sign,publish
144145

145146
# Inspect the generated cask before tagging.
146147
cat dist/homebrew/Casks/truestamp-cli.rb
147148
```
148149

150+
`release-snapshot` skips `sign` and `publish` because cosign keyless signing requires a GitHub OIDC token (only available inside the release workflow), and no dry-run should touch the GitHub Release API. Expect the `version` in the rendered cask to read `X.Y.Z-SNAPSHOT-<shortsha>` — that gets replaced with the real tag during the actual release.
151+
149152
### Update `CHANGELOG.md`
150153

151-
Move entries from `## [Unreleased]` into a new section for the version you're about to cut. Use today's date and the Keep-a-Changelog groupings. Add a matching link reference at the bottom.
154+
Move entries from `## [Unreleased]` into a new section for the version you're about to cut. Use today's date and the Keep-a-Changelog groupings.
152155

153156
```md
154157
## [Unreleased]
@@ -159,34 +162,63 @@ Move entries from `## [Unreleased]` into a new section for the version you're ab
159162
- ...
160163
```
161164

162-
### Commit and push the CHANGELOG
165+
### Open a release PR for the CHANGELOG commit
163166

164-
This repo is a jj colocated workspace. Commit the CHANGELOG edit as a normal change and advance `main`:
167+
The `protect-main` ruleset (see Prerequisites) requires CI checks to pass on the exact SHA before `main` accepts it, so the release commit must land via PR — direct `jj git push --bookmark main` is rejected with `GH013: Repository rule violations found`. This is by design: the PR gives CI a chance to run on the SHA that's about to be tagged.
165168

166169
```sh
167-
jj describe -m "Prep release vX.Y.Z"
168-
jj bookmark move main --to @
169-
jj git push --bookmark main
170+
# Commit the CHANGELOG edit.
171+
jj describe -m "Release vX.Y.Z"
172+
173+
# Push to a release branch instead of directly to main.
174+
jj bookmark create release-vX.Y.Z -r @
175+
jj git push --bookmark release-vX.Y.Z
176+
177+
# Open the PR. Keep the title exactly "Release vX.Y.Z" — it's what
178+
# the changelog and commit history expect.
179+
gh pr create --base main --head release-vX.Y.Z \
180+
--title "Release vX.Y.Z" \
181+
--body "See CHANGELOG.md for the full release notes."
182+
183+
# Wait for CI to go green on the PR, then merge via rebase so the
184+
# signed tag below points at the exact CHANGELOG commit (merge commits
185+
# would introduce a different SHA, which the linear-history rule also
186+
# rejects anyway).
187+
gh pr checks <pr> --watch --repo truestamp/truestamp-cli
188+
gh pr merge <pr> --rebase --delete-branch --repo truestamp/truestamp-cli
189+
190+
# Sync jj to the post-merge main.
191+
jj git fetch
192+
jj bookmark set main -r main@origin
170193
```
171194

172195
### Tag and push
173196

174-
jj does not create annotated tags itself — use the git CLI in the same working copy (the jj repo is colocated with `.git/`):
197+
jj does not create annotated tags itself — use the git CLI in the same working copy (the jj repo is colocated with `.git/`). Run `git tag -v` afterwards to confirm the tag was signed; the repo has `tag.gpgsign=true` + `user.signingkey` set, so plain `git tag -a` auto-signs.
175198

176199
```sh
177200
git tag -a vX.Y.Z -m "vX.Y.Z - one-line summary of the headline change"
201+
git tag -v vX.Y.Z # expect "Good 'git' signature ..." — abort if unsigned.
178202
git push origin vX.Y.Z
179203
```
180204

181205
The tag must point at the exact commit that `main` now holds, and must start with `v` so GoReleaser's trigger (`push: tags: ['v*']`) fires.
182206

183207
### Watch the release
184208

209+
The tag push triggers `release.yml`, which runs two top-level jobs:
210+
211+
1. `ci``workflow_call` into `ci.yml`, re-running the full lint + test matrix on the tagged SHA. If this fails, nothing publishes.
212+
2. `goreleaser` (needs: ci) — runs `goreleaser check`, then a `--snapshot --clean` dry-run (local cross-compile + SBOM + cask template render, surfaces platform-specific breakage before the real publish), then the real `goreleaser release --clean`, then the homebrew-tap PR merge, then build-provenance attestation.
213+
214+
Total runtime: ~7-9 minutes (CI gate 3-5 min + snapshot ~1 min + real release ~2-3 min).
215+
185216
```sh
186217
run_id=$(gh run list --workflow=release.yml --limit 1 --json databaseId -q '.[].databaseId')
187218
gh run watch "$run_id" --exit-status
188219

189-
# Verify artifacts landed.
220+
# Verify artifacts landed (expect 14 assets: checksums.txt +
221+
# checksums.txt.sigstore + 6 platform archives + 6 SBOMs).
190222
gh release view vX.Y.Z --json tagName,assets -q '{tag: .tagName, assets: (.assets | length)}'
191223

192224
# Confirm the tap PR merged and none are dangling.

Taskfile.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,9 @@ tasks:
298298
- mise exec -- goreleaser check
299299

300300
release-snapshot:
301-
desc: Build a local snapshot release into dist/ (no publishing, no network)
301+
desc: "Local snapshot release into dist/ — skips cosign signing (keyless requires the CI OIDC token) and GitHub publishing, so this runs fully offline and is safe for pre-release inspection."
302302
cmds:
303-
- mise exec -- goreleaser release --snapshot --clean
303+
- mise exec -- goreleaser release --snapshot --clean --skip=sign,publish
304304
- 'echo "✅ Snapshot artifacts in dist/"'
305305

306306
# =============================================================================

0 commit comments

Comments
 (0)