diff --git a/doc/handwritten/for-maintainers/workflows/release.en.md b/doc/handwritten/for-maintainers/workflows/release.en.md index 07a1e23..ad2a9d4 100644 --- a/doc/handwritten/for-maintainers/workflows/release.en.md +++ b/doc/handwritten/for-maintainers/workflows/release.en.md @@ -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: @@ -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 @@ -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. @@ -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 @@ -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. diff --git a/doc/handwritten/for-maintainers/workflows/release.fr.md b/doc/handwritten/for-maintainers/workflows/release.fr.md index 68d3ef6..6b586e9 100644 --- a/doc/handwritten/for-maintainers/workflows/release.fr.md +++ b/doc/handwritten/for-maintainers/workflows/release.fr.md @@ -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 : @@ -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é @@ -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 @@ -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 @@ -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.