Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions doc/handwritten/for-maintainers/workflows/release.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,21 @@ skipping the publish steps.

## When it runs

- On **push of a version tag** `v*.*.*` (e.g. `v1.2.3`, `v1.2.3-beta.1`) — this
publishes.
- On **`workflow_dispatch`** with two inputs: `version` and `dry_run` (**default
- On **push of a train-prefixed version tag** — each release train versions and
publishes independently on its own prefix: `lib-v*.*.*` (FirstClassErrors +
FirstClassErrors.Testing + FirstClassErrors.RequestBinder, lockstep),
`cli-v*.*.*` (the `fce` .NET tool), `dum-v*.*.*` (Dummies). E.g.
`lib-v1.2.3`, `cli-v1.2.3-beta.1`. A tag push publishes. The train mapping
lives in [`tools/trains.sh`](../../../../tools/trains.sh); the full wiring is
the [Adding a release train](../AddingAReleaseTrain.en.md) runbook.
- On **`workflow_dispatch`** with three inputs: `component` (which train to
pack/publish: `lib`, `cli` or `dum`), `version` and `dry_run` (**default
`true`**). A manual run publishes only if `dry_run` is explicitly unticked.

## Pre-release labels

The version is a SemVer string (a tag's leading `v` is stripped). A **stable** release
The version is a SemVer string (a tag's train prefix — `lib-v`, `cli-v`,
`dum-v` — is stripped). A **stable** release
has no label (`1.4.2`); anything after a `-` is a **pre-release** label, which the
workflow flags as a pre-release on GitHub — and which nuget.org lists the same way.
Common labels, from least to most mature:
Expand All @@ -51,11 +58,18 @@ uses `nuget/vpre`.

## How it runs

One job, `pack-push`: checkout → setup .NET → **resolve & validate version** →
restore → build → test → **pack** (via `tools/packaging/pack.sh`, embedding the
SPDX SBOM) → upload artifacts → **attest build provenance** → **NuGet login
(OIDC)** → **push to NuGet** → **publish GitHub Release**. The last two steps
(and only those) are gated off on a dry run.
One job, `pack-push`: checkout → setup .NET → **resolve & validate version**
(the train and the SemVer, from the tag or the inputs) → restore → build → test
→ **require a major bump for GenDoc breaking changes** (cli train only) →
**pack the resolved train only** (via `tools/packaging/pack.sh`, embedding the
SPDX SBOM and asserting each train's own guards) → upload artifacts → **attest
build provenance** → **NuGet login (OIDC)** → **push to NuGet** → **publish
GitHub Release** (with train-scoped notes from
`tools/packaging/release-notes.sh`, so a lib release never lists cli or dum
work) → **refresh GenDoc's error-catalog baseline** (cli train only: accept
the just-published catalog as the new contract, pushed straight to `main`).
The three publish steps — the NuGet push, the GitHub Release, and the
baseline refresh — are the only ones gated off on a dry run.

## Permissions & security

Expand All @@ -72,15 +86,21 @@ This workflow encodes several hard-won fixes. Each of the following is
deliberate:

- **Version input is validated against a strict SemVer allowlist, read via the
environment.** The tag/input is attacker-controllable (a tag like `v1.2.3;id`
environment.** The tag/input is attacker-controllable (a tag like `lib-v1.2.3;id`
is a valid ref matching the trigger). It is passed through `env:` rather than
interpolated into the shell, and rejected if it does not match the regex —
otherwise it could inject commands into every step that uses it.
- **Build metadata (`+…`) is rejected even though SemVer allows it.** NuGet
strips `+build` from the package identity, so `v1.2.3+build5` would pack as
strips `+build` from the package identity, so `lib-v1.2.3+build5` would pack as
`1.2.3`; combined with `--skip-duplicate` on push, an already-published `1.2.3`
would turn the release into a green no-op that publishes nothing. Failing
loudly is the point.
- **The GenDoc major-bump gate runs before pack, on the cli train only — and in
a dry run too.** GenDoc ships inside the fce tool, so a breaking change to its
error catalog is a breaking change of the cli train: the release refuses to
publish unless the version bumps the major component over the previous
`cli-v` tag. Rehearsing the gate in a dry run catches a forgotten major bump
before a real release attempt, not during one.
- **`Attest build provenance` runs *before* both publications, and runs even in
a dry run.** Only attested artifacts are ever released or pushed; and OIDC /
attestation-permission failures are exactly what the dry run is there to catch.
Expand All @@ -90,8 +110,9 @@ deliberate:
Release assets**, and consumers verify provenance against *those* with `gh
attestation verify` — the nuget.org copy is verified with `dotnet nuget verify`
instead. Do not "simplify" by attesting only the nuget.org copy.
- **`NuGet login (OIDC)` runs on every trigger, including dry run — only the push
and the Release are gated.** The token exchange is what validates the
- **`NuGet login (OIDC)` runs on every trigger, including dry run — only the
publish steps (the push, the Release, and the cli baseline refresh) are
gated.** The token exchange is what validates the
trusted-publishing policy, so a dry run fails red when the policy or
`NUGET_USER` is missing. It mints a single-use key the dry run never spends.
Requires a trusted-publishing policy on nuget.org and the `NUGET_USER` secret
Expand All @@ -114,5 +135,7 @@ deliberate:
- [Release dry run (manual)](../ReleaseDryRun.en.md) — the operational guide.
- [`release-dryrun`](release-dryrun.en.md) — the automatic, side-effect-free
rehearsal that runs on every PR and push, sharing the same `pack.sh`.
- [Adding a release train](../AddingAReleaseTrain.en.md) — how the tag
prefixes, scopes and packing branches of the trains are wired.
- The README's **Supply chain** section documents how a consumer verifies the
provenance and SBOM this workflow produces.
52 changes: 39 additions & 13 deletions doc/handwritten/for-maintainers/workflows/release.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,22 @@ l'attestation incluse tout en sautant les étapes de publication.

## Quand il s'exécute

- Sur **push d'un tag de version** `v*.*.*` (p. ex. `v1.2.3`, `v1.2.3-beta.1`) —
cela publie.
- Sur **`workflow_dispatch`** avec deux entrées : `version` et `dry_run` (**défaut
- Sur **push d'un tag de version préfixé par un train** — chaque train de
release se versionne et se publie indépendamment sur son propre préfixe :
`lib-v*.*.*` (FirstClassErrors + FirstClassErrors.Testing +
FirstClassErrors.RequestBinder, en lockstep), `cli-v*.*.*` (l'outil .NET
`fce`), `dum-v*.*.*` (Dummies). P. ex. `lib-v1.2.3`, `cli-v1.2.3-beta.1`. Un
push de tag publie. Le mapping des trains vit dans
[`tools/trains.sh`](../../../../tools/trains.sh) ; le câblage complet est le
runbook [Ajouter un train de release](../AddingAReleaseTrain.fr.md).
- Sur **`workflow_dispatch`** avec trois entrées : `component` (quel train
packer/publier : `lib`, `cli` ou `dum`), `version` et `dry_run` (**défaut
`true`**). Un run manuel ne publie que si `dry_run` est explicitement décoché.

## Étiquettes de pré-version

La version est une chaîne SemVer (le `v` d'un tag est retiré). Une release **stable**
La version est une chaîne SemVer (le préfixe de train du tag — `lib-v`,
`cli-v`, `dum-v` — est retiré). Une release **stable**
n'a pas d'étiquette (`1.4.2`) ; tout ce qui suit un `-` est une étiquette de
**pré-version**, que le workflow marque comme pre-release sur GitHub — et que nuget.org
liste de la même façon. Étiquettes courantes, du moins mûr au plus mûr :
Expand All @@ -52,11 +60,19 @@ un consommateur doit passer `--prerelease`, d'où le badge README en `nuget/vpre
## Comment il s'exécute

Un seul job, `pack-push` : checkout → setup .NET → **résoudre & valider la
version** → restore → build → test → **pack** (via `tools/packaging/pack.sh`, qui
embarque le SBOM SPDX) → upload des artefacts → **attester la provenance de
version** (le train et le SemVer, depuis le tag ou les entrées) → restore →
build → test → **exiger un bump majeur pour les breaking changes de GenDoc**
(train cli uniquement) → **packer uniquement le train résolu** (via
`tools/packaging/pack.sh`, qui embarque le SBOM SPDX et vérifie les gardes
propres à chaque train) → upload des artefacts → **attester la provenance de
build** → **login NuGet (OIDC)** → **push vers NuGet** → **publier la GitHub
Release**. Les deux dernières étapes (et elles seules) sont désactivées en dry
run.
Release** (avec des notes scopées au train via
`tools/packaging/release-notes.sh`, pour qu'une release lib ne liste jamais du
travail cli ou dum) → **rafraîchir la baseline du catalogue d'erreurs de
GenDoc** (train cli uniquement : accepter le catalogue tout juste publié comme
nouveau contrat, poussé directement sur `main`). Les trois étapes de
publication — le push NuGet, la GitHub Release et le rafraîchissement de la
baseline — sont les seules désactivées en dry run.

## Permissions & sécurité

Expand All @@ -75,15 +91,22 @@ suivants est délibéré :

- **L'entrée de version est validée contre une allowlist SemVer stricte, lue via
l'environnement.** Le tag/entrée est contrôlable par un attaquant (un tag comme
`v1.2.3;id` est une ref valide qui matche le trigger). Il passe par `env:`
`lib-v1.2.3;id` est une ref valide qui matche le trigger). Il passe par `env:`
plutôt que d'être interpolé dans le shell, et il est rejeté s'il ne matche pas
la regex — sinon il pourrait injecter des commandes dans chaque étape qui
l'utilise.
- **Les métadonnées de build (`+…`) sont rejetées bien que SemVer les autorise.**
NuGet retire `+build` de l'identité du package, donc `v1.2.3+build5` packerait
en `1.2.3` ; combiné à `--skip-duplicate` au push, un `1.2.3` déjà publié
NuGet retire `+build` de l'identité du package, donc `lib-v1.2.3+build5`
packerait en `1.2.3` ; combiné à `--skip-duplicate` au push, un `1.2.3` déjà publié
transformerait la release en no-op vert qui ne publie rien. Échouer bruyamment
est le but.
- **La garde de bump majeur GenDoc tourne avant le pack, sur le train cli
uniquement — et en dry run aussi.** GenDoc est embarqué dans l'outil fce, donc
un breaking change de son catalogue d'erreurs est un breaking change du train
cli : la release refuse de publier tant que la version ne bumpe pas la
composante majeure par rapport au précédent tag `cli-v`. Répéter la garde en
dry run attrape un bump majeur oublié avant une vraie tentative de release,
pas pendant.
- **`Attest build provenance` tourne *avant* les deux publications, et tourne
même en dry run.** Seuls des artefacts attestés sont jamais publiés ou poussés ;
et les échecs d'OIDC / de permission d'attestation sont exactement ce que le dry
Expand All @@ -95,8 +118,9 @@ suivants est délibéré :
contre *ceux-là* avec `gh attestation verify` — la copie de nuget.org se
vérifie avec `dotnet nuget verify`. Ne « simplifiez » pas en attestant
seulement la copie de nuget.org.
- **`NuGet login (OIDC)` tourne à chaque trigger, dry run inclus — seuls le push
et la Release sont conditionnés.** L'échange de token est ce qui valide la
- **`NuGet login (OIDC)` tourne à chaque trigger, dry run inclus — seules les
étapes de publication (le push, la Release et le rafraîchissement de baseline
cli) sont conditionnées.** L'échange de token est ce qui valide la
policy trusted-publishing, donc un dry run échoue (rouge) quand la policy ou
`NUGET_USER` est absent. Il génère une clé à usage unique que le dry run ne
dépense jamais. Nécessite une policy trusted-publishing sur nuget.org et le
Expand All @@ -121,5 +145,7 @@ suivants est délibéré :
le guide opérationnel.
- [`release-dryrun`](release-dryrun.fr.md) — la répétition automatique et sans
effet de bord qui tourne sur chaque PR et push, partageant le même `pack.sh`.
- [Ajouter un train de release](../AddingAReleaseTrain.fr.md) — comment les
préfixes de tag, les scopes et les branches de pack des trains sont câblés.
- La section **Supply chain** du README documente comment un consommateur vérifie
la provenance et le SBOM que ce workflow produit.