Skip to content

Commit 02a9815

Browse files
committed
feat(release): ship the publish-go-live tag ruleset, keep -sig out of the pipeline
`tags-sig-signed.json` requires a verified signature only on the `vX.Y.Z-sig` provenance companions — the publish-go-live counterpart to `tags-signed.json`, so a repository imports it instead of hand-retargeting the signed ruleset's conditions. The reference release pipeline, and this repository's own `release.yml`, now exclude `v*-sig` from the tag trigger: a signed companion carries no changelog section or candidate marker, so pushing one drove the release pipeline into a version-coherence failure; it routes to the attest workflow instead.
1 parent 8c07e68 commit 02a9815

5 files changed

Lines changed: 39 additions & 10 deletions

File tree

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "tags-sig-signed",
3+
"target": "tag",
4+
"enforcement": "active",
5+
"conditions": {
6+
"ref_name": {
7+
"include": ["refs/tags/v[0-9]*.[0-9]*.[0-9]*-sig"],
8+
"exclude": []
9+
}
10+
},
11+
"rules": [
12+
{ "type": "required_signatures" }
13+
],
14+
"bypass_actors": []
15+
}

‎.github/workflows/release.yml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ name: Release
1313
on:
1414
push:
1515
branches: ["release/v**"]
16-
tags: ["v*"]
16+
# Exclude the signed provenance companion (vX.Y.Z-sig): it attests the
17+
# release commit out of band and carries no changelog section or marker, so
18+
# it must not drive the release pipeline — it routes to the attest workflow.
19+
tags: ["v*", "!v*-sig"]
1720
workflow_dispatch: # cut-release dispatches the first run explicitly
1821

1922
permissions:

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@
33
All notable changes to this project are documented in this file.
44
The format follows [Keep a Changelog](https://keepachangelog.com); releases are cut from the `[Unreleased]` section by this repository's own `changelog` action — the flow dogfoods itself.
55

6+
## [Unreleased]
7+
8+
### Added
9+
10+
- `tags-sig-signed.json` ruleset: requires a verified signature only on the `vX.Y.Z-sig` provenance companions, the publish-go-live counterpart to `tags-signed.json`. Import it instead of hand-retargeting the signed ruleset's conditions.
11+
12+
### Fixed
13+
14+
- The reference release pipeline in `docs/release-flow.md`, and this repository's own `release.yml`, exclude `v*-sig` from the tag trigger (`tags: ["v*", "!v*-sig"]`). A signed provenance companion carries no changelog section or candidate marker, so pushing one used to spuriously drive the release pipeline into a version-coherence failure; it now routes solely to the attest workflow.
15+
616
## [1.5.0] - 2026-08-03
717

818
### Added

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ steps:
210210
Promotes a candidate from within the pipeline, or defers to the release manager — governed by `sign-tags`.
211211
`manual` (the mode an active signature-requiring tag ruleset implies when nothing is configured) is a no-op beside the guidance: a human signs.
212212
`off` creates the annotated, unsigned final tag on the built commit with the marker's message, publishes the draft, and optionally advances the moving major — one job, since a workflow-token tag push triggers no second run.
213-
Unreadable rulesets resolve to `manual`; the shipped [`/.github/rulesets/`](.github/rulesets) files set up the signed alternative.
213+
Unreadable rulesets resolve to `manual`; the shipped [`/.github/rulesets/`](.github/rulesets) files set up the signed alternative, and `tags-sig-signed.json` the publish-go-live one (signatures on the `vX.Y.Z-sig` companions only).
214214

215215
```yaml
216216
- uses: gronke/rust-ci/.github/actions/promote-release@main

‎docs/release-flow.md‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,21 +152,19 @@ git tag -s -m "vX.Y.Z" vX.Y.Z-sig 'vX.Y.Z^{}'
152152
git push origin refs/tags/vX.Y.Z-sig
153153
```
154154

155-
The retargeted ruleset below governs the companion's *form* — if one exists it must carry a verified signature — and nothing requires one to exist.
155+
The `tags-sig-signed` ruleset below governs the companion's *form* — if one exists it must carry a verified signature — and nothing requires one to exist.
156156
What makes the companion load-bearing is a step that reads it: [`require-signed-release`](#registry-publication-behind-a-signature) gates registry publication on exactly this, so on a crate repository an unattested release rehearses the upload instead of performing it.
157157
A repository with nothing to publish — this one — gets provenance only, and the companion is genuinely optional.
158158
Any tag name works; `attestation-tags` narrows what counts, so a repository that would rather keep the `v*` namespace to releases can attest with `sig/vX.Y.Z` and leave `git describe` alone.
159159

160-
The tag ruleset must not require signatures on the versions themselves — GitHub creates them unsigned — so `required_signatures` retargets to the companions:
160+
The tag ruleset must not require signatures on the versions themselves — GitHub creates them unsigned — so signatures are required only on the companions. Import the shipped ruleset (the publish-go-live counterpart to `tags-signed.json`):
161161

162162
```sh
163-
gh api repos/{owner}/{repo}/rulesets/{id} | jq '{name, target, enforcement,
164-
bypass_actors: (.bypass_actors // []),
165-
conditions: (.conditions | .ref_name = {include: ["refs/tags/v[0-9]*.[0-9]*.[0-9]*-sig"], exclude: []}),
166-
rules: [.rules[] | if (.parameters // null) == null then {type} else {type, parameters} end]}' \
167-
| gh api -X PUT repos/{owner}/{repo}/rulesets/{id} --input -
163+
gh api repos/{owner}/{repo}/rulesets --input .github/rulesets/tags-sig-signed.json
168164
```
169165

166+
It requires a verified signature on the `vX.Y.Z-sig` companions and leaves the release tags unsigned. An existing all-`v*` `tags-signed` ruleset must be removed or retargeted first, or it rejects the unsigned release tag GitHub creates on publish.
167+
170168
The guidance step checks this alignment at candidate time and errors while everything is still a draft when a signature rule covers the version itself — the misconfiguration never gets as far as a tag the repository would reject, or one an admin bypass would let fail the gate after going live.
171169
This repository releases itself in this mode; the signed mode stays the reference default.
172170

@@ -281,7 +279,9 @@ name: Release
281279
on:
282280
push:
283281
branches: ["release/v**"]
284-
tags: ["v*"]
282+
# `!v*-sig` keeps the signed provenance companion out of this pipeline: it
283+
# carries no changelog section or marker and routes to the attest workflow.
284+
tags: ["v*", "!v*-sig"]
285285
workflow_dispatch: # cut-release dispatches the first run explicitly
286286

287287
permissions:
@@ -420,6 +420,7 @@ jobs:
420420
- **Actions may create pull requests** (Settings → Actions → General) — `cut-release` opens the merge-back pull request with the workflow token; without the setting the cut fails at that step.
421421
- **Tag ruleset**: let Actions create `v*-rc*` marker tags; keep final `v*` tags restricted to release managers and — to back the workflow's signature preference with real enforcement — require signatures.
422422
The shipped [`tags-signed.json`](../.github/rulesets/tags-signed.json) and [`tags-maintainer-only.json`](../.github/rulesets/tags-maintainer-only.json) carry exactly this shape.
423+
In the publish-go-live mode, import [`tags-sig-signed.json`](../.github/rulesets/tags-sig-signed.json) in place of `tags-signed.json`: signatures are required only on the `vX.Y.Z-sig` companions, and the release tags GitHub creates on publish stay unsigned.
423424
The markers are pushed unsigned with the workflow token, so a rule covering all tags blocks the candidate loop: exclude `v*-rc*` from every creation-restricting and signature-requiring tag rule, and give the release managers a bypass on the final `v*` restriction so the signed tag can be pushed at all.
424425
The optional moving-major step force-moves a bare `v<MAJOR>` tag unsigned, so automating it means excluding those names too; without the step, re-tagging the major stays a manual, signed act.
425426
`require-signed-tag` warns when the workflow enforces signatures but no active tag ruleset does.

0 commit comments

Comments
 (0)