diff --git a/CLAUDE.md b/CLAUDE.md index f6379ed..6f0defa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -134,6 +134,8 @@ La documentation technique est centralisée dans `docs/` : - `docs/guide-utilisateur.md` — Guide utilisateur - `docs/archive/` — Anciennes spécifications archivées +Les règles de revue des PR (niveaux de risque, ce qui compte par zone, commandes de vérification, pièges) sont dans [`REVIEW.md`](REVIEW.md), à la racine ; un changement qui rend faux un fait qu'il cite le met à jour dans la même PR. + **Règle : quand un changement touche l'architecture, mettre à jour la documentation :** - Nouveau service, hook, commande Tauri, page/route, ou table SQL → mettre à jour `docs/architecture.md` - Décision technique structurante (choix de librairie, pattern architectural, changement de stratégie) → créer un nouvel ADR dans `docs/adr/` diff --git a/REVIEW.md b/REVIEW.md new file mode 100644 index 0000000..a09c05a --- /dev/null +++ b/REVIEW.md @@ -0,0 +1,187 @@ +# REVIEW.md — Simpl'Résultat + +Règles de revue des PR de ce dépôt : niveau de risque, ce qui compte dans chaque zone, commandes de vérification et pièges connus. Ce fichier complète `CLAUDE.md` et `.claude/rules/` sans les remplacer : une zone en reprend ce qui sert à la revue, avec sa source. + +À partir de #457, `/pr-review` lit ce fichier sur `main`, y compris pour une PR empilée : une PR qui le modifie est jugée par la version de `main`, et ses nouvelles règles valent pour les PR suivantes. + +## Niveaux + +Le niveau fixe la profondeur de la revue, que `/pr-review` règle pour chaque niveau à partir de #457. Il se calcule sur les fichiers que touche le diff, jamais sur l'auteur (Max, `/fix-issue`, `/autopilot`) : + +- un fichier désigné par une ligne du tableau **Haut** dont la condition est remplie est de niveau haut ; sinon, un fichier désigné par **Bas** est de niveau bas ; sinon il est normal ; +- la PR prend le niveau le plus haut de ses fichiers, et la revue l'annonce avec le fichier qui le fixe ; +- un fichier renommé, déplacé ou supprimé se juge sur son ancien et sur son nouveau chemin : un fichier de niveau haut déplacé reste haut, et la PR qui le déplace met ce tableau à jour ; +- une condition qu'on ne sait pas trancher à la lecture du diff compte comme remplie. + +Motifs : chemins depuis la racine. `*` ne traverse jamais un `/` ; `**/` vaut zéro, un ou plusieurs dossiers (`**/*.md` couvre `CLAUDE.md`, à la racine) ; `{a,b}` liste des variantes. + +### Haut + +| Motif | Condition | Zone | +|---|---|---| +| `src-tauri/src/database/**` | toujours | Migrations, profils et données | +| `src-tauri/src/lib.rs` | sauf si le diff se limite à des entrées de `generate_handler![…]` ou au module `tests` | Migrations (`migrations()`), plugins, deep link OAuth | +| `src-tauri/src/commands/profile_commands.rs` | toujours | Migrations, profils et données | +| `src-tauri/src/commands/{license_commands,entitlements,auth_commands,token_store,account_cache}.rs`, `src/services/{licenseService,authService}.ts`, `.claude/skills/issue-license/**` | toujours | Jetons, licence et édition | +| `src-tauri/src/commands/balance_commands.rs` | le diff touche `fetch_price` ou la lecture du jeton d'activation | Jetons, licence et édition | +| `src-tauri/src/commands/export_import_commands.rs` | toujours | Jetons, licence et édition (export chiffré) | +| `src-tauri/src/commands/feedback_commands.rs`, `src/services/feedbackService.ts` | toujours | Vie privée | +| code livré : `src/**`, `src-tauri/src/**`, `index.html`, `public/**` | le diff ajoute un appel réseau, change ce qu'un appel existant envoie, ou ouvre une URL qui porte une donnée de l'utilisateur (donnée financière, identifiant, contenu d'un profil, ligne du Journal) | Vie privée | +| `src-tauri/capabilities/**` | toujours | Vie privée | +| `src-tauri/{tauri*.conf.json*,Tauri*.toml}` | sauf si le diff se limite au champ `"version"` ; un fichier propre à une plateforme est fusionné avec `tauri.conf.json` | Vie privée (CSP), Release (updater, `beforeBuildCommand`) | +| `.forgejo/workflows/release.yml`, `scripts/build-latest-json*`, `scripts/check-latest-json.jq`, `.claude/skills/release/**` | toujours | Release et mise à jour | +| `src-tauri/build.rs`, `{vite,vitest,postcss}.config.*`, `.postcssrc*`, `.npmrc`, `**/rust-toolchain*`, `Cargo.{toml,lock}` à la racine | toujours | Release et mise à jour, CI et gardes (lu ou exécuté par le build de la release ou par `npm test`) | +| fichier à la racine du dépôt ou directement dans `src-tauri/`, hors `*.md` | ajouté, renommé ou supprimé, comme tout fichier d'un dossier caché nouveau : une configuration nouvelle peut changer le build ou la CI sans motif qui la nomme | Release et mise à jour, CI et gardes | +| `src-tauri/Cargo.toml`, `package.json` | sauf si le diff se limite au champ `version` de l'app (dépendances, features dont `dev-override`, `overrides`, scripts) | Dépendances ; Release pour les `scripts` de `package.json` | +| `src-tauri/Cargo.lock`, `package-lock.json` | le diff ajoute ou retire un paquet (un bloc `[[package]]` dont la ligne `name =` apparaît ou disparaît, une seconde version d'un crate comprise ; une clé `"node_modules/"` qui apparaît ou disparaît), ou change l'origine d'un paquet sans changer sa version (`source`, `checksum` ; `resolved`, `integrity`) | Dépendances | +| `**/.cargo/**` | toujours | Dépendances | +| `.forgejo/workflows/**` | fichier ajouté, ou diff qui ajoute une référence à `secrets` | CI et gardes | +| `.forgejo/workflows/audit.yml` | le diff change une ligne active : sans déclencheur `pull_request`, il tourne pour la première fois sur `main` | CI et gardes | +| `.forgejo/workflows/{check-rust,check-frontend}.yml` | le diff peut faire passer au vert ce qui échouait : déclencheur retiré ou restreint (`paths`, `paths-ignore`, `branches`, `schedule`) ; étape de verdict modifiée ou retirée (`npm ci`, `npm run build`, `npm test`, `cargo check`, `cargo test`, `Verify …`, `cargo audit`) ; `continue-on-error` ou `\|\| true` ajouté ; étape `run:` ajoutée qui écrit `$GITHUB_ENV`, `$GITHUB_PATH` ou un fichier du dépôt ; action `uses:` ajoutée, hors cache ; `if:`, `defaults:`, `permissions:`, `env:` ou `container:` modifiés | CI et gardes | +| `REVIEW.md` | le diff touche la section « Niveaux » | Règles de revue | + +Restent au moins normaux, et leurs règles de zone s'appliquent quand même : + +- une montée de version d'un paquet déjà présent dans un lock, correctif d'advisory compris (décision de Max du 2026-10-17, consignée sur #456), et une arête re-résolue vers une version déjà présente : seul le garde `windows-sys` de la CI en surveille, pour ses sept crates ([ADR 0023](docs/adr/0023-windows-sys-pinning.md)) ; +- dans `check-rust.yml` et `check-frontend.yml` : un déclencheur ajouté, une étape `run:` ajoutée qui ne fait que lire, un cache (`Swatinem/rust-cache`, `actions/cache`), un commentaire, la version de Node ; +- `src/services/db.ts`, `src/services/dataExportService.ts`, `src-tauri/src/commands/{fs_commands,backup_commands}.rs`, et le reste de `balance_commands.rs` ; +- `src/shared/entitlements.ts` : la matrice ne verrouille que l'interface ([ADR 0017](docs/adr/0017-feature-gating-par-tier.md), § 2) ; +- `CLAUDE.md`, `REVIEW.md` hors de la section « Niveaux », `docs/adr/**`, `docs/architecture.md`, `docs/qa-update-cycle.md` et `.claude/**` : sources de ce fichier et instructions des agents, jamais de niveau bas. + +### Bas + +Une PR est de niveau bas quand tous ses fichiers sont désignés par `**/*.md`, `docs/**`, `LICENSE`, `tasks/**` ou `reports/**`, hors des exceptions de la liste précédente. `STATE.md` reste bas malgré son import par `CLAUDE.md` : il résume l'état du projet, il ne fixe pas de règle. + +### Normal + +Tout le reste : code applicatif et tests, traductions (`src/i18n/locales/*.json`), `public/`, configuration TypeScript, `.github/**` (miroir dormant). + +### Calibration + +Rejouée le 2026-10-17 (#456) sur les PR mergées de #344 à #452 : les 39 PR de référence du document de départ du pilote, dont la liste est figée dans la PR qui ferme #456, et la plage complète, soit 51 PR. + +| Ensemble | Bas | Normal | Haut | +|---|---|---|---| +| 39 PR de référence, motifs d'essai | 3 | 19 | 17 (44 %) | +| 39 PR de référence, ces motifs | 3 | 28 | 8 (21 %, pour une cible de 20 %) | +| 51 PR de la plage, ces motifs | 6 | 33 | 12 (24 %) | + +Niveau haut sur les 51 : licence (#359, #364, #366, #368, #386, #387, #388), migrations et profils (#394, #436), release (#414, #416, #418) ; parmi les 39 de référence, #386, #387, #388, #394, #414, #416, #418 et #436. L'écart à la cible est justifié dans la PR qui ferme #456, qui consigne aussi le niveau de chaque PR et le script qui les calcule. + +Une PR qui modifie cette section dit, pour chaque motif retiré ou assoupli, ce qu'il protégeait, et rejoue ce script sur les mêmes PR ; le pilote #458 mesure si les niveaux tiennent. + +## Ce qui compte par zone + +Chaque règle cite sa source. Elles valent quel que soit le niveau : une PR normale qui touche une zone les respecte aussi. + +### Vie privée + +- Aucune donnée financière ne quitte le poste : ni télémétrie, ni analytics, et le CSV comme la base restent locaux. Un appel réseau ajouté se justifie dans la PR : il n'envoie rien de financier ni aucun identifiant brut, et il passe derrière un consentement ou l'édition. — `CLAUDE.md` « Privacy-first — NON NÉGOCIABLE », [ADR 0009](docs/adr/0009-proxy-price-fetching-via-maximus-api.md), [ADR 0020](docs/adr/0020-identifiant-machine-derive.md) +- Les appels sortants partent tous du Rust (`reqwest`) : `auth_commands.rs` (compte), `license_commands.rs` (licence et activation), `balance_commands.rs` (`fetch_price`, qui porte le jeton d'activation) et `feedback_commands.rs`, plus l'endpoint du plugin d'updater. Le `connect-src` de `app.security.csp` (`api.`, `auth.` et `feedback.lacompagniemaximus.com`) ne borne que la webview : lire chaque URL, chaque en-tête et chaque corps envoyé. — `CLAUDE.md` « Privacy-first — NON NÉGOCIABLE », `src-tauri/tauri.conf.json`, `src-tauri/src/commands/` +- Les cours passent par `maximus-api`, après un consentement explicite par profil, avec les seuls en-têtes `Authorization`, `Accept` et `User-Agent` : le fournisseur tiers ne voit jamais le poste. — [ADR 0009](docs/adr/0009-proxy-price-fetching-via-maximus-api.md) +- Seule l'activation envoie un identifiant de la machine, et seulement le dérivé (`HMAC-SHA256` de l'identifiant du système) ; un jeton émis avant la dérivation porte encore l'identifiant brut jusqu'à la réactivation. — [ADR 0020](docs/adr/0020-identifiant-machine-derive.md), § 2 et 5 +- Le Feedback Hub reçoit le texte saisi, suivi au choix des dernières erreurs du Journal, un `userId` facultatif et un contexte borné par `FeedbackContext` (page, langue, thème, fenêtre, user-agent, horodatage) : un champ ajouté au contexte élargit ce qui part. — `CLAUDE.md` « Privacy-first — NON NÉGOCIABLE », `src/services/feedbackService.ts`, `src/components/settings/FeedbackDialog.tsx` +- La webview n'a que les permissions de `src-tauri/capabilities/default.json` : l'accès aux fichiers passe par les commandes Rust, jamais par une permission `fs`, et une permission ajoutée se justifie. — `CLAUDE.md` « Règles d'architecture » +- Un message du Journal nomme un fichier, jamais son dossier : le chemin contient le nom d'utilisateur, et une ligne d'erreur du Journal peut partir avec une rétroaction (règle posée pour le rattrapage des profils). — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md) + +### Précision financière et import + +- Montants : virgule ou point décimal, espaces, symboles monétaires, négatifs, cellules vides et formats inattendus ont chacun leur test. — `CLAUDE.md` « Précision financière » et « Conventions de code » (Général) +- Encodages CSV : UTF-8, Windows-1252, ISO-8859-15. — `CLAUDE.md` « Précision financière » +- Le format d'import est persisté en entier sur la source et lu tel quel, jamais re-deviné ; l'aperçu reste obligatoire avant tout import. — [ADR 0019](docs/adr/0019-format-import-persiste.md), `CLAUDE.md` « Fonctionnalités principales » + +### Migrations, profils et données + +- Jamais de modification d'une migration existante : son checksum est vérifié au démarrage et épinglé par le test `migration_checksums_match_the_released_ones`. On ajoute une version. — `CLAUDE.md` « Base de données », [ADR 0003](docs/adr/0003-sqlx-migrations.md), `.claude/rules/sql-migrations.md` +- Une migration atteint deux chemins et reste correcte sur les deux : le profil par défaut, migré par tauri-plugin-sql (sqlx), et chaque profil secondaire, rattrapé à son ouverture par `prepare_profile_db` (rusqlite, clés étrangères actives). — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md), `.claude/rules/sql-migrations.md` +- Tout changement de schéma et toute correction de données (seed, mots-clés, catégories) se reportent dans `consolidated_schema.sql` : un profil neuf naît avec l'historique complet et ne rejoue aucune migration. Aucun marqueur ajouté à `infer_floor`, réservé aux bases créées avant #390. — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md), `.claude/rules/sql-migrations.md` +- Les comptes de tables, d'index et de migrations de `CLAUDE.md` et de `docs/architecture.md` suivent. — `CLAUDE.md` « Base de données » et « Documentation technique » +- Les bases vivent sous `app_config_dir()`, qui n'est pas `app_data_dir()` sous Linux. Ce dossier dérive de l'`identifier` de `tauri.conf.json`, auquel s'aligne aussi le service du trousseau : changer l'`identifier` orpheline les profils et les jetons existants. — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md), [ADR 0006](docs/adr/0006-oauth-tokens-keychain.md), `CLAUDE.md` « Base de données » +- Un booléen se lie en `1`/`0` (`toSqlFlag`), jamais en `true`/`false`. — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md) +- Une base par profil, PIN haché par Argon2. — [ADR 0005](docs/adr/0005-multi-profile-db.md) +- Une nouvelle écriture d'une table que lit `COUNTERS_SQL` appelle `notifyDataChanged()` après succès, avec un test qui prouve la notification. — [ADR 0022](docs/adr/0022-canal-evenements-donnees.md) + +### Jetons, licence et édition + +- Jetons OAuth dans le trousseau du système ; repli sur un fichier `0600` seulement sans trousseau, et plus de retour au fichier une fois le trousseau utilisé. — [ADR 0006](docs/adr/0006-oauth-tokens-keychain.md) +- Le callback `simpl-resultat://auth/callback` n'est traité que par l'instance qui détient le vérificateur PKCE : `tauri-plugin-single-instance` reste le premier plugin enregistré, et un callback sans `code` remonte une erreur au front. — [ADR 0006](docs/adr/0006-oauth-tokens-keychain.md), `docs/architecture.md` « OAuth2 et deep-link (Compte Maximus) » +- Export chiffré SREF : AES-256-GCM, clé dérivée du mot de passe par Argon2id, sel et nonce aléatoires. — [ADR 0004](docs/adr/0004-aes-256-gcm-encryption.md) +- La dérivation de l'identifiant machine est figée (`derive_machine_id`), et un jeton émis avant elle reste accepté. Une montée de `machine-uid` se vérifie : une forme renvoyée différente changerait l'identifiant dérivé de chaque machine. — [ADR 0020](docs/adr/0020-identifiant-machine-derive.md), § 1, 3 et « À surveiller » +- `isEntitled` est fail-closed en Free : le court-circuit `free` passe avant l'override signé `features[]` (CWE-863). `useEntitlement` rend `{ allowed, ready }`. — [ADR 0017](docs/adr/0017-feature-gating-par-tier.md), § 1 et 3 +- Une fonctionnalité verrouillée reste visible, jamais masquée ; le gating bloque l'accès, jamais les données. — [ADR 0017](docs/adr/0017-feature-gating-par-tier.md), § 4 et 5 +- Le gating ne s'applique qu'à l'interface (soft-paywall assumé, l'app est GPL), et `SR_DEV_EDITION` n'existe que dans un build compilé avec la feature `dev-override`. — [ADR 0017](docs/adr/0017-feature-gating-par-tier.md), § 2 et 6 + +### Dépendances + +- `.cargo/audit.toml` n'admet une advisory que si son crate est absent de toutes les cibles livrées (`cargo tree -i --target `, Linux et Windows, jamais `--target all`) ou si aucun correctif n'existe. Une entrée porte un ID, jamais un crate ; elle ajoute son crate à la liste du garde de `check-rust.yml` et a son issue de suivi, qui porte la condition de retrait. — [ADR 0018](docs/adr/0018-suppression-advisories-non-atteignables.md), en-tête de `.cargo/audit.toml`, `CLAUDE.md` « CI/CD » +- Garde `windows-sys` : s'il rougit après un `cargo update`, remettre les arêtes à la main selon l'ADR ; ne jamais modifier la liste gardée, la version épinglée ni le canari pour le faire passer. Un `cargo update` ciblé peut déplacer d'autres arêtes sans le dire : chaque entrée qui bouge dans le lock doit s'expliquer. — [ADR 0023](docs/adr/0023-windows-sys-pinning.md), `CLAUDE.md` « CI/CD » +- Une version montée exécute son nouveau code au build de la release, clé de signature dans l'environnement : scripts de build et macros des crates, code des paquets npm que lancent `npm run build` et `npx tauri build`. Lire ce que change la version, pas seulement son numéro. — `CLAUDE.md` « CI/CD », `docs/architecture.md` « `release.yml` — Build et publication » +- Une advisory corrigée se prouve par `cargo audit` lancé depuis la racine, là où `.cargo/audit.toml` s'applique. `npm audit` n'est pas un gate de CI. — `CLAUDE.md` « CI/CD » + +### 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é, `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` + +### CI et gardes + +- Aucun workflow `check-*` ne filtre sur `branches:` : `branches: [main]` avait laissé sans CI quatre des cinq PR empilées de la milestone feature-gating. `check-frontend.yml` exclut par `paths-ignore` pour qu'un fichier nouveau déclenche la CI au lieu de la sauter. — `CLAUDE.md` « CI/CD », en-têtes de `check-rust.yml` et `check-frontend.yml` +- `audit.yml` est le seul canal des avis RustSec : quotidien et bloquant. L'étape `cargo audit` de `check-rust` est non bloquante par choix. — `CLAUDE.md` « CI/CD » +- Une garde affiche chacune de ses vérifications, y compris celles qui passent, et lit un canari : une garde muette ne prouve rien. — [ADR 0018](docs/adr/0018-suppression-advisories-non-atteignables.md) (règle 3), [ADR 0023](docs/adr/0023-windows-sys-pinning.md) +- Une PR qui ne touche que `audit.yml` ou `release.yml` n'a aucune CI : les `paths` de `check-rust.yml` ne les listent pas, et le `paths-ignore` de `check-frontend.yml` les exclut. La revue rejoue elle-même ce qui peut l'être. — `CLAUDE.md` « CI/CD », `check-rust.yml`, `check-frontend.yml` +- Les secrets du dépôt (clé de signature et son mot de passe, jeton du registre de paquets qui remplace `latest.json`) sont lisibles par tout workflow : un workflow ajouté, ou une référence à `secrets` ajoutée, se lit comme du code de release. — `CLAUDE.md` « CI/CD », `release.yml` + +### i18n et documentation + +- Toute chaîne visible passe par i18next, dans `fr.json` et `en.json` à la fois ; aucun texte en dur dans un composant. — `CLAUDE.md` « Internationalisation (i18n) », `.claude/rules/i18n.md` +- Un changement visible ajoute une entrée sous `[Unreleased]` dans `CHANGELOG.md` et `CHANGELOG.fr.md`. — `CLAUDE.md` « Documentation technique » (Règle CHANGELOG) +- Un changement d'usage met à jour `docs/guide-utilisateur.md` et les clés `docs.*` ; un service, un hook, une commande, une page ou une table de plus met à jour `docs/architecture.md` ; une décision structurante a son ADR. — `CLAUDE.md` « Documentation technique » + +### Règles de revue + +- Un changement qui rend faux un fait cité ici (sites d'appel réseau, hôtes de `connect-src`, commandes et déclencheurs de la CI, chemins du tableau Haut) met ce fichier à jour dans la même PR. — `CLAUDE.md` « Documentation technique » + +## Commandes de vérification + +Les workflows font foi ; ce tableau les résume au 2026-10-17. Le runner a une capacité de 1 : les jobs se suivent, et chaque maillon d'une pile a sa CI. — `CLAUDE.md` « CI/CD » + +| Workflow | Déclencheur | Commandes | +|---|---|---| +| `check-frontend.yml` | PR, sauf si tous ses fichiers sont exclus : `src-tauri/**`, `.cargo/**`, `docs/**`, `reports/**`, `tasks/**`, `.github/**`, `.claude/**`, `*.md` à la racine, `LICENSE`, les trois autres workflows | `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) | +| `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. + +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 audit --file src-tauri/Cargo.lock # depuis la racine : .cargo/audit.toml s'applique +``` + +- `npm test` lancé à la racine parcourt aussi `.claude/worktrees/` (`vite.config.ts` ne l'exclut pas) : vider ce dossier avant, ou lancer la suite depuis le worktree. — `.claude/skills/release/SKILL.md` (étape 0) +- Les gardes de `check-rust.yml` se rejouent en exécutant le bloc `run:` de leur étape depuis la racine, sous `bash --noprofile --norc -e -o pipefail`, le shell du runner dans un conteneur `ubuntu:22.04`. +- Windows, pour une dépendance, une feature ou du code sous `cfg(windows)`, sur un poste où cargo-xwin est déjà installé : `cargo xwin check --manifest-path src-tauri/Cargo.toml --locked --target x86_64-pc-windows-msvc`, précédé d'un témoin sur `main` (un témoin rouge met l'outil en cause, pas la PR). Installer cargo-xwin vaut acceptation de la licence du CRT et du SDK Windows de Microsoft : une revue ne l'installe jamais. (#455) +- Un `npm test` rejoué hors CI ne compte qu'après une sonde : un test volontairement rouge doit le faire sortir en code non nul. + +## Pièges de revue + +Les trois premiers viennent du skill global `pr-review`, corrigés pour ce dépôt. + +- **CI d'une pile.** Chaque maillon a sa CI depuis #232 : la section « CI coverage gap » du skill (CI réservée aux PR qui ciblent `main`) ne vaut pas ici. `check-rust` ne tourne que si le diff du maillon touche ses chemins : l'état Rust d'une pile est prouvé par le dernier maillon qui en a touché. — `CLAUDE.md` « CI/CD » +- **Un filtre `paths:` n'est pas prouvé par une PR qui touche le chemin exact.** Le glob (`src-tauri/**`) l'est par une PR qui touche un autre fichier du dossier, comme la PR #344 pour `check-rust`. — skill `pr-review` (section CI), #232 +- **CI Rust et revue en fork.** Un fork qui attend la CI pour poster son verdict peut s'arrêter sans rien publier (PR #303) : poster les constats, surveiller le statut depuis la session principale, relancer la revue au vert. `check-rust` prend environ 3 min 30 avec le cache et 10 min à froid, sans compter la file du runner. — skill `pr-review` (délégation), #234 +- **Un vert de `check-rust` ne dit rien de l'audit** : son étape `cargo audit` est `continue-on-error`. Lire le journal : `cargo-audit` écrit `1 vulnerability found` mais `N vulnerabilities found` (chercher `vulnerabilit(y|ies) found`), et le journal de `check-rust` contient des codes ANSI à retirer avant tout `grep`. Un `audit.yml` vert veut dire « zéro advisory hors de la liste acceptée », pas « zéro advisory ». — `CLAUDE.md` « CI/CD », [ADR 0018](docs/adr/0018-suppression-advisories-non-atteignables.md), #447 +- **Windows n'est compilé qu'à la release.** La CI des PR ne compile que pour Linux : sans `cargo xwin check`, une casse Windows apparaît le jour du tag. — `docs/architecture.md` « `release.yml` — Build et publication », #455 +- **Tests sans jsdom.** Un rendu par `renderToStaticMarkup` ne lance ni effet ni clic : ce qui en dépend n'est prouvé que par un test qui passe par `src/test/hookHarness.ts` (effets joués par `commit()`, gestionnaires appelés depuis l'arbre rendu). Le harnais ne rend pas de nouveau après un setter et laisse les enfants à l'état d'éléments : lire ce que le test exerce avant de le créditer. — `docs/architecture.md` « Tests (vitest sans jsdom) » +- **Faux `t`.** Un faux `t` qui rend la clé brute rend invisible un `t` oublié : les tests prennent `src/test/i18nMock.ts`, gardé par `src/__integration__/i18n-mock-guard.test.ts`. Jusqu'à #453, aucune garde ne détecte une clé présente dans une seule langue : comparer `fr.json` et `en.json`. — `docs/architecture.md` « Faux `t` marqué », #389 +- **Sous-chaîne du markup.** `not.toContain("disabled")` sur le HTML rendu bute sur une classe Tailwind (`disabled:opacity-50`) : tester l'attribut (`disabled=""`). — PR #452 +- **Booléen lié en texte.** tauri-plugin-sql stocke un booléen JS en texte `'true'`, qu'une condition `= 1` ne trouve pas : défaut resté invisible de la v0.1.0 à #390. — [ADR 0021](docs/adr/0021-migrations-profils-secondaires.md) +- **Transaction hors verrou.** Un `BEGIN` et son `COMMIT` envoyés par deux `db.execute` peuvent partir sur deux connexions du pool et laisser un verrou zombie (« database is locked », v0.10.0) : toujours `withTransaction` et son handle. — `docs/architecture.md` « Accès DB et transactions » +- **Dépôt public.** Aucune coordonnée d'un tiers (courriel, adresse, téléphone), aucune clé de licence ni aucun jeton dans un fichier commité, `STATE.md`, `docs/` et `.claude/skills/` compris ; un testeur se désigne par son identifiant Logto. — `.claude/skills/issue-license/SKILL.md`