From aad85a412d72b404737a6aecbbfec90df5f2d34b Mon Sep 17 00:00:00 2001 From: le king fu Date: Sun, 18 Oct 2026 17:28:34 -0400 Subject: [PATCH 1/3] ci: pass --locked to cargo in check-rust and the release builds, pin cargo-xwin (#449) check-rust.yml ran cargo check and cargo test without --locked: a Cargo.lock out of step with Cargo.toml was rewritten in the job's checkout, and the suppression guard, the windows-sys guard and cargo audit then read the rewritten lockfile instead of the committed one. Both now pass --locked, and the windows-sys guard comment says the lockfile it reads is the committed one. release.yml built the shipped binaries the same way. Both npx tauri build calls now end with `-- --locked`, which the Tauri CLI hands to its runner (cargo, or cargo-xwin for Windows, which passes it to cargo build). cargo-xwin, the runner of the Windows build, runs with the updater signing key in its environment and was installed at whatever version crates.io served on the day of the tag. It is pinned at 0.23.1, the version the v0.17.2 release installed (run 570), and its version is printed in the job log. Refs #449 Co-Authored-By: Claude Opus 5.5 --- .forgejo/workflows/check-rust.yml | 19 ++++++++++++++----- .forgejo/workflows/release.yml | 21 ++++++++++++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/.forgejo/workflows/check-rust.yml b/.forgejo/workflows/check-rust.yml index f864f69..ef37ebd 100644 --- a/.forgejo/workflows/check-rust.yml +++ b/.forgejo/workflows/check-rust.yml @@ -126,8 +126,15 @@ jobs: with: workspaces: "src-tauri -> target" + # --locked (#449): a Cargo.lock that no longer matches Cargo.toml (hand + # edit, conflict resolved badly, dependency added without its lockfile) + # fails the job here instead of being rewritten in the job's checkout. + # The guards and the audit below then read the committed lockfile, the + # one release.yml builds the shipped binaries from, also with --locked. + # A deliberate `cargo update` in a PR is not affected: it commits the + # lockfile it writes. - name: cargo check - run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets + run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets --locked # Anti-rot guard for the suppressions in .cargo/audit.toml (#310). Each # entry there is justified by its crate being absent from every shipped @@ -218,9 +225,10 @@ jobs: # and a canary must be read and checked, or the loop's silence proves # nothing. # - # No cargo call, so neither the warm index nor --locked matters here: the - # step reads the lockfile as cargo check left it, which is the committed - # one unless cargo had to update it. + # No cargo call, so neither the warm index nor --locked matters here. The + # lockfile it reads is the committed one: cargo check runs with --locked + # and has already failed the job on a lockfile cargo would have had to + # rewrite (#449). - name: Verify windows-sys pinning run: | set -u @@ -330,8 +338,9 @@ jobs: echo "windows-sys pinning: $checks checks, exit $rc" exit $rc + # --locked, as cargo check above (#449). - name: cargo test - run: cargo test --manifest-path src-tauri/Cargo.toml --all-targets + run: cargo test --manifest-path src-tauri/Cargo.toml --all-targets --locked # Prebuilt binary. `cargo install --locked cargo-audit` recompiled the # tool from source on every single run (~4m40s). No continue-on-error: diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml index 79eb542..f1aa963 100644 --- a/.forgejo/workflows/release.yml +++ b/.forgejo/workflows/release.yml @@ -43,7 +43,15 @@ jobs: run: | apt-get install -y lld llvm clang nsis rustup target add x86_64-pc-windows-msvc - cargo install --locked cargo-xwin + # Pinned (#449): cargo-xwin is the runner of the Windows build below + # and runs with TAURI_SIGNING_PRIVATE_KEY in its environment, so a + # new release of it must be a decision, not whatever crates.io serves + # on the day of the tag. 0.23.1 is the version the v0.17.2 release + # installed unpinned (run 570). A version without an operator makes + # cargo install exactly that one. Bump it on purpose, after reading + # what the new version changes. + cargo install --locked cargo-xwin --version 0.23.1 + cargo-xwin --version - name: Install frontend dependencies run: npm ci @@ -53,14 +61,21 @@ jobs: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: | - npx tauri build --bundles deb,rpm + # Arguments after -- go to the runner (#449): cargo here, cargo-xwin + # in the Windows step, which hands them to cargo build. --locked + # builds the shipped binaries from the committed Cargo.lock, or fails + # rather than rewrite it. The log never prints the cargo command + # line: only a build that fails on a lockfile out of step with + # Cargo.toml proves the flag gets there. + npx tauri build --bundles deb,rpm -- --locked - name: Build Tauri Windows env: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} run: | - npx tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc + # -- --locked: see the Linux build above (#449). + npx tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc -- --locked - name: Collect release files run: | -- 2.45.2 From 14badbe2b0b2847d272a57db6b9dca030cd457f1 Mon Sep 17 00:00:00 2001 From: le king fu Date: Sun, 18 Oct 2026 17:28:39 -0400 Subject: [PATCH 2/3] docs: describe the --locked builds and the lockfile check of /release (#449) - Release skill, step 2: right after the hand edit of Cargo.lock and before the windows-sys guard, `cargo metadata --format-version 1 --locked` must exit 0, since the tag builds with -- --locked and the release commit goes to main without a pull request. Step 0 runs cargo check and cargo test with --locked, like CI, so the pre-flight cannot fold a lockfile rewrite into the release commit. - ADR 0023: the windows-sys guard reads the committed lockfile, cargo check having already refused one it would have had to rewrite. - CLAUDE.md, REVIEW.md and docs/architecture.md: CI commands with --locked, release builds on the committed lockfile, cargo-xwin pinned (REVIEW.md said it was installed unpinned), the stale REVIEW.md sentence about #449 removed, and why the dormant .github mirror is not aligned. Resolves #449 Co-Authored-By: Claude Opus 5.5 --- .claude/skills/release/SKILL.md | 10 ++++++++-- CLAUDE.md | 6 +++--- REVIEW.md | 12 +++++------- docs/adr/0023-windows-sys-pinning.md | 2 +- docs/architecture.md | 4 +++- 5 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index fd3af0d..abe31fd 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -2,7 +2,7 @@ name: release description: Release a new version of Simpl-Resultat (bump, changelog, tag, push) user-invocable: true -updated: 2026-10-11 +updated: 2026-10-18 --- # /release — Release Simpl-Resultat @@ -15,7 +15,7 @@ updated: 2026-10-11 ## Workflow -0. **Pré-vol — revalider le tip localement.** Les workflows `check-rust.yml` / `check-frontend.yml` ne tournent pas sur `main` : le tip mergé n'a jamais été vu par le CI (le dernier run vert portait sur la branche d'issue avant merge), et le tag grave ce tip exact dans des binaires distribués. Lancer `npm run build && npm test` (vitest) + `cd src-tauri && cargo check && cargo test`. Vérifier aussi que `.claude/worktrees/` est vide (worktrees leftover → vitest récurse et gonfle le compteur). Ne tagger que sur un tip vert. +0. **Pré-vol — revalider le tip localement.** Les workflows `check-rust.yml` / `check-frontend.yml` ne tournent pas sur `main` : le tip mergé n'a jamais été vu par le CI (le dernier run vert portait sur la branche d'issue avant merge), et le tag grave ce tip exact dans des binaires distribués. Lancer `npm run build && npm test` (vitest) + `cd src-tauri && cargo check --locked && cargo test --locked` (`--locked` comme la CI et le build du tag depuis #449 : sans lui, un lock que cargo réécrirait ici partirait sans relecture dans le commit de release). Vérifier aussi que `.claude/worktrees/` est vide (worktrees leftover → vitest récurse et gonfle le compteur). Ne tagger que sur un tip vert. **Accès des testeurs.** Si `[Unreleased]` touche `FEATURE_TIERS`, le gating par édition ou `auto-update` : vérifier que chaque testeur (Max, Michel) a une clé Base+ **saisie avant le tag**, émise via `/issue-license` au besoin — la clé s'entre dans la version installée et survit à la mise à jour ; une installation sans clé reçoit cette version, puis plus rien. Cas réel v0.15.0 : deux clés Premium émises le jour même, juste avant le tag. **Tableau des éditions du site.** Si `[Unreleased]` change la matrice des éditions (`ENTITLEMENTS` de `src/shared/entitlements.ts`, `FEATURE_TIERS` de `src-tauri/src/commands/entitlements.rs`, ou un module Free qui passe sous verrou, comme le fractionnement de #369) : la page produit du site en a une copie (`la-compagnie-maximus/src/data/editions.ts`, tableau comparatif et phrase `simplresultat_detail_desc`). Préparer la PR du site avant le tag en suivant la section « Matrice des éditions — Simpl-Résultat » de son `CLAUDE.md`, et ne la déployer qu'**après** la publication de la release : avant, le tableau promettrait un verrou absent des binaires. 1. Determiner la nouvelle version (argument utilisateur ou demander) @@ -26,6 +26,11 @@ updated: 2026-10-11 - `package.json` (champ `"version"`) - `package-lock.json` (deux champs `"version"` — root ~ligne 3 et le package racine `""` ~ligne 9) - Si `package-lock.json` est stale (hygiene warning `package-lock.json plus ancien que package.json`) : `npm install --package-lock-only --no-audit --no-fund` pour resync. Note : peut ajouter des entrees bundled optionnelles (tailwindcss oxide wasm etc.) — cosmetique, pas d'install effective. + - Puis vérifier que cargo accepte le lock édité tel quel, sans le réécrire : les deux builds de `release.yml` passent `-- --locked` (#449), donc un lock qui ne correspond plus à `Cargo.toml` arrêterait le build du tag, et le commit de release va sur `main` sans PR ni `check-rust`. Depuis la racine du dépôt : + ```bash + cargo metadata --manifest-path src-tauri/Cargo.toml --format-version 1 --locked > /dev/null + ``` + Attendu : code de sortie 0 (avec `--locked`, cargo n'écrit jamais le lock). Un refus (`the lock file … needs to be updated but --locked was passed to prevent this`) veut dire que l'édition à la main ne correspond pas à `Cargo.toml` (version de `simpl-result` différente des deux côtés, strophe abîmée) : corriger le lock à la main, ne jamais le régénérer avec cargo. - Puis rejouer le garde `windows-sys` de `check-rust.yml` sur le lock édité : le commit de release touche `Cargo.lock` directement sur `main`, sans PR, et `check-rust` ne le verrait qu'au lundi suivant, après le build du tag ([ADR 0023](../../../docs/adr/0023-windows-sys-pinning.md)). Depuis la racine du dépôt, le texte exact de l'étape avec les options du runner : ```bash python3 -c 'import sys, yaml; steps = yaml.safe_load(open(".forgejo/workflows/check-rust.yml"))["jobs"]["rust"]["steps"]; sys.stdout.write(next(s["run"] for s in steps if s.get("name") == "Verify windows-sys pinning"))' \ @@ -94,3 +99,4 @@ updated: 2026-10-11 - 2026-10-11 — Étape 2 : rejouer le garde `windows-sys` de `check-rust.yml` (ADR 0023) juste après l'édition de `Cargo.lock`, puisque le commit de release n'a pas de PR et que `check-rust` ne le verrait qu'au lundi suivant. Commande éprouvée sur le lock de la pile (exit 0) et sur une arête dérivée (exit 1). Source : revue de la PR #415. - 2026-10-16 — Étape 0 : report d'un changement de matrice des éditions vers le tableau comparatif du site (`la-compagnie-maximus/src/data/editions.ts`), PR du site préparée avant le tag et déployée après la publication. Source : la-compagnie-maximus#180. - 2026-10-18 — Étape 9 (post-CI) : le filtre de `latest.json` est appelé avec `-s` (fichier vide ou à deux documents refusé sous jq 1.6, celle du conteneur), et le texte de l'étape « Publish » de `release.yml` est exécuté par `scripts/build-latest-json.test.mjs` ; `check-frontend` ne saute plus les PR qui ne touchent que `.claude/**` ou `release.yml`. Source : #419 (suivis de la revue de la PR #418). +- 2026-10-18 — Étape 2 : `cargo metadata --manifest-path src-tauri/Cargo.toml --format-version 1 --locked` juste après l'édition du lock, avant le garde `windows-sys`, puisque les deux builds de `release.yml` passent désormais `-- --locked` et qu'un lock désaccordé arrêterait le tag. Étape 0 : `cargo check --locked` et `cargo test --locked`, comme la CI. Source : #449. diff --git a/CLAUDE.md b/CLAUDE.md index e5ab328..c49b156 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -164,12 +164,12 @@ Pour maintenir l'éligibilité aux crédits d'impôt R&D (RS&DE fédéral + CRIC Workflows Forgejo Actions dans `.forgejo/workflows/`. Le runner est à **capacité 1** — les jobs se suivent, ils ne tournent pas en parallèle. -- **`check-rust.yml`** — déclenché sur les PR touchant `src-tauri/**` ou `.cargo/**`, plus chaque lundi à 06:30 UTC sur `main` et par `workflow_dispatch`. Lance `cargo check`, une vérification bloquante que les advisories acceptées restent non atteignables, une autre que les sept arêtes `windows-sys` à borne large du lock restent sur `0.61.2` ([ADR 0023](docs/adr/0023-windows-sys-pinning.md)), `cargo test` et un `cargo audit` informatif, avec le cache `Swatinem/rust-cache`. Doit être vert avant tout merge. Un `cargo update` qui re-résout ces arêtes (`-p hyper` et `-p h2` l'ont fait) fait rougir le garde `windows-sys` : remettre les arêtes à la main selon l'ADR, ne jamais modifier la liste gardée pour le faire passer. +- **`check-rust.yml`** — déclenché sur les PR touchant `src-tauri/**` ou `.cargo/**`, plus chaque lundi à 06:30 UTC sur `main` et par `workflow_dispatch`. Lance `cargo check --locked`, une vérification bloquante que les advisories acceptées restent non atteignables, une autre que les sept arêtes `windows-sys` à borne large du lock restent sur `0.61.2` ([ADR 0023](docs/adr/0023-windows-sys-pinning.md)), `cargo test --locked` et un `cargo audit` informatif, avec le cache `Swatinem/rust-cache`. Doit être vert avant tout merge. Avec `--locked` ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)), un `Cargo.lock` qui ne correspond plus à `Cargo.toml` fait échouer le job au lieu d'être réécrit dans le checkout : les gardes et l'audit lisent le lock commité. Un `cargo update` voulu dans une PR reste possible, il commite le lock qu'il écrit. Un `cargo update` qui re-résout ces arêtes (`-p hyper` et `-p h2` l'ont fait) fait rougir le garde `windows-sys` : remettre les arêtes à la main selon l'ADR, ne jamais modifier la liste gardée pour le faire passer. - **`check-frontend.yml`** — déclenché sur les PR, sauf si tous les fichiers modifiés sont du Rust, de la doc, du markdown, `check-rust.yml` ou `audit.yml` ; `.claude/**` et `release.yml` le déclenchent, car `scripts/build-latest-json.test.mjs` lit `verify-release.sh` et exécute l'étape « Publish » de `release.yml` (#419). Lance `npm run build` (tsc + vite) et `npm test` (vitest). Doit être vert avant tout merge. **Aucune étape `npm audit`** : les advisories npm ne sont pas un gate de CI. `npm audit` à 0 au 2026-10-10 ([#358](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/358)), un constat daté : aucun job ne le fait échouer, mais `npm ci` en imprime le compte à chaque run de `check-frontend`, et le Défenseur relève les advisories de son côté (#358 en vient). - **`audit.yml`** — audit RustSec quotidien (06:00 UTC) + `workflow_dispatch`, pour couvrir les avis de sécurité entre deux PR Rust. Échec bloquant. **Un run vert signifie « zéro advisory hors de la liste acceptée »**, pas « zéro advisory » : cette liste est dans `.cargo/audit.toml` (preuve de non-atteignabilité et condition de retrait par entrée), encadrée par l'[ADR 0018](docs/adr/0018-suppression-advisories-non-atteignables.md). Ne jamais y ajouter une advisory atteignable ni élargir une entrée à un crate entier. -- **`release.yml`** — déclenché par les tags `v*`. Build Windows (NSIS `.exe`) + Linux (`.deb`, `.rpm`), signe les binaires et publie le JSON d'updater pour les mises à jour automatiques. +- **`release.yml`** — déclenché par les tags `v*`. Build Windows (NSIS `.exe`) + Linux (`.deb`, `.rpm`), signe les binaires et publie le JSON d'updater pour les mises à jour automatiques. Les deux `npx tauri build` passent `-- --locked` à cargo (par cargo-xwin pour Windows) : les binaires livrés sont bâtis sur le lock commité, jamais réécrit, et l'étape 2 de `/release` vérifie le lock édité à la main par `cargo metadata --locked` (#449). cargo-xwin, qui tourne avec la clé de signature dans son environnement, est installé en version épinglée (`--version 0.23.1`) ; la toolchain Rust (`rustup`, `stable`) ne l'est pas. -Aucun workflow `check-*` ne filtre sur `branches:` : une PR stackée sur une autre branche de feature déclenche donc bien la CI. Le cache Rust passe par `Swatinem/rust-cache` ([#234](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/234)) : le runner garde ce qu'écrit un run de PR pour cette seule PR, donc c'est le run du lundi (ou un `workflow_dispatch`) qui alimente le cache partagé que lisent les autres PR. Un run à froid reste normal après une nouvelle version stable de Rust ou une modification de l'`env:` du job, quand une entrée expire (7 jours sans usage, 30 jours d'âge), ou quand une PR sauvegarde sous la clé de l'entrée partagée et l'évince, le runner ne gardant qu'une entrée par clé. La liste complète est en tête de `check-rust.yml` ; seul un délai dépassé signale un retour de #234. Pas de cache npm : `check-frontend` ne tourne que sur des PR, et `npm ci` y prend 9 s. Le miroir `.github/workflows/` est dormant (aucune PR côté GitHub). +Aucun workflow `check-*` ne filtre sur `branches:` : une PR stackée sur une autre branche de feature déclenche donc bien la CI. Le cache Rust passe par `Swatinem/rust-cache` ([#234](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/234)) : le runner garde ce qu'écrit un run de PR pour cette seule PR, donc c'est le run du lundi (ou un `workflow_dispatch`) qui alimente le cache partagé que lisent les autres PR. Un run à froid reste normal après une nouvelle version stable de Rust ou une modification de l'`env:` du job, quand une entrée expire (7 jours sans usage, 30 jours d'âge), ou quand une PR sauvegarde sous la clé de l'entrée partagée et l'évince, le runner ne gardant qu'une entrée par clé. La liste complète est en tête de `check-rust.yml` ; seul un délai dépassé signale un retour de #234. Pas de cache npm : `check-frontend` ne tourne que sur des PR, et `npm ci` y prend 9 s. Le miroir `.github/workflows/` est dormant (aucune PR côté GitHub) et volontairement pas aligné : rien n'y tourne, il garde l'ancienne mise en page combinée, et ni `--locked` ni l'épinglage de cargo-xwin n'y sont reportés (décision de Max sur #449). --- diff --git a/REVIEW.md b/REVIEW.md index 568d6c1..88f0f13 100644 --- a/REVIEW.md +++ b/REVIEW.md @@ -123,7 +123,7 @@ Chaque règle cite sa source. Elles valent quel que soit le niveau : une PR norm ### Release et mise à jour -- `release.yml` met la clé de signature de l'updater (`TAURI_SIGNING_PRIVATE_KEY`) dans l'environnement des deux étapes `npx tauri build`. S'exécutent avec elle : `beforeBuildCommand` (`npm run build`, donc `tsc`, `vite.config.ts` et les `scripts` de `package.json`), `@tauri-apps/cli` et son binaire natif, qui lit la clé, `cargo-xwin` (`--runner cargo-xwin` pour Windows) et la toolchain Rust, installés à chaque tag sans version épinglée, `src-tauri/build.rs`, les scripts de build et les macros des crates, et tout module JS qu'une directive CSS `@plugin` ou `@config` fait charger. `npm ci` tourne avant, sans elle, mais ce qu'il installe s'exécute ensuite avec elle. — `CLAUDE.md` « CI/CD », `docs/architecture.md` « `release.yml` — Build et publication » +- `release.yml` met la clé de signature de l'updater (`TAURI_SIGNING_PRIVATE_KEY`) dans l'environnement des deux étapes `npx tauri build`. S'exécutent avec elle : `beforeBuildCommand` (`npm run build`, donc `tsc`, `vite.config.ts` et les `scripts` de `package.json`), `@tauri-apps/cli` et son binaire natif, qui lit la clé, `cargo-xwin` (`--runner cargo-xwin` pour Windows), installé à chaque tag en version épinglée depuis #449, la toolchain Rust, installée à chaque tag sans version épinglée, `src-tauri/build.rs`, les scripts de build et les macros des crates, et tout module JS qu'une directive CSS `@plugin` ou `@config` fait charger. `npm ci` tourne avant, sans elle, mais ce qu'il installe s'exécute ensuite avec elle. — `CLAUDE.md` « CI/CD », `docs/architecture.md` « `release.yml` — Build et publication » - `latest.json` porte une clé par paquet (`linux-x86_64-deb`, `linux-x86_64-rpm`, `windows-x86_64`), contrôlée par `scripts/check-latest-json.jq` avant le `DELETE` du registre ; `scripts/build-latest-json.test.mjs` tourne dans `npm test`. — `CLAUDE.md` « CI/CD », `docs/architecture.md` « `release.yml` — Build et publication » - `release.yml` signe les binaires et publie le JSON d'updater que lisent toutes les installations : une clé publique de `plugins.updater` changée sans bascule prévue coupe leurs mises à jour, comme un endpoint déplacé. — `CLAUDE.md` « CI/CD » et « Fonctionnalités principales » - Une release qui touche l'updater, ses dépendances, `release.yml` ou la config `updater` exige un cycle réel de mise à jour (étape 10 de `/release`, `docs/qa-update-cycle.md`) : la PR le signale pour la release suivante. `verify-release.sh` contrôle la release publiée. — `CLAUDE.md` « CI/CD », `.claude/skills/release/SKILL.md` @@ -153,18 +153,16 @@ Les workflows font foi ; ce tableau les résume au 2026-10-18. Le runner a une c | Workflow | Déclencheur | Commandes | |---|---|---| | `check-frontend.yml` | PR, sauf si tous ses fichiers sont exclus : `src-tauri/**`, `.cargo/**`, `docs/**`, `reports/**`, `tasks/**`, `.github/**`, `*.md` à la racine, `LICENSE`, `check-rust.yml` et `audit.yml` | `npm ci`, `npm run build` (tsc + vite), `npm test` (vitest, `scripts/build-latest-json.test.mjs` compris) | -| `check-rust.yml` | PR qui touche `src-tauri/**`, `.cargo/**` ou ce workflow ; lundi 06:30 UTC sur `main` ; `workflow_dispatch` | `cargo check --manifest-path src-tauri/Cargo.toml --all-targets`, garde des advisories supprimées, garde `windows-sys`, `cargo test --manifest-path src-tauri/Cargo.toml --all-targets`, `cargo audit --file src-tauri/Cargo.lock` (non bloquant) | +| `check-rust.yml` | PR qui touche `src-tauri/**`, `.cargo/**` ou ce workflow ; lundi 06:30 UTC sur `main` ; `workflow_dispatch` | `cargo check --manifest-path src-tauri/Cargo.toml --all-targets --locked`, garde des advisories supprimées, garde `windows-sys`, `cargo test --manifest-path src-tauri/Cargo.toml --all-targets --locked`, `cargo audit --file src-tauri/Cargo.lock` (non bloquant) | | `audit.yml` | chaque jour à 06:00 UTC ; `workflow_dispatch` | `cargo-audit audit --file src-tauri/Cargo.lock` (bloquant) | -| `release.yml` | tag `v*` | build Linux (`.deb`, `.rpm`) et Windows (cargo-xwin), signature, `latest.json` | - -`cargo check` et `cargo test` tournent sans `--locked` dans la CI ; #449 l'ajoute, et sa PR met ce tableau à jour. +| `release.yml` | tag `v*` | build Linux (`.deb`, `.rpm`) et Windows (cargo-xwin 0.23.1, épinglé), les deux par `npx tauri build … -- --locked`, signature, `latest.json` | En local, les mêmes commandes, depuis la racine : ```bash npm ci && npm run build && npm test -cargo check --manifest-path src-tauri/Cargo.toml --all-targets -cargo test --manifest-path src-tauri/Cargo.toml --all-targets +cargo check --manifest-path src-tauri/Cargo.toml --all-targets --locked +cargo test --manifest-path src-tauri/Cargo.toml --all-targets --locked cargo audit --file src-tauri/Cargo.lock # depuis la racine : .cargo/audit.toml s'applique ``` diff --git a/docs/adr/0023-windows-sys-pinning.md b/docs/adr/0023-windows-sys-pinning.md index 61f7336..a02a5e2 100644 --- a/docs/adr/0023-windows-sys-pinning.md +++ b/docs/adr/0023-windows-sys-pinning.md @@ -73,7 +73,7 @@ L'étape `Verify windows-sys pinning` de `check-rust.yml`, placée après `cargo 3. **Ensemble des versions.** Les versions de `windows-sys` du lock, toutes cibles confondues et doublons compris, doivent égaler exactement `{0.45.0, 0.48.0, 0.52.0, 0.59.0, 0.60.2, 0.61.2}` (état du 2026-10-10). Une version qui apparaît ou disparaît fait échouer l'étape avec un renvoi à cet ADR : c'est soit le coût réel (une version de plus), soit le début de la sortie, et dans les deux cas une décision. 4. **Canari.** La boucle doit avoir lu et vérifié une arête de `tempfile`, crate connu pour en porter une, faute de quoi son silence ne prouve rien et l'étape échoue. -Comme dans l'ADR 0018, chaque vérification est loggée, succès compris (`ok: rustix 1.1.3 -> windows-sys 0.61.2`), puis un bilan (`windows-sys pinning: 9 checks, exit 0`), et le code de sortie d'`awk` est testé à part de sa sortie. Une sortie vide échoue déjà (ensemble des versions, blocs introuvables, canari) ; ce test attrape seul un `awk` qui échoue après avoir tout imprimé, et sans lui le garde sortirait en 0 (éprouvé à la revue de la PR #415). Le garde n'appelle pas cargo, donc ni l'index chaud ni `--locked` ne le concernent ; il lit le lock tel que `cargo check` l'a laissé. +Comme dans l'ADR 0018, chaque vérification est loggée, succès compris (`ok: rustix 1.1.3 -> windows-sys 0.61.2`), puis un bilan (`windows-sys pinning: 9 checks, exit 0`), et le code de sortie d'`awk` est testé à part de sa sortie. Une sortie vide échoue déjà (ensemble des versions, blocs introuvables, canari) ; ce test attrape seul un `awk` qui échoue après avoir tout imprimé, et sans lui le garde sortirait en 0 (éprouvé à la revue de la PR #415). Le garde n'appelle pas cargo, donc ni l'index chaud ni `--locked` ne le concernent. Il lit le lock commité : `cargo check` tourne avec `--locked` depuis #449 et a déjà fait échouer le job sur un lock que cargo aurait dû réécrire. La liste gardée, la version épinglée et l'ensemble attendu sont en tête du script, sous un commentaire qui renvoie ici. On ne les modifie que sur la preuve que demandent les sections suivantes, jamais pour faire passer l'étape au vert. diff --git a/docs/architecture.md b/docs/architecture.md index 07ab926..1ecbb7f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -646,7 +646,7 @@ Quatre workflows Forgejo Actions dans `.forgejo/workflows/`. Le runner est à ** ### `check-rust.yml` — Vérifications Rust sur PR et chaque semaine -Déclenché sur les PR qui touchent `src-tauri/**`, `.cargo/**` (ou le workflow lui-même), chaque lundi à 06:30 UTC sur `main` et par `workflow_dispatch`. Lance `cargo check` puis `cargo test`, et un `cargo audit` informatif (non bloquant, binaire pré-buildé via `taiki-e/install-action`). +Déclenché sur les PR qui touchent `src-tauri/**`, `.cargo/**` (ou le workflow lui-même), chaque lundi à 06:30 UTC sur `main` et par `workflow_dispatch`. Lance `cargo check --locked` puis `cargo test --locked`, et un `cargo audit` informatif (non bloquant, binaire pré-buildé via `taiki-e/install-action`). Avec `--locked` ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)), un `Cargo.lock` qui ne correspond plus à `Cargo.toml` (édition à la main, conflit mal résolu, dépendance ajoutée sans son lock) fait échouer le job à `cargo check` au lieu d'être réécrit dans le checkout : les deux gardes et l'audit qui suivent lisent le lock commité. Entre `cargo check` et `cargo test`, une étape **bloquante** rejoue la justification des advisories acceptées dans `.cargo/audit.toml` : elle vérifie par `cargo tree --locked` que chaque crate supprimé reste absent des deux cibles livrées, et échoue si l'un d'eux redevient atteignable. Un canari (`webkit2gtk`, la webview Linux de Tauri, présente sous Linux et absente sous Windows ; `tar` jusqu'à #455) garantit que son silence prouve quelque chose, et que `--target` résout bien les dépendances conditionnelles. Voir [ADR 0018](adr/0018-suppression-advisories-non-atteignables.md). @@ -691,6 +691,8 @@ Fonctionnalités : **Node.js 24** ([#376](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/376)), installé comme dans les trois autres workflows par le script NodeSource (`curl … | bash`). Ce script tourne en root dans le conteneur du job, et le Node.js qu'il installe lance ensuite `npx tauri build` avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement : le job confie à NodeSource (script et dépôt apt) la clé de signature de l'updater, et les jetons de publication des étapes suivantes. Risque accepté par Max le 2026-10-10, alternative écartée : un tarball Node.js épinglé. La chaîne Rust, installée de la même façon par `rustup`, est dans le même cas. Ce workflow ne tourne qu'au tag : la première release après #376 est la première sous Node 24, à vérifier par `node --version` dans le journal du run (étape 9 de `/release`). +**Lock commité, cargo-xwin épinglé** ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)). Les deux `npx tauri build` passent `-- --locked` : la CLI de Tauri transmet ce qui suit `--` à son runner, cargo pour Linux, cargo-xwin pour Windows, qui le passe à `cargo build`. Les binaires livrés sont donc bâtis sur le `Cargo.lock` commité, ou le build échoue au lieu de le réécrire ; l'étape 2 de `/release`, qui édite le lock à la main sur `main` sans PR, vérifie ce lock par `cargo metadata --locked` avant le tag. Le journal du run n'imprime jamais la ligne de commande de cargo : la preuve que le drapeau arrive est un lock désaccordé qui fait échouer le build, faite en local dans la PR de #449. cargo-xwin, qui tourne avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement, est installé en version fixe (`cargo install --locked cargo-xwin --version 0.23.1`, celle qu'avait installée la release v0.17.2), et l'étape affiche `cargo-xwin --version` ; une nouvelle version se décide, après lecture de ce qu'elle change. + ## Architecture Decision Records (ADRs) Les ADRs documentent les décisions techniques structurantes. Ils vivent dans `docs/adr/`. -- 2.45.2 From db625d02efc015ab8ef0ca28d84bbed79f5a7036 Mon Sep 17 00:00:00 2001 From: le king fu Date: Sun, 18 Oct 2026 17:39:11 -0400 Subject: [PATCH 3/3] docs: record the proven --locked relay of both release runners (#449) - Release skill, step 2: quote the refusal cargo actually prints, the same under cargo 1.94.1 and 1.99.1 (`cannot update the lock file ... because --locked was passed to prevent this`, code 101). - docs/architecture.md: the relay of --locked is proven for both runners on a lockfile out of step with Cargo.toml: cargo-xwin 0.23.1 runs `cargo build ... --locked ...`, and the other cargo calls of the Tauri CLI and cargo-xwin are `cargo metadata --no-deps`, which never touch the lockfile. Resolves #449 Co-Authored-By: Claude Opus 5.5 --- .claude/skills/release/SKILL.md | 2 +- docs/architecture.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index abe31fd..a34eff7 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -30,7 +30,7 @@ updated: 2026-10-18 ```bash cargo metadata --manifest-path src-tauri/Cargo.toml --format-version 1 --locked > /dev/null ``` - Attendu : code de sortie 0 (avec `--locked`, cargo n'écrit jamais le lock). Un refus (`the lock file … needs to be updated but --locked was passed to prevent this`) veut dire que l'édition à la main ne correspond pas à `Cargo.toml` (version de `simpl-result` différente des deux côtés, strophe abîmée) : corriger le lock à la main, ne jamais le régénérer avec cargo. + Attendu : code de sortie 0 (avec `--locked`, cargo n'écrit jamais le lock). Un refus (code 101, `error: cannot update the lock file … because --locked was passed to prevent this`) veut dire que l'édition à la main ne correspond pas à `Cargo.toml` (version de `simpl-result` différente des deux côtés, strophe abîmée) : corriger le lock à la main, ne jamais le régénérer avec cargo. - Puis rejouer le garde `windows-sys` de `check-rust.yml` sur le lock édité : le commit de release touche `Cargo.lock` directement sur `main`, sans PR, et `check-rust` ne le verrait qu'au lundi suivant, après le build du tag ([ADR 0023](../../../docs/adr/0023-windows-sys-pinning.md)). Depuis la racine du dépôt, le texte exact de l'étape avec les options du runner : ```bash python3 -c 'import sys, yaml; steps = yaml.safe_load(open(".forgejo/workflows/check-rust.yml"))["jobs"]["rust"]["steps"]; sys.stdout.write(next(s["run"] for s in steps if s.get("name") == "Verify windows-sys pinning"))' \ diff --git a/docs/architecture.md b/docs/architecture.md index 1ecbb7f..b56a2f3 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -691,7 +691,7 @@ Fonctionnalités : **Node.js 24** ([#376](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/376)), installé comme dans les trois autres workflows par le script NodeSource (`curl … | bash`). Ce script tourne en root dans le conteneur du job, et le Node.js qu'il installe lance ensuite `npx tauri build` avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement : le job confie à NodeSource (script et dépôt apt) la clé de signature de l'updater, et les jetons de publication des étapes suivantes. Risque accepté par Max le 2026-10-10, alternative écartée : un tarball Node.js épinglé. La chaîne Rust, installée de la même façon par `rustup`, est dans le même cas. Ce workflow ne tourne qu'au tag : la première release après #376 est la première sous Node 24, à vérifier par `node --version` dans le journal du run (étape 9 de `/release`). -**Lock commité, cargo-xwin épinglé** ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)). Les deux `npx tauri build` passent `-- --locked` : la CLI de Tauri transmet ce qui suit `--` à son runner, cargo pour Linux, cargo-xwin pour Windows, qui le passe à `cargo build`. Les binaires livrés sont donc bâtis sur le `Cargo.lock` commité, ou le build échoue au lieu de le réécrire ; l'étape 2 de `/release`, qui édite le lock à la main sur `main` sans PR, vérifie ce lock par `cargo metadata --locked` avant le tag. Le journal du run n'imprime jamais la ligne de commande de cargo : la preuve que le drapeau arrive est un lock désaccordé qui fait échouer le build, faite en local dans la PR de #449. cargo-xwin, qui tourne avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement, est installé en version fixe (`cargo install --locked cargo-xwin --version 0.23.1`, celle qu'avait installée la release v0.17.2), et l'étape affiche `cargo-xwin --version` ; une nouvelle version se décide, après lecture de ce qu'elle change. +**Lock commité, cargo-xwin épinglé** ([#449](https://git.lacompagniemaximus.com/maximus/simpl-resultat/issues/449)). Les deux `npx tauri build` passent `-- --locked` : la CLI de Tauri transmet ce qui suit `--` à son runner, cargo pour Linux, cargo-xwin pour Windows, qui le passe à `cargo build`. Les binaires livrés sont donc bâtis sur le `Cargo.lock` commité, ou le build échoue au lieu de le réécrire ; l'étape 2 de `/release`, qui édite le lock à la main sur `main` sans PR, vérifie ce lock par `cargo metadata --locked` avant le tag. Le journal du run n'imprime jamais la ligne de commande de cargo : la preuve que le drapeau arrive est un lock désaccordé qui fait échouer le build, faite en local dans la PR de #449 pour les deux runners (cargo-xwin 0.23.1 lance `cargo build … --locked …`, et les autres appels à cargo de la CLI et de cargo-xwin sont des `cargo metadata --no-deps`, qui ne touchent pas au lock). cargo-xwin, qui tourne avec `TAURI_SIGNING_PRIVATE_KEY` dans son environnement, est installé en version fixe (`cargo install --locked cargo-xwin --version 0.23.1`, celle qu'avait installée la release v0.17.2), et l'étape affiche `cargo-xwin --version` ; une nouvelle version se décide, après lecture de ce qu'elle change. ## Architecture Decision Records (ADRs) -- 2.45.2