From ea2458f74f9afa68a7b1a3a75f90541a88c8691f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 23:16:27 +0000 Subject: [PATCH 1/3] docs: add maintainer reference for the CI/CD workflows Add a maintainer-facing reference under maintainers/workflows/ documenting every GitHub Actions workflow. A main index (README) presents the cross-cutting conventions (SHA-pinned actions, least-privilege permissions, per-job timeouts, concurrency, weekly scheduled scans, fork-safety, and required checks as the real gate) and links, grouped by theme, to one page per workflow. Each per-workflow page follows a common template: what it is for (pedagogical), when it runs, how it runs, permissions and security, and a "handle with care" section capturing the non-obvious decisions that must not be changed without understanding why. The release and analyzers pages link to the existing ReleaseDryRun guide and ADR 0001 rather than duplicating them. All pages are provided in English (canonical) and French, per CONTRIBUTING.md and the repository language policy. --- maintainers/workflows/README.en.md | 97 +++++++++++++++++ maintainers/workflows/README.fr.md | 101 ++++++++++++++++++ maintainers/workflows/analyzers.en.md | 86 +++++++++++++++ maintainers/workflows/analyzers.fr.md | 90 ++++++++++++++++ maintainers/workflows/ci.en.md | 64 +++++++++++ maintainers/workflows/ci.fr.md | 67 ++++++++++++ maintainers/workflows/codeql.en.md | 60 +++++++++++ maintainers/workflows/codeql.fr.md | 62 +++++++++++ maintainers/workflows/commit-lint.en.md | 60 +++++++++++ maintainers/workflows/commit-lint.fr.md | 61 +++++++++++ .../workflows/dependabot-automerge.en.md | 61 +++++++++++ .../workflows/dependabot-automerge.fr.md | 65 +++++++++++ maintainers/workflows/dependency-review.en.md | 57 ++++++++++ maintainers/workflows/dependency-review.fr.md | 61 +++++++++++ maintainers/workflows/release-dryrun.en.md | 66 ++++++++++++ maintainers/workflows/release-dryrun.fr.md | 68 ++++++++++++ maintainers/workflows/release.en.md | 90 ++++++++++++++++ maintainers/workflows/release.fr.md | 95 ++++++++++++++++ maintainers/workflows/scorecard.en.md | 74 +++++++++++++ maintainers/workflows/scorecard.fr.md | 76 +++++++++++++ maintainers/workflows/sonar.en.md | 63 +++++++++++ maintainers/workflows/sonar.fr.md | 67 ++++++++++++ 22 files changed, 1591 insertions(+) create mode 100644 maintainers/workflows/README.en.md create mode 100644 maintainers/workflows/README.fr.md create mode 100644 maintainers/workflows/analyzers.en.md create mode 100644 maintainers/workflows/analyzers.fr.md create mode 100644 maintainers/workflows/ci.en.md create mode 100644 maintainers/workflows/ci.fr.md create mode 100644 maintainers/workflows/codeql.en.md create mode 100644 maintainers/workflows/codeql.fr.md create mode 100644 maintainers/workflows/commit-lint.en.md create mode 100644 maintainers/workflows/commit-lint.fr.md create mode 100644 maintainers/workflows/dependabot-automerge.en.md create mode 100644 maintainers/workflows/dependabot-automerge.fr.md create mode 100644 maintainers/workflows/dependency-review.en.md create mode 100644 maintainers/workflows/dependency-review.fr.md create mode 100644 maintainers/workflows/release-dryrun.en.md create mode 100644 maintainers/workflows/release-dryrun.fr.md create mode 100644 maintainers/workflows/release.en.md create mode 100644 maintainers/workflows/release.fr.md create mode 100644 maintainers/workflows/scorecard.en.md create mode 100644 maintainers/workflows/scorecard.fr.md create mode 100644 maintainers/workflows/sonar.en.md create mode 100644 maintainers/workflows/sonar.fr.md diff --git a/maintainers/workflows/README.en.md b/maintainers/workflows/README.en.md new file mode 100644 index 0000000..9353b1f --- /dev/null +++ b/maintainers/workflows/README.en.md @@ -0,0 +1,97 @@ +# CI/CD workflow reference + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](README.fr.md) + +> Maintainer documentation. This describes the GitHub Actions workflows that +> build, check, and publish FirstClassErrors. It is **not** part of the +> library's user documentation under `doc/`. + +## What this is + +Each workflow under [`.github/workflows/`](../../.github/workflows/) carries a +fair amount of intent that is easy to break by "cleaning it up": a permission +that is narrow on purpose, a step ordering that guards a specific failure, a +version that is frozen for a product reason. The workflow files themselves hold +the line-by-line rationale in comments — those comments are the source of truth +closest to the code. **These pages are the pedagogical layer above them:** what +each workflow is *for*, when and how it runs, and the handful of things you must +not change without understanding why. + +Read the page for a workflow before you touch it. When the page and the YAML +disagree, the YAML wins — and the page should be corrected. + +## The cross-cutting conventions + +A few decisions are shared by (almost) every workflow. They are documented once +here instead of being repeated on every page. + +- **Actions are pinned by commit SHA, not by tag.** A tag like `@v4` can be + moved by its owner to point at new code; a 40-hex SHA cannot. Every `uses:` + therefore pins a SHA with the human-readable tag in a trailing comment + (`# v4`). When you bump an action, change **both**. Dependabot's + `github-actions` ecosystem proposes these bumps. +- **`permissions:` start read-only and widen per job.** The workflow-level block + is the least privilege the workflow needs (usually `contents: read`); a job + that must write something (upload SARIF, publish a release, enable auto-merge) + re-declares a `permissions:` block that adds *only* that scope. Never widen the + top-level block to satisfy one job. +- **Every job sets `timeout-minutes`.** The GitHub default is six hours; a hung + step would otherwise hold a runner for that long. Each cap is set a few times + the observed run time, noted in a comment next to it. +- **`concurrency` cancels superseded runs.** Pushing twice to the same branch or + PR cancels the in-flight run. The one exception is `release`, which sets + `cancel-in-progress: false` — you never want to cancel a half-finished + publish. +- **Security scanners also run weekly on a `schedule`.** `codeql` and + `scorecard` re-run against unchanged code so newly shipped queries/checks are + applied even when nothing was pushed. +- **Forks cannot read secrets.** Workflows that need a secret (e.g. `sonar`) + detect a fork PR and skip rather than fail; GitHub does not expose repository + secrets to a PR raised from a fork. +- **Required checks are the real gate.** Several workflows (`dependency-review`, + `dependabot-automerge`) only *signal* or *enable* — they merge nothing on their + own. What actually blocks a bad merge is the branch-protection / ruleset + configuration on `main` marking these checks as **required**. That is a + repository setting, not something a workflow can enforce for itself. + +## The workflows + +### Build & quality + +| Workflow | Purpose | +| --- | --- | +| [`ci`](ci.en.md) | Build and test the whole solution on Linux and Windows, with coverage. The primary gate. | +| [`sonar`](sonar.en.md) | SonarQube Cloud analysis — quality gate and coverage reporting. | +| [`analyzers`](analyzers.en.md) | Dogfood the bundled Roslyn analyzers, including on the oldest supported compiler (the Roslyn floor). | +| [`commit-lint`](commit-lint.en.md) | Enforce the Conventional Commits convention on every PR commit, using the same script as the local hook. | + +### Security & supply chain + +| Workflow | Purpose | +| --- | --- | +| [`codeql`](codeql.en.md) | GitHub CodeQL static analysis for C#, results on the code-scanning dashboard. | +| [`dependency-review`](dependency-review.en.md) | Block a PR that introduces a known-vulnerable dependency. | +| [`scorecard`](scorecard.en.md) | OpenSSF Scorecard — scores the repo's security posture and powers the README badge. | + +### Release + +| Workflow | Purpose | +| --- | --- | +| [`release`](release.en.md) | Build, attest, and publish the NuGet packages on a version tag (with a manual dry run). | +| [`release-dryrun`](release-dryrun.en.md) | Continuously rehearse the side-effect-free part of the release (pack + SBOM) on every PR and push. | + +### Dependency maintenance + +| Workflow | Purpose | +| --- | --- | +| [`dependabot-automerge`](dependabot-automerge.en.md) | Enable auto-merge on Dependabot patch/minor updates; leave majors for a human. | + +## Related maintainer docs + +- [Release dry run (manual)](../ReleaseDryRun.en.md) — the operational guide to + the manual `release` dispatch dry run. +- [ADR 0001 — Lock the analyzer Roslyn floor](../adr/0001-lock-the-analyzer-roslyn-floor.md) + — why the analyzer's Roslyn version is frozen, which the `analyzers` workflow + enforces. +- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) — the commit and PR conventions the + `commit-lint` workflow checks. diff --git a/maintainers/workflows/README.fr.md b/maintainers/workflows/README.fr.md new file mode 100644 index 0000000..be02852 --- /dev/null +++ b/maintainers/workflows/README.fr.md @@ -0,0 +1,101 @@ +# Référence des workflows CI/CD + +🌍 🇬🇧 [English](README.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur. Elle décrit les workflows GitHub Actions qui +> construisent, vérifient et publient FirstClassErrors. Elle ne fait **pas** +> partie de la documentation utilisateur de la librairie, sous `doc/`. + +## De quoi il s'agit + +Chaque workflow sous [`.github/workflows/`](../../.github/workflows/) porte une +intention qu'il est facile de casser en « faisant le ménage » : une permission +volontairement étroite, un ordre d'étapes qui protège d'une panne précise, une +version gelée pour une raison produit. Les fichiers de workflow eux-mêmes +contiennent le raisonnement ligne à ligne dans leurs commentaires — ces +commentaires sont la source de vérité la plus proche du code. **Ces pages sont la +couche pédagogique au-dessus :** à quoi sert chaque workflow, quand et comment il +s'exécute, et les quelques points qu'il ne faut pas modifier sans en comprendre +la raison. + +Lisez la page d'un workflow avant d'y toucher. Si la page et le YAML se +contredisent, c'est le YAML qui fait foi — et la page doit être corrigée. + +## Les conventions transverses + +Quelques décisions sont partagées par (presque) tous les workflows. Elles sont +documentées une seule fois ici plutôt que répétées sur chaque page. + +- **Les actions sont épinglées par SHA de commit, pas par tag.** Un tag comme + `@v4` peut être déplacé par son propriétaire vers du nouveau code ; un SHA de + 40 caractères hexadécimaux, non. Chaque `uses:` épingle donc un SHA avec le tag + lisible en commentaire de fin (`# v4`). Quand vous montez une action, changez + **les deux**. L'écosystème `github-actions` de Dependabot propose ces montées. +- **Les `permissions:` partent en lecture seule et s'élargissent par job.** Le + bloc au niveau du workflow est le moindre privilège nécessaire (en général + `contents: read`) ; un job qui doit écrire quelque chose (uploader un SARIF, + publier une release, activer l'auto-merge) redéclare un bloc `permissions:` qui + ajoute *uniquement* ce périmètre. N'élargissez jamais le bloc de haut niveau + pour satisfaire un seul job. +- **Chaque job fixe `timeout-minutes`.** Le défaut GitHub est de six heures ; une + étape bloquée retiendrait sinon un runner tout ce temps. Chaque plafond est + fixé à quelques fois le temps observé, noté en commentaire à côté. +- **`concurrency` annule les runs remplacés.** Pousser deux fois sur la même + branche ou PR annule le run en cours. La seule exception est `release`, qui met + `cancel-in-progress: false` — on ne veut jamais annuler une publication à + moitié faite. +- **Les scanners de sécurité tournent aussi chaque semaine via `schedule`.** + `codeql` et `scorecard` se relancent sur du code inchangé pour que les + requêtes/checks nouvellement livrés soient appliqués même sans push. +- **Les forks ne peuvent pas lire les secrets.** Les workflows qui ont besoin + d'un secret (p. ex. `sonar`) détectent une PR issue d'un fork et s'abstiennent + au lieu d'échouer ; GitHub n'expose pas les secrets du dépôt à une PR ouverte + depuis un fork. +- **Ce sont les checks *required* qui font barrage.** Plusieurs workflows + (`dependency-review`, `dependabot-automerge`) ne font que *signaler* ou + *activer* — ils ne mergent rien seuls. Ce qui bloque réellement un mauvais + merge, c'est la configuration de protection de branche / ruleset sur `main` qui + marque ces checks comme **required**. C'est un réglage de dépôt, pas quelque + chose qu'un workflow peut imposer pour lui-même. + +## Les workflows + +### Build & qualité + +| Workflow | Rôle | +| --- | --- | +| [`ci`](ci.fr.md) | Construit et teste toute la solution sous Linux et Windows, avec couverture. Le barrage principal. | +| [`sonar`](sonar.fr.md) | Analyse SonarQube Cloud — quality gate et remontée de couverture. | +| [`analyzers`](analyzers.fr.md) | Dogfood des analyzers Roslyn embarqués, y compris sur le plus vieux compilateur supporté (le floor Roslyn). | +| [`commit-lint`](commit-lint.fr.md) | Impose la convention Conventional Commits sur chaque commit de PR, via le même script que le hook local. | + +### Sécurité & chaîne d'approvisionnement + +| Workflow | Rôle | +| --- | --- | +| [`codeql`](codeql.fr.md) | Analyse statique GitHub CodeQL pour C#, résultats sur le tableau de bord code-scanning. | +| [`dependency-review`](dependency-review.fr.md) | Bloque une PR qui introduit une dépendance vulnérable connue. | +| [`scorecard`](scorecard.fr.md) | OpenSSF Scorecard — note la posture de sécurité du dépôt et alimente le badge du README. | + +### Release + +| Workflow | Rôle | +| --- | --- | +| [`release`](release.fr.md) | Construit, atteste et publie les packages NuGet sur un tag de version (avec un dry run manuel). | +| [`release-dryrun`](release-dryrun.fr.md) | Répète en continu la partie sans effet de bord de la release (pack + SBOM) sur chaque PR et push. | + +### Maintenance des dépendances + +| Workflow | Rôle | +| --- | --- | +| [`dependabot-automerge`](dependabot-automerge.fr.md) | Active l'auto-merge sur les mises à jour patch/minor de Dependabot ; laisse les majeures à un humain. | + +## Docs mainteneur en rapport + +- [Répétition de release à blanc (« dry run » manuel)](../ReleaseDryRun.fr.md) — + le guide opérationnel du dry run manuel via le dispatch `release`. +- [ADR 0001 — Verrouiller le floor Roslyn de l'analyzer](../adr/0001-lock-the-analyzer-roslyn-floor.md) + — pourquoi la version de Roslyn de l'analyzer est gelée, ce que le workflow + `analyzers` fait respecter. *(Rédigé en anglais.)* +- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) — les conventions de commit et de PR + que le workflow `commit-lint` vérifie. diff --git a/maintainers/workflows/analyzers.en.md b/maintainers/workflows/analyzers.en.md new file mode 100644 index 0000000..203e14d --- /dev/null +++ b/maintainers/workflows/analyzers.en.md @@ -0,0 +1,86 @@ +# `analyzers` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](analyzers.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/analyzers.yml`](../../.github/workflows/analyzers.yml) + +## What it is for + +The library ships Roslyn analyzers (`FCExxx`) **bundled inside the NuGet +package**. This workflow proves two things that ordinary CI does not: + +1. **Dogfood** — the analyzers actually run and flag nothing they shouldn't when + built against the sample project that references them. +2. **Floor check** — the *shipped* analyzer, packed exactly as a consumer + receives it, **loads and runs under the oldest supported compiler** (Roslyn + 4.8.0 == .NET 8 SDK / Visual Studio 2022 17.8). + +The floor check exists because a bundled analyzer compiled against a newer +Roslyn fails to load (`CS8032`) on older SDKs/IDEs — silently degrading the +product for those users. See +[ADR 0001 — Lock the analyzer Roslyn floor](../adr/0001-lock-the-analyzer-roslyn-floor.md) +for the full rationale; this workflow is its CI enforcement. + +## When it runs + +- On every **push to `main`**, **pull request targeting `main`**, and on demand + via **`workflow_dispatch`**. + +## How it runs + +Two jobs: + +- **`dogfood`** — builds `FirstClassErrors.Usage` with the analyzers wired in + (`OutputItemType=Analyzer`); fails on any `Error`-severity `FCExxx` + diagnostic. (The analyzer *unit tests* run inside [`ci`](ci.en.md), which + builds and tests the whole solution.) +- **`floor`** — the real contract test: + 1. Set up **two SDKs**: the release SDK (10.0.x, the one `release` packs with) + and the floor SDK (8.0.100, Roslyn 4.8). + 2. **Pack** `FirstClassErrors` under the *release* SDK — the exact artifact a + consumer restores. + 3. **Consume** that package from `tools/floor-check/`, whose nested + `global.json` pins the build to the *floor* SDK. + 4. **Prove the analyzer loaded** by grepping the `ReportAnalyzer` log for a + fully-qualified analyzer *type*. + +## Permissions & security + +`contents: read` only — it builds and packs locally, publishes nothing. + +## Handle with care + +This job is dense because each line closes a specific hole. Before editing it, +read the comments in the YAML — they are the source of truth. The traps: + +- **The pack runs under the release SDK, the consume under the floor SDK.** + Packing under the floor SDK would test an analyzer nobody ships and would pin + the library to C# 12. The split is the whole point. +- **`FLOORCHECK_VERSION` carries a `run_number.run_attempt` suffix** so every run + produces a version NuGet has never cached, forcing the consume step to restore + *this run's* freshly packed `.nupkg` instead of a stale copy. `FloorCheck.csproj` + pins that **exact** version (not a float), so it can never silently resolve a + future stable `FirstClassErrors` from nuget.org. +- **SDK selection is CWD-based.** The consume step picks the floor SDK *because* + it runs from `tools/floor-check/` with a nested `global.json` (`rollForward: + disable`). Move the step out of that directory and it silently builds on the + wrong SDK. +- **`ReportAnalyzer=true` + `-v detailed` + `--no-incremental` are all + required** to make Roslyn's per-analyzer table reach the log. Drop any one and + the "prove it loaded" grep has nothing to match. +- **The grep matches an analyzer *type* (`…SomethingAnalyzer`), not the assembly + name.** The assembly name appears in ordinary build lines even when the + analyzer never loaded; only the type appears in the `ReportAnalyzer` table. + A never-loaded analyzer would otherwise leave the build green. + +**To raise the floor deliberately**, follow the procedure in ADR 0001 — it is a +product decision, not a routine bump (which is why Dependabot is configured to +ignore `Microsoft.CodeAnalysis.*`). + +## Related + +- [ADR 0001 — Lock the analyzer Roslyn floor](../adr/0001-lock-the-analyzer-roslyn-floor.md) +- [`ci`](ci.en.md) — runs the analyzer unit-test suite as part of the full solution. diff --git a/maintainers/workflows/analyzers.fr.md b/maintainers/workflows/analyzers.fr.md new file mode 100644 index 0000000..0e0188d --- /dev/null +++ b/maintainers/workflows/analyzers.fr.md @@ -0,0 +1,90 @@ +# Workflow `analyzers` + +🌍 🇬🇧 [English](analyzers.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/analyzers.yml`](../../.github/workflows/analyzers.yml) + +## À quoi il sert + +La librairie livre des analyzers Roslyn (`FCExxx`) **embarqués dans le package +NuGet**. Ce workflow prouve deux choses que la CI ordinaire ne prouve pas : + +1. **Dogfood** — les analyzers tournent réellement et ne signalent rien + d'indu quand on construit le projet d'exemple qui les référence. +2. **Floor check** — l'analyzer *tel que livré*, packagé exactement comme un + consommateur le reçoit, **se charge et s'exécute sous le plus vieux + compilateur supporté** (Roslyn 4.8.0 == SDK .NET 8 / Visual Studio 2022 17.8). + +Le floor check existe parce qu'un analyzer embarqué compilé contre un Roslyn plus +récent ne se charge pas (`CS8032`) sur les SDK/IDE plus anciens — dégradant +silencieusement le produit pour ces utilisateurs. Voir +[ADR 0001 — Verrouiller le floor Roslyn de l'analyzer](../adr/0001-lock-the-analyzer-roslyn-floor.md) +pour le raisonnement complet *(rédigé en anglais)* ; ce workflow en est la mise +en application côté CI. + +## Quand il s'exécute + +- À chaque **push sur `main`**, **pull request visant `main`**, et à la demande + via **`workflow_dispatch`**. + +## Comment il s'exécute + +Deux jobs : + +- **`dogfood`** — construit `FirstClassErrors.Usage` avec les analyzers branchés + (`OutputItemType=Analyzer`) ; échoue sur tout diagnostic `FCExxx` de sévérité + `Error`. (Les *tests unitaires* de l'analyzer tournent dans [`ci`](ci.fr.md), + qui build et teste toute la solution.) +- **`floor`** — le vrai test de contrat : + 1. Installer **deux SDK** : le SDK de release (10.0.x, celui avec lequel + `release` packe) et le SDK floor (8.0.100, Roslyn 4.8). + 2. **Packer** `FirstClassErrors` sous le SDK *de release* — l'artefact exact + qu'un consommateur restaure. + 3. **Consommer** ce package depuis `tools/floor-check/`, dont le `global.json` + imbriqué épingle le build au SDK *floor*. + 4. **Prouver que l'analyzer s'est chargé** en grep-ant le log `ReportAnalyzer` + à la recherche d'un *type* d'analyzer pleinement qualifié. + +## Permissions & sécurité + +`contents: read` seulement — il build et packe localement, ne publie rien. + +## À manipuler avec précaution + +Ce job est dense parce que chaque ligne bouche un trou précis. Avant de +l'éditer, lisez les commentaires du YAML — ils font foi. Les pièges : + +- **Le pack tourne sous le SDK de release, la consommation sous le SDK floor.** + Packer sous le SDK floor testerait un analyzer que personne ne livre et + épinglerait la librairie à C# 12. Ce découpage est tout l'intérêt. +- **`FLOORCHECK_VERSION` porte un suffixe `run_number.run_attempt`** pour que + chaque run produise une version que NuGet n'a jamais mise en cache, forçant + l'étape de consommation à restaurer le `.nupkg` fraîchement packé *de ce run* + plutôt qu'une copie périmée. `FloorCheck.csproj` épingle cette version + **exacte** (pas un flottant), donc il ne peut jamais résoudre silencieusement + un futur `FirstClassErrors` stable depuis nuget.org. +- **La sélection du SDK dépend du répertoire courant.** L'étape de consommation + choisit le SDK floor *parce qu*'elle tourne depuis `tools/floor-check/` avec un + `global.json` imbriqué (`rollForward: disable`). Sortez l'étape de ce + répertoire et elle build silencieusement sur le mauvais SDK. +- **`ReportAnalyzer=true` + `-v detailed` + `--no-incremental` sont tous + requis** pour faire remonter la table par-analyzer de Roslyn dans le log. + Retirez-en un et le grep « prouve qu'il s'est chargé » n'a plus rien à + matcher. +- **Le grep matche un *type* d'analyzer (`…QuelqueChoseAnalyzer`), pas le nom de + l'assembly.** Le nom de l'assembly apparaît dans des lignes de build ordinaires + même si l'analyzer ne s'est jamais chargé ; seul le type apparaît dans la table + `ReportAnalyzer`. Un analyzer jamais chargé laisserait sinon le build vert. + +**Pour relever le floor délibérément**, suivez la procédure de l'ADR 0001 — +c'est une décision produit, pas une montée de routine (raison pour laquelle +Dependabot est configuré pour ignorer `Microsoft.CodeAnalysis.*`). + +## En rapport + +- [ADR 0001 — Verrouiller le floor Roslyn de l'analyzer](../adr/0001-lock-the-analyzer-roslyn-floor.md) +- [`ci`](ci.fr.md) — exécute la suite de tests unitaires de l'analyzer dans le + cadre de la solution complète. diff --git a/maintainers/workflows/ci.en.md b/maintainers/workflows/ci.en.md new file mode 100644 index 0000000..f193277 --- /dev/null +++ b/maintainers/workflows/ci.en.md @@ -0,0 +1,64 @@ +# `ci` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](ci.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml) + +## What it is for + +`ci` is the primary gate: it builds the whole solution and runs the entire test +suite, on **both Linux and Windows**, and collects code coverage. If a change +breaks the build or a test on either platform, this is where it shows. + +The cross-platform matrix is not ceremony. The documentation generator spawns +its worker as a **separate process** and manipulates file-system paths, so the +solution genuinely behaves differently on Windows and Linux — both legs are +exercised on purpose. + +## When it runs + +- On every **push to `main`**. +- On every **pull request targeting `main`**. +- On demand via **`workflow_dispatch`**. + +## How it runs + +One job, `build-test`, on a `[ubuntu-latest, windows-latest]` matrix: + +1. Checkout, then set up the .NET SDK. +2. `dotnet restore` → `dotnet build -c Release` → `dotnet test -c Release`. +3. Tests collect OpenCover coverage via `coverlet.collector`, configured by + [`coverage.runsettings`](../../coverage.runsettings), into + `artifacts/coverage//coverage.opencover.xml`. +4. The coverage report is uploaded as a per-OS artifact. + +## Permissions & security + +`contents: read` only — the workflow just checks out and builds. It stores no +secrets and needs no write scope. + +## Handle with care + +- **`fail-fast: false` is deliberate.** It forces both matrix legs to run to + completion so a platform-specific failure is always visible; do not remove it + to "save minutes". +- **The coverage artifact name is per-OS** (`coverage-${{ matrix.os }}`). + Uploading two matrix legs under the same artifact name would clash — keep the + name parameterised. +- **`if-no-files-found: error`** is intentional: a silent "no coverage produced" + would let a broken coverage setup pass unnoticed. It should fail. +- **This is the enforcement point for the warning ratchet.** The + `TreatWarningsAsErrors` / `MSBuildTreatWarningsAsErrors` promotion (see + [`Directory.Build.props`](../../Directory.Build.props)) is scoped to CI and is + *enforced here*, on both OS legs. The `sonar` workflow deliberately disables it + for its own analysis build — so `ci`, not `sonar`, is the gate that must stay + green on warnings. + +## Related + +- [`sonar`](sonar.en.md) reuses the same `coverage.runsettings` so its coverage + report matches this one. +- [`analyzers`](analyzers.en.md) covers the analyzer dogfood that `ci` does not. diff --git a/maintainers/workflows/ci.fr.md b/maintainers/workflows/ci.fr.md new file mode 100644 index 0000000..3c0b267 --- /dev/null +++ b/maintainers/workflows/ci.fr.md @@ -0,0 +1,67 @@ +# Workflow `ci` + +🌍 🇬🇧 [English](ci.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml) + +## À quoi il sert + +`ci` est le barrage principal : il construit toute la solution et exécute toute +la suite de tests, sous **Linux et Windows**, et collecte la couverture de code. +Si un changement casse le build ou un test sur l'une des deux plateformes, c'est +ici que ça se voit. + +La matrice multiplateforme n'est pas décorative. Le générateur de documentation +lance son worker dans un **processus séparé** et manipule des chemins de système +de fichiers ; la solution se comporte donc réellement différemment sous Windows +et Linux — les deux branches sont exercées à dessein. + +## Quand il s'exécute + +- À chaque **push sur `main`**. +- À chaque **pull request visant `main`**. +- À la demande via **`workflow_dispatch`**. + +## Comment il s'exécute + +Un seul job, `build-test`, sur une matrice `[ubuntu-latest, windows-latest]` : + +1. Checkout, puis installation du SDK .NET. +2. `dotnet restore` → `dotnet build -c Release` → `dotnet test -c Release`. +3. Les tests collectent la couverture OpenCover via `coverlet.collector`, + configuré par [`coverage.runsettings`](../../coverage.runsettings), dans + `artifacts/coverage//coverage.opencover.xml`. +4. Le rapport de couverture est uploadé en artefact, un par OS. + +## Permissions & sécurité + +`contents: read` seulement — le workflow ne fait que checkout et build. Il ne +stocke aucun secret et n'a besoin d'aucun périmètre en écriture. + +## À manipuler avec précaution + +- **`fail-fast: false` est volontaire.** Il force les deux branches de la matrice + à aller au bout, pour qu'une panne propre à une plateforme soit toujours + visible ; ne le retirez pas pour « gagner des minutes ». +- **Le nom de l'artefact de couverture est par OS** (`coverage-${{ matrix.os }}`). + Uploader les deux branches sous le même nom d'artefact provoquerait une + collision — gardez le nom paramétré. +- **`if-no-files-found: error`** est intentionnel : un « aucune couverture + produite » silencieux laisserait passer une configuration de couverture cassée. + Ça doit échouer. +- **C'est ici que le cliquet de warnings est imposé.** La promotion + `TreatWarningsAsErrors` / `MSBuildTreatWarningsAsErrors` (voir + [`Directory.Build.props`](../../Directory.Build.props)) est cantonnée à la CI et + *imposée ici*, sur les deux branches OS. Le workflow `sonar` la désactive + volontairement pour son propre build d'analyse — c'est donc `ci`, et non + `sonar`, le barrage qui doit rester vert sur les warnings. + +## En rapport + +- [`sonar`](sonar.fr.md) réutilise le même `coverage.runsettings` pour que son + rapport de couverture corresponde à celui-ci. +- [`analyzers`](analyzers.fr.md) couvre le dogfood des analyzers que `ci` ne fait + pas. diff --git a/maintainers/workflows/codeql.en.md b/maintainers/workflows/codeql.en.md new file mode 100644 index 0000000..32ad644 --- /dev/null +++ b/maintainers/workflows/codeql.en.md @@ -0,0 +1,60 @@ +# `codeql` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](codeql.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/codeql.yml`](../../.github/workflows/codeql.yml) + +## What it is for + +`codeql` runs GitHub's CodeQL static analysis over the C# code and uploads the +findings to the repository's **code-scanning dashboard** (the Security tab). It +is the semantic security scanner: it looks for vulnerability patterns +(injection, unsafe deserialization, etc.) rather than style issues. It powers +the `codeql` badge in the README. + +## When it runs + +- On every **push to `main`** and **pull request targeting `main`**. +- **Weekly** on a `schedule` (`cron: 17 6 * * 1`), so newly shipped CodeQL + queries run against unchanged code. +- On demand via **`workflow_dispatch`**. + +## How it runs + +One job, `analyze`: + +1. Checkout. +2. **Initialize CodeQL** for `csharp` with **`build-mode: none`** (buildless + extraction). +3. **Perform CodeQL analysis** and upload the results. + +## Permissions & security + +The workflow defaults to `contents: read`; the `analyze` job adds +`security-events: write` (to upload results to the dashboard) and `actions: +read` (needed by the action on private repositories, harmless on public ones). + +## Handle with care + +- **`build-mode: none` is a deliberate choice.** Buildless extraction needs no + .NET SDK or build step, and it sidesteps compiler-tracing problems on a very + new SDK. If you ever want deeper data-flow analysis, switch to `manual` and add + an explicit `dotnet build` step — do not expect `autobuild` to be a free + upgrade. +- **The two CodeQL steps must stay on the same action SHA.** `init` and + `analyze` come from `github/codeql-action`; bump them together (and the + `upload-sarif` reference in [`scorecard`](scorecard.en.md), which uses the same + action family). +- **The weekly `schedule` is not redundant.** It applies query updates to code + that has not changed; removing it means new query classes are never run until + the next push. + +## Related + +- [`scorecard`](scorecard.en.md) — also uploads SARIF to code-scanning, via the + same `github/codeql-action/upload-sarif` action. +- [`dependency-review`](dependency-review.en.md) — the dependency-side security + gate, complementary to this code-side one. diff --git a/maintainers/workflows/codeql.fr.md b/maintainers/workflows/codeql.fr.md new file mode 100644 index 0000000..287f17c --- /dev/null +++ b/maintainers/workflows/codeql.fr.md @@ -0,0 +1,62 @@ +# Workflow `codeql` + +🌍 🇬🇧 [English](codeql.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/codeql.yml`](../../.github/workflows/codeql.yml) + +## À quoi il sert + +`codeql` exécute l'analyse statique CodeQL de GitHub sur le code C# et remonte +les résultats au **tableau de bord code-scanning** du dépôt (l'onglet Security). +C'est le scanner de sécurité sémantique : il cherche des motifs de vulnérabilité +(injection, désérialisation non sûre, etc.) plutôt que des problèmes de style. Il +alimente le badge `codeql` du README. + +## Quand il s'exécute + +- À chaque **push sur `main`** et **pull request visant `main`**. +- **Chaque semaine** via `schedule` (`cron: 17 6 * * 1`), pour que les requêtes + CodeQL nouvellement livrées tournent sur du code inchangé. +- À la demande via **`workflow_dispatch`**. + +## Comment il s'exécute + +Un seul job, `analyze` : + +1. Checkout. +2. **Initialize CodeQL** pour `csharp` avec **`build-mode: none`** (extraction + sans build). +3. **Perform CodeQL analysis** et upload des résultats. + +## Permissions & sécurité + +Le workflow est par défaut en `contents: read` ; le job `analyze` ajoute +`security-events: write` (pour uploader les résultats au tableau de bord) et +`actions: read` (nécessaire à l'action sur les dépôts privés, inoffensif sur les +publics). + +## À manipuler avec précaution + +- **`build-mode: none` est un choix délibéré.** L'extraction sans build ne + demande aucun SDK .NET ni étape de build, et elle contourne les problèmes de + traçage du compilateur sur un SDK très récent. Si un jour vous voulez une + analyse de flux de données plus poussée, passez à `manual` et ajoutez une étape + `dotnet build` explicite — ne comptez pas sur `autobuild` comme sur une + amélioration gratuite. +- **Les deux étapes CodeQL doivent rester sur le même SHA d'action.** `init` et + `analyze` viennent de `github/codeql-action` ; montez-les ensemble (ainsi que + la référence `upload-sarif` dans [`scorecard`](scorecard.fr.md), qui utilise la + même famille d'action). +- **Le `schedule` hebdomadaire n'est pas redondant.** Il applique les mises à + jour de requêtes à du code qui n'a pas changé ; le retirer signifie que les + nouvelles classes de requêtes ne tournent qu'au prochain push. + +## En rapport + +- [`scorecard`](scorecard.fr.md) — uploade aussi du SARIF vers code-scanning, via + la même action `github/codeql-action/upload-sarif`. +- [`dependency-review`](dependency-review.fr.md) — le barrage de sécurité côté + dépendances, complémentaire de celui-ci côté code. diff --git a/maintainers/workflows/commit-lint.en.md b/maintainers/workflows/commit-lint.en.md new file mode 100644 index 0000000..3df7d35 --- /dev/null +++ b/maintainers/workflows/commit-lint.en.md @@ -0,0 +1,60 @@ +# `commit-lint` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](commit-lint.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/commit-lint.yml`](../../.github/workflows/commit-lint.yml) + +## What it is for + +It enforces the repository's commit-message convention (Conventional Commits, as +specified in [`CONTRIBUTING.md`](../../CONTRIBUTING.md)) on **every non-merge +commit of a pull request**. It is the server-side backstop for the local +`commit-msg` hook: a contributor who bypasses the hook with +`git commit --no-verify` is still caught here. + +Both the hook and this workflow call the **same script**, +[`tools/commit-lint/lint-commit-message.sh`](../../tools/commit-lint/lint-commit-message.sh), +so there is exactly one definition of "a valid commit message" and CI can never +drift from the local check. + +## When it runs + +- On every **pull request** (any base branch). + +## How it runs + +One job, `Conventional commits`: + +1. Checkout with **`fetch-depth: 0`** — the full history is needed to enumerate + the PR's commits. +2. Enumerate non-merge commits in `base..head` (`git rev-list --no-merges`) and + pipe each message through the lint script with `--ci`. Merge commits are + generated by GitHub and are exempt. +3. If any commit fails, the job prints which ones and exits non-zero, pointing + the contributor at an interactive rebase to fix them. + +## Permissions & security + +`contents: read` only. + +## Handle with care + +- **The script is shared with the local hook — change the rule in one place.** + If you adjust the accepted types/scopes, edit + `tools/commit-lint/lint-commit-message.sh`; both the hook and this workflow + pick it up. Do not encode a second, divergent rule in the workflow. +- **`--no-merges` is intentional.** GitHub's own merge commits do not follow the + convention and must stay exempt. +- **`fetch-depth: 0` is required.** A shallow checkout would not contain the + PR's commit range, so the lint would silently pass over nothing. +- **This check only helps if it is required.** As with the other quality checks, + it blocks a merge only when branch protection on `main` marks it **required**. + +## Related + +- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) — the commit and PR conventions this + workflow enforces. Enable the local hook once per clone with + `git config core.hooksPath .githooks`. diff --git a/maintainers/workflows/commit-lint.fr.md b/maintainers/workflows/commit-lint.fr.md new file mode 100644 index 0000000..8058921 --- /dev/null +++ b/maintainers/workflows/commit-lint.fr.md @@ -0,0 +1,61 @@ +# Workflow `commit-lint` + +🌍 🇬🇧 [English](commit-lint.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/commit-lint.yml`](../../.github/workflows/commit-lint.yml) + +## À quoi il sert + +Il impose la convention de message de commit du dépôt (Conventional Commits, +telle que spécifiée dans [`CONTRIBUTING.md`](../../CONTRIBUTING.md)) sur **chaque +commit non-merge d'une pull request**. C'est le filet de sécurité côté serveur du +hook local `commit-msg` : un contributeur qui contourne le hook avec +`git commit --no-verify` est quand même rattrapé ici. + +Le hook et ce workflow appellent le **même script**, +[`tools/commit-lint/lint-commit-message.sh`](../../tools/commit-lint/lint-commit-message.sh), +donc il existe exactement une définition de « message de commit valide » et la CI +ne peut jamais diverger du contrôle local. + +## Quand il s'exécute + +- À chaque **pull request** (quelle que soit la branche de base). + +## Comment il s'exécute + +Un seul job, `Conventional commits` : + +1. Checkout avec **`fetch-depth: 0`** — l'historique complet est nécessaire pour + énumérer les commits de la PR. +2. Énumérer les commits non-merge de `base..head` (`git rev-list --no-merges`) et + passer chaque message au script de lint avec `--ci`. Les commits de merge sont + générés par GitHub et sont exemptés. +3. Si un commit échoue, le job affiche lesquels et sort en non-zéro, en orientant + le contributeur vers un rebase interactif pour les corriger. + +## Permissions & sécurité + +`contents: read` seulement. + +## À manipuler avec précaution + +- **Le script est partagé avec le hook local — changez la règle à un seul + endroit.** Si vous ajustez les types/scopes acceptés, éditez + `tools/commit-lint/lint-commit-message.sh` ; le hook comme ce workflow le + reprennent. N'encodez pas une seconde règle divergente dans le workflow. +- **`--no-merges` est intentionnel.** Les commits de merge de GitHub ne suivent + pas la convention et doivent rester exemptés. +- **`fetch-depth: 0` est requis.** Un checkout superficiel ne contiendrait pas la + plage de commits de la PR ; le lint passerait alors silencieusement sur rien. +- **Ce check n'aide que s'il est *required*.** Comme les autres checks de + qualité, il ne bloque un merge que lorsque la protection de branche sur `main` + le marque **required**. + +## En rapport + +- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) — les conventions de commit et de PR + que ce workflow impose. Activez le hook local une fois par clone avec + `git config core.hooksPath .githooks`. diff --git a/maintainers/workflows/dependabot-automerge.en.md b/maintainers/workflows/dependabot-automerge.en.md new file mode 100644 index 0000000..c0b3b75 --- /dev/null +++ b/maintainers/workflows/dependabot-automerge.en.md @@ -0,0 +1,61 @@ +# `dependabot-automerge` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](dependabot-automerge.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/dependabot-automerge.yml`](../../.github/workflows/dependabot-automerge.yml) + +## What it is for + +For Dependabot pull requests, this workflow **enables GitHub auto-merge on patch +and minor updates**, so they merge on their own once the required checks pass. +**Major** updates are deliberately left untouched, to wait for human review. It +is the low-friction lane of the dependency-update policy: routine bumps do not +need a human, risky ones do. + +The Dependabot configuration itself (which ecosystems, schedule, ignored +packages) lives in [`.github/dependabot.yml`](../../.github/dependabot.yml), not +here. + +## When it runs + +- On every **pull request targeting `main`**, but the job is gated on + `github.actor == 'dependabot[bot]'`, so it acts only on Dependabot's PRs. + +## How it runs + +One job, `automerge`: + +1. `dependabot/fetch-metadata` reads the update type (patch / minor / major). +2. For **patch or minor** updates, `gh pr merge --auto` enables auto-merge. Major + updates fall through the condition and stay open. + +## Permissions & security + +Workflow default `contents: read`; the job widens to `contents: write` and +`pull-requests: write` — the scopes needed to enable auto-merge on the PR. + +## Handle with care + +- **This workflow only *enables* auto-merge; it does not decide when to merge.** + GitHub merges the PR only once the branch's **required** status checks pass. + **Without a branch-protection rule on `main` that marks the CI checks + required, auto-merge would merge immediately** — before CI. The required checks + are the safety gate, not this workflow. This is the single most important thing + to understand before relying on it. +- **The `major` exclusion is intentional.** Only `semver-patch` and + `semver-minor` get auto-merge; majors are left for a human because they are the + ones most likely to break. Do not broaden the condition to majors. +- **The actor guard matters.** `if: github.actor == 'dependabot[bot]'` keeps the + elevated `contents: write` / `pull-requests: write` path from running on + human PRs. + +## Related + +- [`.github/dependabot.yml`](../../.github/dependabot.yml) — what Dependabot + updates and what it ignores (e.g. the frozen `Microsoft.CodeAnalysis.*`; see + [`analyzers`](analyzers.en.md)). +- [`dependency-review`](dependency-review.en.md) — the PR-time vulnerability gate + that a Dependabot PR also passes through. diff --git a/maintainers/workflows/dependabot-automerge.fr.md b/maintainers/workflows/dependabot-automerge.fr.md new file mode 100644 index 0000000..abdd0d0 --- /dev/null +++ b/maintainers/workflows/dependabot-automerge.fr.md @@ -0,0 +1,65 @@ +# Workflow `dependabot-automerge` + +🌍 🇬🇧 [English](dependabot-automerge.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/dependabot-automerge.yml`](../../.github/workflows/dependabot-automerge.yml) + +## À quoi il sert + +Pour les pull requests de Dependabot, ce workflow **active l'auto-merge de GitHub +sur les mises à jour patch et minor**, pour qu'elles se mergent d'elles-mêmes une +fois les checks required passés. Les mises à jour **majeures** sont +volontairement laissées telles quelles, en attente d'une revue humaine. C'est la +voie à faible friction de la politique de mise à jour des dépendances : les +montées de routine n'ont pas besoin d'un humain, les risquées si. + +La configuration de Dependabot elle-même (quels écosystèmes, planning, packages +ignorés) vit dans [`.github/dependabot.yml`](../../.github/dependabot.yml), pas +ici. + +## Quand il s'exécute + +- À chaque **pull request visant `main`**, mais le job est conditionné à + `github.actor == 'dependabot[bot]'`, donc il n'agit que sur les PR de + Dependabot. + +## Comment il s'exécute + +Un seul job, `automerge` : + +1. `dependabot/fetch-metadata` lit le type de mise à jour (patch / minor / major). +2. Pour les mises à jour **patch ou minor**, `gh pr merge --auto` active + l'auto-merge. Les majeures ne passent pas la condition et restent ouvertes. + +## Permissions & sécurité + +Défaut du workflow `contents: read` ; le job élargit à `contents: write` et +`pull-requests: write` — les périmètres nécessaires pour activer l'auto-merge sur +la PR. + +## À manipuler avec précaution + +- **Ce workflow ne fait qu'*activer* l'auto-merge ; il ne décide pas quand + merger.** GitHub ne merge la PR qu'une fois les checks de statut **required** + de la branche passés. **Sans une règle de protection de branche sur `main` qui + marque les checks CI comme required, l'auto-merge mergerait immédiatement** — + avant la CI. Les checks required sont le garde-fou de sécurité, pas ce workflow. + C'est le point le plus important à comprendre avant de s'y fier. +- **L'exclusion des `major` est intentionnelle.** Seuls `semver-patch` et + `semver-minor` obtiennent l'auto-merge ; les majeures sont laissées à un humain + parce que ce sont elles qui risquent le plus de casser. N'élargissez pas la + condition aux majeures. +- **Le garde-fou sur l'acteur compte.** `if: github.actor == 'dependabot[bot]'` + empêche le chemin élevé `contents: write` / `pull-requests: write` de tourner + sur des PR humaines. + +## En rapport + +- [`.github/dependabot.yml`](../../.github/dependabot.yml) — ce que Dependabot met + à jour et ce qu'il ignore (p. ex. les `Microsoft.CodeAnalysis.*` gelés ; voir + [`analyzers`](analyzers.fr.md)). +- [`dependency-review`](dependency-review.fr.md) — le barrage de vulnérabilité au + moment de la PR, par lequel une PR Dependabot passe aussi. diff --git a/maintainers/workflows/dependency-review.en.md b/maintainers/workflows/dependency-review.en.md new file mode 100644 index 0000000..32cb732 --- /dev/null +++ b/maintainers/workflows/dependency-review.en.md @@ -0,0 +1,57 @@ +# `dependency-review` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](dependency-review.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/dependency-review.yml`](../../.github/workflows/dependency-review.yml) + +## What it is for + +`dependency-review` blocks a pull request that **introduces a known-vulnerable +dependency**. It diffs the base-vs-head dependency graph and fails the PR if a +change pulls in a package with an advisory at or above the configured severity. + +It is the **PR-time** complement to Dependabot: Dependabot only reacts *after* a +vulnerable dependency is already on `main`, whereas this catches the problem at +the moment a PR would add it — which is exactly when it is cheapest to fix. + +## When it runs + +- On every **pull request targeting `main`**. + +## How it runs + +One job, `review`: checkout, then run +`actions/dependency-review-action` with `fail-on-severity: moderate`. + +## Permissions & security + +`contents: read` only — the action reads the repository's dependency graph. It +posts **no** PR comment (that would need `pull-requests: write`); the failed +check is the signal. + +## Handle with care + +- **It requires the repository's Dependency graph to be enabled.** This is a + GitHub repository setting, not something the workflow can turn on. If it is + off, the action fails with *"Dependency review is not supported on this + repository… ensure that Dependency graph is enabled"* — that is a + configuration error, not a workflow bug. (On a private repo it also needs + Advanced Security.) +- **It only sees changes the PR makes.** A CVE published overnight against an + *existing* dependency does not fail this check — that stays a warning through + the NuGet audit (see [`Directory.Build.props`](../../Directory.Build.props)). + This workflow blocks only at the point of introduction, on purpose. +- **`fail-on-severity: moderate` is the tuning knob.** Lower it to `low` to be + stricter, raise it to `high` to be laxer. Because it only inspects the PR's own + dependency changes, `moderate` is a genuine gate rather than noise. +- **It gates nothing unless it is required.** As with the other checks, it must + be marked **required** in branch protection on `main` to actually block a merge. + +## Related + +- [`codeql`](codeql.en.md) — the code-side security scanner. +- [`dependabot-automerge`](dependabot-automerge.en.md) — the post-merge + dependency-update side. diff --git a/maintainers/workflows/dependency-review.fr.md b/maintainers/workflows/dependency-review.fr.md new file mode 100644 index 0000000..03c9225 --- /dev/null +++ b/maintainers/workflows/dependency-review.fr.md @@ -0,0 +1,61 @@ +# Workflow `dependency-review` + +🌍 🇬🇧 [English](dependency-review.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/dependency-review.yml`](../../.github/workflows/dependency-review.yml) + +## À quoi il sert + +`dependency-review` bloque une pull request qui **introduit une dépendance +vulnérable connue**. Il compare le graphe de dépendances base-vs-head et fait +échouer la PR si un changement tire un package portant un avis de sécurité à la +sévérité configurée ou au-dessus. + +C'est le complément **au moment de la PR** de Dependabot : Dependabot ne réagit +qu'*après* qu'une dépendance vulnérable est déjà sur `main`, alors que celui-ci +attrape le problème au moment où une PR l'ajouterait — exactement quand il est le +moins coûteux à corriger. + +## Quand il s'exécute + +- À chaque **pull request visant `main`**. + +## Comment il s'exécute + +Un seul job, `review` : checkout, puis exécution de +`actions/dependency-review-action` avec `fail-on-severity: moderate`. + +## Permissions & sécurité + +`contents: read` seulement — l'action lit le graphe de dépendances du dépôt. Elle +ne poste **aucun** commentaire de PR (cela demanderait `pull-requests: write`) ; +le check en échec est le signal. + +## À manipuler avec précaution + +- **Il exige que le Dependency graph du dépôt soit activé.** C'est un réglage de + dépôt GitHub, pas quelque chose que le workflow peut activer. S'il est éteint, + l'action échoue avec *« Dependency review is not supported on this repository… + ensure that Dependency graph is enabled »* — c'est une erreur de configuration, + pas un bug de workflow. (Sur un dépôt privé, il faut aussi Advanced Security.) +- **Il ne voit que les changements apportés par la PR.** Une CVE publiée pendant + la nuit contre une dépendance *existante* ne fait pas échouer ce check — cela + reste un warning via l'audit NuGet (voir + [`Directory.Build.props`](../../Directory.Build.props)). Ce workflow ne bloque + qu'au point d'introduction, à dessein. +- **`fail-on-severity: moderate` est le bouton de réglage.** Abaissez-le à `low` + pour être plus strict, montez-le à `high` pour être plus laxiste. Comme il + n'inspecte que les changements de dépendances de la PR, `moderate` est un vrai + barrage plutôt que du bruit. +- **Il ne fait barrage que s'il est *required*.** Comme les autres checks, il + doit être marqué **required** dans la protection de branche sur `main` pour + réellement bloquer un merge. + +## En rapport + +- [`codeql`](codeql.fr.md) — le scanner de sécurité côté code. +- [`dependabot-automerge`](dependabot-automerge.fr.md) — le versant mise à jour de + dépendances après merge. diff --git a/maintainers/workflows/release-dryrun.en.md b/maintainers/workflows/release-dryrun.en.md new file mode 100644 index 0000000..ed5b6d4 --- /dev/null +++ b/maintainers/workflows/release-dryrun.en.md @@ -0,0 +1,66 @@ +# `release-dryrun` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](release-dryrun.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/release-dryrun.yml`](../../.github/workflows/release-dryrun.yml) + +## What it is for + +`release-dryrun` continuously rehearses the **side-effect-free** portion of the +release — build, pack, and embed the SBOM for the two published projects — on +every push and pull request. Because [`release`](release.en.md) itself runs only +on a tag or a manual dispatch, its packaging path would otherwise be exercised +for the first time in production, on a tag, once. This catches packaging/SBOM +regressions in ordinary CI instead. + +It is the automatic counterpart to the **manual** dry run documented in +[Release dry run (manual)](../ReleaseDryRun.en.md): this one deliberately leaves +out the attestation/OIDC path (which has side effects), the manual one adds it. + +## When it runs + +- On every **push to `main`**, **pull request targeting `main`**, and on demand + via **`workflow_dispatch`**. + +## How it runs + +One job, `pack`: checkout → setup the release SDK (10.0.x) → `dotnet build` → +**`tools/packaging/pack.sh`**, which packs the two published projects with their +SBOM and asserts the SBOM is actually embedded. + +## Permissions & security + +`contents: read` only. It stops before every step that has a side effect, so it +needs none of `release`'s write scopes: + +- **no provenance attestation** — that writes a permanent public record + (Sigstore/Rekor + the attestation store); kept to the manual dry run and real + releases; +- **no NuGet login / push** — nuget.org has no "dry-run push", so the publish + stays release-only; +- **no GitHub Release** — no tag or release is ever created here. + +## Handle with care + +- **It shares `tools/packaging/pack.sh` with `release`, and that is the point.** + There is exactly one definition of "pack the release artifacts", so this + rehearsal cannot drift from the real release. Do not inline a separate pack + command here — change `pack.sh` and both follow. +- **Keep it side-effect-free.** The value of this workflow is that it runs on + *every* PR with no attestation and no publish. Do not add the attestation or a + login step here; those belong to the manual dry run in `release`, which runs + deliberately, not on every push. +- **The `DRYRUN_VERSION` is a throwaway.** Nothing is published, so the exact + value only has to be a valid SemVer the pack accepts. The real version comes + from the tag and is validated in `release`. +- **This job's unique contribution is the *packaging*.** The unit/integration + tests run in [`ci`](ci.en.md); do not duplicate them here. + +## Related + +- [`release`](release.en.md) — the real publish path, sharing the same `pack.sh`. +- [Release dry run (manual)](../ReleaseDryRun.en.md) — the manual dry run that + additionally rehearses the attestation/OIDC path. diff --git a/maintainers/workflows/release-dryrun.fr.md b/maintainers/workflows/release-dryrun.fr.md new file mode 100644 index 0000000..aed1d5c --- /dev/null +++ b/maintainers/workflows/release-dryrun.fr.md @@ -0,0 +1,68 @@ +# Workflow `release-dryrun` + +🌍 🇬🇧 [English](release-dryrun.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/release-dryrun.yml`](../../.github/workflows/release-dryrun.yml) + +## À quoi il sert + +`release-dryrun` répète en continu la portion **sans effet de bord** de la +release — build, pack et embarquement du SBOM pour les deux projets publiés — sur +chaque push et pull request. Comme [`release`](release.fr.md) ne tourne lui-même +que sur un tag ou un dispatch manuel, son chemin d'empaquetage serait autrement +exercé pour la première fois en production, sur un tag, une seule fois. Celui-ci +attrape les régressions d'empaquetage/SBOM dans la CI ordinaire à la place. + +C'est le pendant automatique du dry run **manuel** documenté dans +[Répétition de release à blanc (« dry run » manuel)](../ReleaseDryRun.fr.md) : +celui-ci laisse volontairement de côté le chemin attestation/OIDC (qui a des +effets de bord), le manuel l'ajoute. + +## Quand il s'exécute + +- À chaque **push sur `main`**, **pull request visant `main`**, et à la demande + via **`workflow_dispatch`**. + +## Comment il s'exécute + +Un seul job, `pack` : checkout → setup du SDK de release (10.0.x) → +`dotnet build` → **`tools/packaging/pack.sh`**, qui packe les deux projets +publiés avec leur SBOM et assert que le SBOM est bien embarqué. + +## Permissions & sécurité + +`contents: read` seulement. Il s'arrête avant chaque étape qui a un effet de +bord, donc il n'a besoin d'aucun des périmètres en écriture de `release` : + +- **pas d'attestation de provenance** — cela écrit un enregistrement public + permanent (Sigstore/Rekor + le magasin d'attestations) ; réservé au dry run + manuel et aux vraies releases ; +- **pas de login / push NuGet** — nuget.org n'a pas de « push à blanc », donc la + publication reste réservée à la release ; +- **pas de GitHub Release** — aucun tag ni release n'est jamais créé ici. + +## À manipuler avec précaution + +- **Il partage `tools/packaging/pack.sh` avec `release`, et c'est le but.** Il + existe exactement une définition de « packer les artefacts de release », donc + cette répétition ne peut pas diverger de la vraie release. N'écrivez pas une + commande de pack séparée ici — changez `pack.sh` et les deux suivent. +- **Gardez-le sans effet de bord.** L'intérêt de ce workflow est qu'il tourne sur + *chaque* PR sans attestation ni publication. N'ajoutez pas ici l'attestation ni + une étape de login ; celles-ci appartiennent au dry run manuel de `release`, + qui tourne délibérément, pas à chaque push. +- **Le `DRYRUN_VERSION` est jetable.** Rien n'est publié, donc la valeur exacte + doit seulement être un SemVer valide que le pack accepte. La vraie version vient + du tag et est validée dans `release`. +- **La contribution unique de ce job est l'*empaquetage*.** Les tests + unitaires/intégration tournent dans [`ci`](ci.fr.md) ; ne les dupliquez pas ici. + +## En rapport + +- [`release`](release.fr.md) — le vrai chemin de publication, partageant le même + `pack.sh`. +- [Répétition de release à blanc (« dry run » manuel)](../ReleaseDryRun.fr.md) — + le dry run manuel qui répète en plus le chemin attestation/OIDC. diff --git a/maintainers/workflows/release.en.md b/maintainers/workflows/release.en.md new file mode 100644 index 0000000..67e18ec --- /dev/null +++ b/maintainers/workflows/release.en.md @@ -0,0 +1,90 @@ +# `release` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](release.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/release.yml`](../../.github/workflows/release.yml) + +## What it is for + +`release` builds, attests, and publishes the NuGet packages. It is the only +workflow whose full path is otherwise **never exercised before a real tag** — +version resolution, pack, SBOM, OIDC and attestation permissions all run for the +first time in production conditions. To make that testable, it doubles as a +**manual dry run** that runs everything up to and including the attestation while +skipping the publish steps. + +> For the operational side of the manual dry run — how to launch it, what it +> touches, and when to use it — see the dedicated guide: +> **[Release dry run (manual)](../ReleaseDryRun.en.md)**. This page covers what +> the workflow *is* and the traps in its structure. + +## 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 + `true`**). A manual run publishes only if `dry_run` is explicitly unticked. + +## 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. + +## Permissions & security + +The workflow needs three write scopes: `contents: write` (create the Release and +upload assets), `id-token: write` (mint the short-lived NuGet key via trusted +publishing), and `attestations: write` (store the signed provenance). No +long-lived `NUGET_API_KEY` is stored. + +## Handle with care + +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` + 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 + `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. +- **`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. +- **The attestation deliberately does not match the nuget.org copy.** nuget.org + repository-signs every upload (adds `.signature.p7s` inside the `.nupkg`), + changing the checksum. The attested bytes are therefore published as **GitHub + 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 + 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 + (the profile **username**, not the email). +- **The Release step pins `--target "$GITHUB_SHA"`.** On `workflow_dispatch` the + tag does not exist yet and `gh` would otherwise create it from the default + branch's latest state; pinning the SHA ties the tag, source archive and + attested packages to the commit this job actually built. The `|| … upload + --clobber` fallback keeps a re-run idempotent. +- **`concurrency` sets `cancel-in-progress: false`.** Never cancel a + half-finished publish. + +## Related + +- [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`. +- The README's **Supply chain** section documents how a consumer verifies the + provenance and SBOM this workflow produces. diff --git a/maintainers/workflows/release.fr.md b/maintainers/workflows/release.fr.md new file mode 100644 index 0000000..8f8d392 --- /dev/null +++ b/maintainers/workflows/release.fr.md @@ -0,0 +1,95 @@ +# Workflow `release` + +🌍 🇬🇧 [English](release.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/release.yml`](../../.github/workflows/release.yml) + +## À quoi il sert + +`release` construit, atteste et publie les packages NuGet. C'est le seul workflow +dont le chemin complet n'est autrement **jamais exercé avant un vrai tag** — +résolution de version, pack, SBOM, OIDC et permissions d'attestation ne tournent +pour la première fois qu'en conditions de production. Pour rendre tout cela +testable, il joue aussi le rôle de **dry run manuel** qui exécute tout jusqu'à +l'attestation incluse tout en sautant les étapes de publication. + +> Pour le versant opérationnel du dry run manuel — comment le lancer, ce qu'il +> touche et quand l'utiliser — voir le guide dédié : +> **[Répétition de release à blanc (« dry run » manuel)](../ReleaseDryRun.fr.md)**. +> Cette page couvre ce que le workflow *est* et les pièges de sa structure. + +## 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 + `true`**). Un run manuel ne publie que si `dry_run` est explicitement décoché. + +## 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 +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. + +## Permissions & sécurité + +Le workflow a besoin de trois périmètres en écriture : `contents: write` (créer la +Release et uploader les assets), `id-token: write` (générer la clé NuGet +éphémère via trusted publishing) et `attestations: write` (stocker la provenance +signée). Aucun `NUGET_API_KEY` durable n'est stocké. + +## À manipuler avec précaution + +Ce workflow encode plusieurs corrections durement acquises. Chacun des points +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:` + 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é + transformerait la release en no-op vert qui ne publie rien. Échouer bruyamment + est le but. +- **`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 + run sert à détecter. +- **L'attestation ne correspond volontairement pas à la copie de nuget.org.** + nuget.org re-signe chaque upload côté dépôt (ajoute un `.signature.p7s` dans le + `.nupkg`), changeant le checksum. Les octets attestés sont donc publiés comme + **assets de GitHub Release**, et les consommateurs vérifient la provenance + 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 + 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 + secret `NUGET_USER` (le **nom d'utilisateur** du profil, pas l'e-mail). +- **L'étape Release épingle `--target "$GITHUB_SHA"`.** Sur `workflow_dispatch` le + tag n'existe pas encore et `gh` le créerait sinon depuis le dernier état de la + branche par défaut ; épingler le SHA lie le tag, l'archive source et les + packages attestés au commit que ce job a réellement construit. Le repli + `|| … upload --clobber` garde un re-run idempotent. +- **`concurrency` met `cancel-in-progress: false`.** Ne jamais annuler une + publication à moitié faite. + +## En rapport + +- [Répétition de release à blanc (« dry run » manuel)](../ReleaseDryRun.fr.md) — + 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`. +- La section **Supply chain** du README documente comment un consommateur vérifie + la provenance et le SBOM que ce workflow produit. diff --git a/maintainers/workflows/scorecard.en.md b/maintainers/workflows/scorecard.en.md new file mode 100644 index 0000000..14cd776 --- /dev/null +++ b/maintainers/workflows/scorecard.en.md @@ -0,0 +1,74 @@ +# `scorecard` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](scorecard.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/scorecard.yml`](../../.github/workflows/scorecard.yml) + +## What it is for + +`scorecard` runs [OpenSSF Scorecard](https://securityscorecards.dev), which +scores the repository's **security posture** against a set of automated checks — +pinned actions, token permissions, branch protection, signed releases, +dependency-update tooling, and more. It uploads the findings to the +code-scanning dashboard and **publishes the score to securityscorecards.dev**, +which powers the OpenSSF Scorecard badge in the README. + +Where `codeql` scores the *code* and `dependency-review` scores the +*dependencies*, Scorecard scores the *project's practices* — including several +of the very conventions the other workflows implement. + +## When it runs + +It runs on the **default branch only** — it assesses the repository, not a PR +diff, so there is **no `pull_request` trigger**: + +- On **`branch_protection_rule`** — re-score when a branch-protection rule + changes (this feeds Scorecard's Branch-Protection check). +- **Weekly** on a `schedule` (`cron: 23 5 * * 1`). +- On **push to `main`**. + +## How it runs + +One job, `analysis`: credential-free checkout → run `ossf/scorecard-action` with +`publish_results: true` → upload the SARIF both as a build artifact and to the +code-scanning dashboard. + +## Permissions & security + +Top-level `permissions: read-all`; the job widens only two write scopes: + +- `security-events: write` — upload the SARIF to code-scanning. +- `id-token: write` — publish results to the public OpenSSF API via OIDC; **this + is what enables the badge**. + +The checkout uses **`persist-credentials: false`** — a credential-free checkout +that Scorecard's own Token-Permissions check rewards. + +## Handle with care + +- **It does not run on pull requests, by design.** Do not add a `pull_request` + trigger to "test it on a PR" — Scorecard evaluates the whole repository, and it + needs the default-branch context and the OIDC identity to publish. A PR run + would be meaningless and could not publish. To exercise a change, merge it and + watch the first push-to-`main` run. +- **The badge shows "no data" until the first `main` run publishes.** That is + expected on first setup, not a failure. +- **`publish_results: true` is public-repo-appropriate.** It publishes the score + to securityscorecards.dev for transparency and the badge. Keep it as-is for a + public repo; if the repo ever goes private, drop it (and the badge). +- **The permission comment is load-bearing.** This repo is public, so + `contents: read` / `actions: read` are not needed in the token. The commented + lines in the YAML explain what to **uncomment for a private repository** — do + not delete them. +- **Improving the score is done in the *other* workflows and in repo settings**, + not here. A low Branch-Protection sub-score, for instance, is fixed by marking + checks required on `main`, not by editing this file. + +## Related + +- [`codeql`](codeql.en.md) — shares the `github/codeql-action/upload-sarif` + action used to push results to code-scanning. +- The README badge links to the public viewer at `securityscorecards.dev`. diff --git a/maintainers/workflows/scorecard.fr.md b/maintainers/workflows/scorecard.fr.md new file mode 100644 index 0000000..b3ce880 --- /dev/null +++ b/maintainers/workflows/scorecard.fr.md @@ -0,0 +1,76 @@ +# Workflow `scorecard` + +🌍 🇬🇧 [English](scorecard.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/scorecard.yml`](../../.github/workflows/scorecard.yml) + +## À quoi il sert + +`scorecard` exécute [OpenSSF Scorecard](https://securityscorecards.dev), qui note +la **posture de sécurité** du dépôt selon un ensemble de checks automatisés — +actions épinglées, permissions des tokens, protection de branche, releases +signées, outillage de mise à jour des dépendances, et plus. Il remonte les +résultats au tableau de bord code-scanning et **publie le score sur +securityscorecards.dev**, ce qui alimente le badge OpenSSF Scorecard du README. + +Là où `codeql` note le *code* et `dependency-review` note les *dépendances*, +Scorecard note les *pratiques du projet* — dont plusieurs des conventions mêmes +que les autres workflows mettent en œuvre. + +## Quand il s'exécute + +Il tourne sur la **branche par défaut uniquement** — il évalue le dépôt, pas un +diff de PR, donc il n'y a **pas de trigger `pull_request`** : + +- Sur **`branch_protection_rule`** — re-note quand une règle de protection de + branche change (cela alimente le check Branch-Protection de Scorecard). +- **Chaque semaine** via `schedule` (`cron: 23 5 * * 1`). +- Sur **push vers `main`**. + +## Comment il s'exécute + +Un seul job, `analysis` : checkout sans credentials → exécution de +`ossf/scorecard-action` avec `publish_results: true` → upload du SARIF à la fois +en artefact de build et vers le tableau de bord code-scanning. + +## Permissions & sécurité + +`permissions: read-all` au niveau supérieur ; le job n'élargit que deux +périmètres en écriture : + +- `security-events: write` — uploader le SARIF vers code-scanning. +- `id-token: write` — publier les résultats vers l'API publique OpenSSF via OIDC ; + **c'est ce qui active le badge**. + +Le checkout utilise **`persist-credentials: false`** — un checkout sans +credentials que le propre check Token-Permissions de Scorecard récompense. + +## À manipuler avec précaution + +- **Il ne tourne pas sur les pull requests, à dessein.** N'ajoutez pas de trigger + `pull_request` pour « le tester sur une PR » — Scorecard évalue le dépôt entier, + et il a besoin du contexte de la branche par défaut et de l'identité OIDC pour + publier. Un run de PR serait dénué de sens et ne pourrait pas publier. Pour + exercer un changement, mergez-le et observez le premier run push-sur-`main`. +- **Le badge affiche « no data » jusqu'au premier run sur `main` qui publie.** + C'est attendu lors de la mise en place initiale, pas un échec. +- **`publish_results: true` convient à un dépôt public.** Il publie le score sur + securityscorecards.dev par transparence et pour le badge. Laissez-le tel quel + pour un dépôt public ; si le dépôt passait un jour en privé, retirez-le (et le + badge). +- **Le commentaire sur les permissions porte du sens.** Ce dépôt est public, donc + `contents: read` / `actions: read` ne sont pas nécessaires dans le token. Les + lignes commentées dans le YAML expliquent ce qu'il faut **décommenter pour un + dépôt privé** — ne les supprimez pas. +- **On améliore le score dans les *autres* workflows et dans les réglages du + dépôt**, pas ici. Un sous-score Branch-Protection faible, par exemple, se + corrige en marquant des checks required sur `main`, pas en éditant ce fichier. + +## En rapport + +- [`codeql`](codeql.fr.md) — partage l'action `github/codeql-action/upload-sarif` + utilisée pour pousser les résultats vers code-scanning. +- Le badge du README pointe vers le visualiseur public sur `securityscorecards.dev`. diff --git a/maintainers/workflows/sonar.en.md b/maintainers/workflows/sonar.en.md new file mode 100644 index 0000000..c96c5e9 --- /dev/null +++ b/maintainers/workflows/sonar.en.md @@ -0,0 +1,63 @@ +# `sonar` workflow + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](sonar.fr.md) + +> Maintainer documentation — part of the [workflow reference](README.en.md). +> Not part of the user documentation under `doc/`. + +**Workflow file:** [`.github/workflows/sonar.yml`](../../.github/workflows/sonar.yml) + +## What it is for + +`sonar` runs the SonarQube Cloud analysis: it feeds the **Quality Gate** and the +**coverage** metric shown on the two SonarCloud badges in the README. It is the +static-analysis-plus-coverage view of the codebase, hosted off GitHub. + +## When it runs + +- On every **push to `main`**. +- On every **pull request targeting `main`** — **except PRs from forks** (see + below). +- On demand via **`workflow_dispatch`**. + +## How it runs + +One job, `analyze`, on Linux: + +1. Checkout with **`fetch-depth: 0`** — full history, so Sonar can attribute + issues via `git blame` and distinguish new code from old. +2. Set up .NET **and Java 17** — the SonarScanner for .NET runs on the JVM. +3. `dotnet-sonarscanner begin` → **build** → test with coverage → + `dotnet-sonarscanner end`. + +## Permissions & security + +`contents: read` only. PR decoration (the inline Sonar comments) is delivered by +the **SonarQube Cloud GitHub App**, not by this workflow's token, so no +`pull-requests: write` is needed here. The analysis authenticates with the +`SONAR_TOKEN` secret. + +## Handle with care + +- **The build must sit *between* `begin` and `end`.** The scanner hooks MSBuild + to observe the compilation; it cannot analyse a pre-built or `--no-build` + output. Do not reorder these steps or add `--no-build` to the analysis build. +- **The analysis build disables the warning ratchet on purpose.** It passes + `-p:TreatWarningsAsErrors=false -p:MSBuildTreatWarningsAsErrors=false`. The + scanner needs the compilation to **complete** so it can collect the + `SonarAnalyzer` diagnostics and upload them in `end`; a Sonar-rule warning + promoted to an error would fail the build before results are reported. The + ratchet stays enforced by [`ci`](ci.en.md) on both OS legs — that is the gate, + this analysis leg is not. +- **The fork guard is required, not optional.** The + `if: … head.repo.full_name == github.repository` condition skips the analysis + on PRs from forks, because a fork PR cannot read `SONAR_TOKEN` and would fail + on a missing secret rather than a real problem. Branches inside this repository + (the normal contributor flow) run normally. +- **`fetch-depth: 0` matters.** A shallow checkout would break Sonar's new-code + detection and blame attribution. + +## Related + +- [`ci`](ci.en.md) — produces the same OpenCover coverage shape via the shared + `coverage.runsettings`, and is where the warning ratchet is actually enforced. diff --git a/maintainers/workflows/sonar.fr.md b/maintainers/workflows/sonar.fr.md new file mode 100644 index 0000000..c7727de --- /dev/null +++ b/maintainers/workflows/sonar.fr.md @@ -0,0 +1,67 @@ +# Workflow `sonar` + +🌍 🇬🇧 [English](sonar.en.md) · 🇫🇷 Français (ce fichier) + +> Documentation mainteneur — fait partie de la [référence des workflows](README.fr.md). +> Ne fait pas partie de la documentation utilisateur sous `doc/`. + +**Fichier du workflow :** [`.github/workflows/sonar.yml`](../../.github/workflows/sonar.yml) + +## À quoi il sert + +`sonar` exécute l'analyse SonarQube Cloud : il alimente le **Quality Gate** et la +métrique de **couverture** affichés par les deux badges SonarCloud du README. +C'est la vue analyse-statique-plus-couverture du code, hébergée hors de GitHub. + +## Quand il s'exécute + +- À chaque **push sur `main`**. +- À chaque **pull request visant `main`** — **sauf les PR issues de forks** (voir + plus bas). +- À la demande via **`workflow_dispatch`**. + +## Comment il s'exécute + +Un seul job, `analyze`, sous Linux : + +1. Checkout avec **`fetch-depth: 0`** — historique complet, pour que Sonar puisse + attribuer les problèmes via `git blame` et distinguer le code neuf de + l'ancien. +2. Installation de .NET **et de Java 17** — le SonarScanner for .NET tourne sur la + JVM. +3. `dotnet-sonarscanner begin` → **build** → test avec couverture → + `dotnet-sonarscanner end`. + +## Permissions & sécurité + +`contents: read` seulement. La décoration des PR (les commentaires Sonar en +ligne) est livrée par la **GitHub App SonarQube Cloud**, pas par le token de ce +workflow, donc aucun `pull-requests: write` n'est requis ici. L'analyse +s'authentifie avec le secret `SONAR_TOKEN`. + +## À manipuler avec précaution + +- **Le build doit se trouver *entre* `begin` et `end`.** Le scanner s'accroche à + MSBuild pour observer la compilation ; il ne peut pas analyser une sortie + pré-construite ou `--no-build`. Ne réordonnez pas ces étapes et n'ajoutez pas + `--no-build` au build d'analyse. +- **Le build d'analyse désactive volontairement le cliquet de warnings.** Il + passe `-p:TreatWarningsAsErrors=false -p:MSBuildTreatWarningsAsErrors=false`. Le + scanner a besoin que la compilation **aille au bout** pour collecter les + diagnostics `SonarAnalyzer` et les uploader dans `end` ; un warning de règle + Sonar promu en erreur ferait échouer le build avant que les résultats ne soient + remontés. Le cliquet reste imposé par [`ci`](ci.fr.md) sur les deux branches OS + — c'est ça le barrage, pas cette branche d'analyse. +- **Le garde-fou fork est nécessaire, pas optionnel.** La condition + `if: … head.repo.full_name == github.repository` saute l'analyse sur les PR de + forks, parce qu'une PR de fork ne peut pas lire `SONAR_TOKEN` et échouerait sur + un secret absent plutôt que sur un vrai problème. Les branches internes au dépôt + (le flux contributeur normal) tournent normalement. +- **`fetch-depth: 0` compte.** Un checkout superficiel casserait la détection de + code neuf et l'attribution par blame de Sonar. + +## En rapport + +- [`ci`](ci.fr.md) — produit la même forme de couverture OpenCover via le + `coverage.runsettings` partagé, et c'est là que le cliquet de warnings est + réellement imposé. From e27818308dc00250deb70a00dec0ea3929389672 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 23:27:01 +0000 Subject: [PATCH 2/3] docs: integrate the workflow reference into maintainers/ Wire the new workflow pages into the maintainer documentation so no page is orphaned: - Add a maintainers/ landing index (README.md + README.fr.md) that GitHub auto-renders, listing the workflow reference, the release dry-run runbook, and the ADR log. - Rename the workflow index from README.en.md to README.md so it auto-renders when browsing maintainers/workflows/, and update the inbound links. - Add breadcrumbs from the workflow reference up to the maintainers index, and reciprocal links from the ReleaseDryRun runbook to the release and release-dryrun reference pages (plus a language switcher on the runbook). No content in the ADR is changed; it stays a standalone decision record, now linked from the index and the analyzers page. --- maintainers/README.fr.md | 43 +++++++++++++++++++ maintainers/README.md | 41 ++++++++++++++++++ maintainers/ReleaseDryRun.en.md | 14 ++++++ maintainers/ReleaseDryRun.fr.md | 15 +++++++ maintainers/workflows/README.fr.md | 4 +- .../workflows/{README.en.md => README.md} | 2 + maintainers/workflows/analyzers.en.md | 2 +- maintainers/workflows/ci.en.md | 2 +- maintainers/workflows/codeql.en.md | 2 +- maintainers/workflows/commit-lint.en.md | 2 +- .../workflows/dependabot-automerge.en.md | 2 +- maintainers/workflows/dependency-review.en.md | 2 +- maintainers/workflows/release-dryrun.en.md | 2 +- maintainers/workflows/release.en.md | 2 +- maintainers/workflows/scorecard.en.md | 2 +- maintainers/workflows/sonar.en.md | 2 +- 16 files changed, 128 insertions(+), 11 deletions(-) create mode 100644 maintainers/README.fr.md create mode 100644 maintainers/README.md rename maintainers/workflows/{README.en.md => README.md} (98%) diff --git a/maintainers/README.fr.md b/maintainers/README.fr.md new file mode 100644 index 0000000..70f92c1 --- /dev/null +++ b/maintainers/README.fr.md @@ -0,0 +1,43 @@ +# Documentation mainteneur + +🌍 🇬🇧 [English](README.md) · 🇫🇷 Français (ce fichier) + +> Documentation pour les **mainteneurs et opérateurs** de FirstClassErrors — +> comment le projet est construit, publié et maintenu en bonne santé. Elle ne +> fait **pas** partie de la documentation utilisateur de la librairie, sous +> [`doc/`](../doc/). La version anglaise est canonique ; les pages françaises sont +> tenues à jour en parallèle. + +## Sommaire + +### [Référence des workflows CI/CD](workflows/README.fr.md) + +Une page par workflow GitHub Actions — à quoi il sert, quand et comment il +s'exécute, ses permissions, et les décisions non évidentes qu'il ne faut pas +modifier sans en comprendre la raison. Commencez par l'[index](workflows/README.fr.md) ; +il documente aussi les conventions transverses (actions épinglées par SHA, +permissions au moindre privilège, timeouts par job, checks *required* comme vrai +barrage). + +### [Répétition de release à blanc (« dry run » manuel)](ReleaseDryRun.fr.md) + +Le runbook opérationnel du dry run manuel via le dispatch `release` : comment le +lancer, ce qu'il touche (et ce qu'il ne touche volontairement pas), et quand +l'utiliser. Il complète les pages [`release`](workflows/release.fr.md) et +[`release-dryrun`](workflows/release-dryrun.fr.md) de la référence, qui décrivent +ces workflows structurellement. Aussi en [anglais](ReleaseDryRun.en.md). + +### [Registres de décision d'architecture (ADR)](adr/) + +Des enregistrements datés des décisions importantes — leur contexte, l'option +retenue et les conséquences. Un ADR est un journal historique : il est *superseded* +par un ADR plus récent, pas édité sur place. + +- [ADR 0001 — Verrouiller le floor Roslyn de l'analyzer](adr/0001-lock-the-analyzer-roslyn-floor.md) + — pourquoi la version de Roslyn de l'analyzer est gelée, ce que le workflow + [`analyzers`](workflows/analyzers.fr.md) fait respecter. *(En anglais uniquement.)* + +## En rapport + +- [`CONTRIBUTING.md`](../CONTRIBUTING.md) — conventions de commit et de pull + request (imposées par le workflow [`commit-lint`](workflows/commit-lint.fr.md)). diff --git a/maintainers/README.md b/maintainers/README.md new file mode 100644 index 0000000..31adedc --- /dev/null +++ b/maintainers/README.md @@ -0,0 +1,41 @@ +# Maintainer documentation + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](README.fr.md) + +> Documentation for **maintainers and operators** of FirstClassErrors — how the +> project is built, released, and kept healthy. It is **not** part of the +> library's user documentation, which lives under [`doc/`](../doc/). The English +> version is canonical; the French pages are kept in sync. + +## Contents + +### [CI/CD workflow reference](workflows/README.md) + +A page per GitHub Actions workflow — what it is for, when and how it runs, its +permissions, and the non-obvious decisions you must not change without +understanding why. Start at the [index](workflows/README.md); it also documents +the cross-cutting conventions (SHA-pinned actions, least-privilege permissions, +per-job timeouts, required checks as the real gate). + +### [Release dry run (manual)](ReleaseDryRun.en.md) + +The operational runbook for the manual `release` dispatch dry run: how to launch +it, what it touches (and what it deliberately does not), and when to use it. It +complements the workflow reference's [`release`](workflows/release.en.md) and +[`release-dryrun`](workflows/release-dryrun.en.md) pages, which describe those +workflows structurally. Also in [French](ReleaseDryRun.fr.md). + +### [Architecture Decision Records](adr/) + +Dated records of significant decisions — their context, the option chosen, and +the consequences. An ADR is a historical log: it is superseded by a newer ADR, not +edited in place. + +- [ADR 0001 — Lock the analyzer Roslyn floor](adr/0001-lock-the-analyzer-roslyn-floor.md) + — why the analyzer's Roslyn version is frozen, enforced by the + [`analyzers`](workflows/analyzers.en.md) workflow. *(English only.)* + +## Related + +- [`CONTRIBUTING.md`](../CONTRIBUTING.md) — commit and pull-request conventions + (enforced by the [`commit-lint`](workflows/commit-lint.en.md) workflow). diff --git a/maintainers/ReleaseDryRun.en.md b/maintainers/ReleaseDryRun.en.md index 79e0fa5..9f0d8f2 100644 --- a/maintainers/ReleaseDryRun.en.md +++ b/maintainers/ReleaseDryRun.en.md @@ -1,5 +1,11 @@ # Release dry run (manual) +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](ReleaseDryRun.fr.md) + +↑ Part of the [maintainer documentation](README.md) · see also the workflow +reference for [`release`](workflows/release.en.md) and +[`release-dryrun`](workflows/release-dryrun.en.md). + > Maintainer / operational documentation. This is **not** part of the library's > user documentation under `doc/`. @@ -99,3 +105,11 @@ the **attestation / OIDC** path that the automatic one deliberately leaves out. The actual **push to nuget.org** and the **repository-signed bytes** nuget.org serves cannot be exercised without publishing — nuget.org has no "dry-run push". That final link is only ever validated by a real release. + +## Related + +- [`release`](workflows/release.en.md) — the workflow this rehearses, described + structurally (triggers, jobs, the traps in its design). +- [`release-dryrun`](workflows/release-dryrun.en.md) — the automatic, + side-effect-free dry run that runs on every PR and push. +- [Maintainer documentation](README.md) — the index of all maintainer docs. diff --git a/maintainers/ReleaseDryRun.fr.md b/maintainers/ReleaseDryRun.fr.md index bbbea78..717ddf0 100644 --- a/maintainers/ReleaseDryRun.fr.md +++ b/maintainers/ReleaseDryRun.fr.md @@ -1,5 +1,11 @@ # Répétition de release à blanc (« dry run » manuel) +🌍 🇬🇧 [English](ReleaseDryRun.en.md) · 🇫🇷 Français (ce fichier) + +↑ Fait partie de la [documentation mainteneur](README.fr.md) · voir aussi la +référence des workflows pour [`release`](workflows/release.fr.md) et +[`release-dryrun`](workflows/release-dryrun.fr.md). + > Documentation technique / mainteneur. Elle ne fait **pas** partie de la > documentation utilisateur de la librairie, sous `doc/`. @@ -106,3 +112,12 @@ Le **push réel vers nuget.org** et les **octets re-signés par le dépôt** que nuget.org sert ne peuvent pas être exercés sans publier — nuget.org n'a pas de « push à blanc ». Ce dernier maillon n'est jamais validé que par une vraie release. + +## En rapport + +- [`release`](workflows/release.fr.md) — le workflow que ceci répète, décrit + structurellement (déclencheurs, jobs, pièges de sa conception). +- [`release-dryrun`](workflows/release-dryrun.fr.md) — le dry run automatique et + sans effet de bord qui tourne sur chaque PR et push. +- [Documentation mainteneur](README.fr.md) — l'index de toutes les docs + mainteneur. diff --git a/maintainers/workflows/README.fr.md b/maintainers/workflows/README.fr.md index be02852..f950682 100644 --- a/maintainers/workflows/README.fr.md +++ b/maintainers/workflows/README.fr.md @@ -1,6 +1,8 @@ # Référence des workflows CI/CD -🌍 🇬🇧 [English](README.en.md) · 🇫🇷 Français (ce fichier) +🌍 🇬🇧 [English](README.md) · 🇫🇷 Français (ce fichier) + +↑ Fait partie de la [documentation mainteneur](../README.fr.md). > Documentation mainteneur. Elle décrit les workflows GitHub Actions qui > construisent, vérifient et publient FirstClassErrors. Elle ne fait **pas** diff --git a/maintainers/workflows/README.en.md b/maintainers/workflows/README.md similarity index 98% rename from maintainers/workflows/README.en.md rename to maintainers/workflows/README.md index 9353b1f..5dd4dbd 100644 --- a/maintainers/workflows/README.en.md +++ b/maintainers/workflows/README.md @@ -2,6 +2,8 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](README.fr.md) +↑ Part of the [maintainer documentation](../README.md). + > Maintainer documentation. This describes the GitHub Actions workflows that > build, check, and publish FirstClassErrors. It is **not** part of the > library's user documentation under `doc/`. diff --git a/maintainers/workflows/analyzers.en.md b/maintainers/workflows/analyzers.en.md index 203e14d..8ac4150 100644 --- a/maintainers/workflows/analyzers.en.md +++ b/maintainers/workflows/analyzers.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](analyzers.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/analyzers.yml`](../../.github/workflows/analyzers.yml) diff --git a/maintainers/workflows/ci.en.md b/maintainers/workflows/ci.en.md index f193277..e547720 100644 --- a/maintainers/workflows/ci.en.md +++ b/maintainers/workflows/ci.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](ci.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml) diff --git a/maintainers/workflows/codeql.en.md b/maintainers/workflows/codeql.en.md index 32ad644..cf9ae08 100644 --- a/maintainers/workflows/codeql.en.md +++ b/maintainers/workflows/codeql.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](codeql.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/codeql.yml`](../../.github/workflows/codeql.yml) diff --git a/maintainers/workflows/commit-lint.en.md b/maintainers/workflows/commit-lint.en.md index 3df7d35..8c8e42b 100644 --- a/maintainers/workflows/commit-lint.en.md +++ b/maintainers/workflows/commit-lint.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](commit-lint.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/commit-lint.yml`](../../.github/workflows/commit-lint.yml) diff --git a/maintainers/workflows/dependabot-automerge.en.md b/maintainers/workflows/dependabot-automerge.en.md index c0b3b75..05796be 100644 --- a/maintainers/workflows/dependabot-automerge.en.md +++ b/maintainers/workflows/dependabot-automerge.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](dependabot-automerge.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/dependabot-automerge.yml`](../../.github/workflows/dependabot-automerge.yml) diff --git a/maintainers/workflows/dependency-review.en.md b/maintainers/workflows/dependency-review.en.md index 32cb732..461b061 100644 --- a/maintainers/workflows/dependency-review.en.md +++ b/maintainers/workflows/dependency-review.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](dependency-review.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/dependency-review.yml`](../../.github/workflows/dependency-review.yml) diff --git a/maintainers/workflows/release-dryrun.en.md b/maintainers/workflows/release-dryrun.en.md index ed5b6d4..a7ca3a2 100644 --- a/maintainers/workflows/release-dryrun.en.md +++ b/maintainers/workflows/release-dryrun.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](release-dryrun.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/release-dryrun.yml`](../../.github/workflows/release-dryrun.yml) diff --git a/maintainers/workflows/release.en.md b/maintainers/workflows/release.en.md index 67e18ec..b68522d 100644 --- a/maintainers/workflows/release.en.md +++ b/maintainers/workflows/release.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](release.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/release.yml`](../../.github/workflows/release.yml) diff --git a/maintainers/workflows/scorecard.en.md b/maintainers/workflows/scorecard.en.md index 14cd776..234ac85 100644 --- a/maintainers/workflows/scorecard.en.md +++ b/maintainers/workflows/scorecard.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](scorecard.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/scorecard.yml`](../../.github/workflows/scorecard.yml) diff --git a/maintainers/workflows/sonar.en.md b/maintainers/workflows/sonar.en.md index c96c5e9..9ace285 100644 --- a/maintainers/workflows/sonar.en.md +++ b/maintainers/workflows/sonar.en.md @@ -2,7 +2,7 @@ 🌍 🇬🇧 English (this file) · 🇫🇷 [Français](sonar.fr.md) -> Maintainer documentation — part of the [workflow reference](README.en.md). +> Maintainer documentation — part of the [workflow reference](README.md). > Not part of the user documentation under `doc/`. **Workflow file:** [`.github/workflows/sonar.yml`](../../.github/workflows/sonar.yml) From 87d1ba5dfea3fb9dfac18d4b1e679d7fe40a8b0b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 10 Jul 2026 23:28:51 +0000 Subject: [PATCH 3/3] docs: correct the ADR path in CONTRIBUTING The adoption note pointed ADRs at doc/adr/, but the repository keeps them under maintainers/adr/ (where ADR 0001 lives). Align the guide with the actual location. --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4e59841..61b0680 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -334,7 +334,8 @@ Ordered as the rules are: type, scope, description, body, breaking, issue. ### Adoption This guide is the rule for commits in this repository. Deviating from it -requires a justification — an ADR under `doc/adr/`, or an update to this guide. +requires a justification — an ADR under `maintainers/adr/`, or an update to this +guide. It applies from its adoption on, to every commit created after. Prior history is not rewritten.