Simpl-Resultat/REVIEW.md
le king fu c859405deb
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 2m4s
fix(release): refuse an empty or multi-document latest.json, test the Publish step
Follow-ups of the review of PR #418.

- check-latest-json.jq wants exactly one document and release.yml calls it
  with -s: under jq 1.6, the release container's, an empty file passed the
  check (no output, status 0) and two documents were judged on the last one,
  so the job would have deleted the published manifest and uploaded the bad
  one. Two tests cover both cases.
- build-latest-json.test.mjs runs the text of release.yml's "Publish
  latest.json to package registry" step against a fake curl: DELETE then PUT
  on a valid manifest, exit 1 and no request on a refused one.
- check-frontend no longer ignores .claude/** and release.yml (decision of
  Max): the test reads verify-release.sh and runs the Publish step, so "on
  every pull request" holds. REVIEW.md, CLAUDE.md and architecture.md follow.
- release.yml: stale ".deb fallback" comment fixed.
- qa-update-cycle.md: a tauri dev build on Linux shows TargetsNotFound,
  expected since #418.

Refs #461

Resolves #419

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 13:00:40 -04:00

29 KiB

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, ouvre une URL qui porte une donnée de l'utilisateur (donnée financière, identifiant, contenu d'un profil, ligne du Journal), ou retire ou contourne un consentement (celui des cours vit côté front : src/components/balance/PriceFetchControl.tsx, src/components/settings/PriceFetchConsentToggle.tsx) 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/<nom>" qui apparaît ou disparaît), ou change l'origine d'un paquet : hôte ou schéma de source (Cargo) ou de resolved (npm), avec ou sans nouvelle version, ou checksum, integrity changés sans nouvelle version Dépendances
**/.cargo/** toujours Dépendances
.forgejo/workflows/** fichier ajouté, ou diff qui ajoute une référence à secrets ou un déclencheur pull_request_target, qui exécute la définition de la base avec ses secrets pour toute PR, forks compris 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, par exemple : 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 ; étape d'installation modifiée (toolchain, binaire placé dans le PATH) ; if:, defaults:, permissions:, env: ou container: modifiés CI et gardes
.claude/settings.json, .mcp.json toujours : hooks, permissions et serveurs MCP décident de ce que les agents exécutent sur le poste, sans CI pour le voir 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) ;
  • dans check-rust.yml et check-frontend.yml : un déclencheur ajouté, sauf pull_request_target, 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 et src/services/dataExportService.ts, même quand le diff touche le verrou, le changement de profil ou les suppressions de la restauration (décision de Max du 2026-10-18, revue de #460) : contre leurs défauts, la mutation du niveau normal est le bon outil ;
  • 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, § 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. Le script qui les calcule est scripts/review-levels.py (API Forgejo, lecture seule) ; python3 scripts/review-levels.py --check vérifie, sans réseau, que ses motifs sont ceux du tableau Haut.

Une PR qui modifie cette section dit, pour chaque motif retiré ou assoupli, ce qu'il protégeait, met scripts/review-levels.py à jour dans le même diff et le rejoue 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, ADR 0020
  • 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. Ce consentement n'est vérifié que côté front (PriceFetchControl.tsx, PriceFetchConsentToggle.tsx) : fetch_price ne le revérifie pas. — ADR 0009, src-tauri/src/commands/balance_commands.rs
  • 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, § 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

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, 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, .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, .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, .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. Les deux dossiers dérivent de l'identifier de tauri.conf.json : le changer orpheline les bases des profils et, sous app_data_dir(), la clé de licence, le jeton d'activation et le fichier de repli des jetons OAuth. Le service du trousseau (KEYCHAIN_SERVICE de token_store.rs) est une constante alignée sur l'identifier par convention : il ne suit pas. — ADR 0021, ADR 0006, CLAUDE.md « Base de données », src-tauri/src/commands/license_commands.rs
  • Toute requête passe par le verrou unique de db.ts, et une transaction par withTransaction et son handle ; un changement de profil ferme puis rouvre la connexion. La restauration d'une sauvegarde (runRestore de dataExportService.ts) efface puis réinsère dans une seule transaction et ne notifie qu'après le COMMIT, une fois le verrou relâché. — ADR 0022, ADR 0005, docs/architecture.md « Accès DB et transactions »
  • Un booléen se lie en 1/0 (toSqlFlag), jamais en true/false. — ADR 0021
  • Une base par profil, PIN haché par Argon2. — ADR 0005
  • 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

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
  • 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/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
  • 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, § 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, § 1 et 3
  • Une fonctionnalité verrouillée reste visible, jamais masquée ; le gating bloque l'accès, jamais les données. — ADR 0017, § 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, § 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 <crate> --target <triple>, 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, 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 (CRATES), la version épinglée (PIN), l'ensemble attendu (EXPECTED) 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, 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é, 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 »
  • 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 (règle 3), ADR 0023
  • Une PR qui ne touche que audit.yml n'a aucune CI : les paths de check-rust.yml ne le listent pas, et le paths-ignore de check-frontend.yml l'exclut. Une PR qui ne touche que release.yml lance check-frontend, dont un test exécute le texte de l'étape « Publish latest.json to package registry » avec un curl factice (#419) ; le reste de release.yml ne s'exerce qu'au tag. 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é, une référence à secrets ajoutée ou un déclencheur pull_request_target 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-18. 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/**, *.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)
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 :

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é. Exception : fs_commands.rs compile par include_str! les deux CSV de src/__fixtures__/samples/, hors de ces chemins ; une PR qui les renomme, les déplace ou les supprime casse cargo check sans que check-rust tourne. — CLAUDE.md « CI/CD », src-tauri/src/commands/fs_commands.rs
  • 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, #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
  • 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