state: sync after #343/#344 (daily audit was red for 17 days, now green)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhPofpLQTZUbkVYR3i2e2D
This commit is contained in:
le king fu 2026-09-04 16:11:35 -04:00
parent 5154c5276b
commit 65f6c778a4

View file

@ -1,8 +1,8 @@
# STATE — Simpl'Résultat
> Derniere MAJ : 2026-08-15 (**PRs #321 et #322 reviewées, corrigées et mergées ff-only** (`main` `97d376b`) — les deux dormaient depuis 18 jours, chacune avec une revue du 2026-08-14 non traitée. **#321 APPROVE** : `plist 1.10.0` remonte `quick-xml` à `0.41.0`, la version corrigée, dans la borne existante de `tauri` → RUSTSEC-2026-0194/-0195 **résolues, plus acceptées**, `.cargo/audit.toml` réduit à `rsa` seul, `deep-link` dé-yanké. Point **inversé** par la revue de re-vérification : les 7 réaiguillages `windows-sys` **remontent** à `0.61.2`, soit l'état exact du tag `v0.14.0` (vérifié crate par crate) — c'était `main` qui portait la configuration jamais compilée sous Windows, la PR la supprime au lieu de l'ajouter. **#322 REQUEST_CHANGES puis corrigée avant merge** : la checklist QA inversait la séquence d'états Linux — `downloadAndInstall` télécharge **et** installe dans le même appel (`updater.rs:723-729`, `Finished` no-op en `useUpdater.ts:119-121`), donc l'invite pkexec s'ouvre pendant « Téléchargement en cours… » et `readyToInstall` n'arrive qu'**après** `dpkg -i` ; l'ordre des cases **neutralisait le prérequis polkit** que la page existe pour imposer (sans agent l'app se fige en `downloading`, le testeur rapporte « le téléchargement bloque »). #312/#313/#315 fermées. **#314 est caduque** : `audit.yml` tire tous les jours à 06:00 UTC, **19 runs `schedule` verts** depuis le 2026-07-28 — l'issue a été écrite le 07-27 au soir, avant que le premier tir programmé ait pu exister. CI runs 370/371 vertes, garde-fou d'audit tracé (`Suppression guard: 2 checks, exit 0`), **111 Rust**, 0 vulnérabilité. Candidate **v0.15.0**. Rappels infra : `main` est **protégée** (whitelist push `maximus`) + remote ancré `https://maximus@…`)
> Derniere MAJ : 2026-09-04 (**#343/#344 — l'audit quotidien était rouge depuis 17 jours et personne ne l'avait vu.** `audit.yml` échouait tous les matins depuis le 2026-08-19 (runs 375→391, dernier vert le 08-18) sur **une seule** vulnérabilité : RUSTSEC-2026-0258, `h2` 0.4.13, publiée le 08-17 — déni de service de sévérité faible, les frames HTTP/2 `DATA` vides sont mises en file sans limite, donc mémoire non bornée et panique possible sur dépassement. Aucun push sur `main` dans la fenêtre : c'est l'advisory-db qui a bougé, pas le code. **Atteignable sur les deux cibles livrées**, par deux chemins (`tauri-plugin-updater`→`reqwest 0.13.2` pour l'updater, et le `reqwest 0.12.28` direct de l'app pour licence/auth/cours/rétroaction) → règle 1 de `.cargo/audit.toml` : on corrige, on ne supprime pas. `h2` → **0.4.19**, `Cargo.toml` intouché, 666 paquets avant/après, `.cargo/audit.toml` et le garde-fou inchangés. **Le lock ne bouge que de 2 lignes** : `cargo update -p h2` réaiguille au passage les 7 pointeurs `windows-sys` de `0.61.2` vers 0.48/0.52/0.59 — la dérive même que #321 avait défaite 3 semaines plus tôt — donc seule la strophe `h2` a été appliquée, après re-vérification **crate par crate** contre `git show v0.14.0:src-tauri/Cargo.lock` plutôt que sur la foi de la note de #321. `/pr-review` **APPROVE**, et il a trouvé le point manquant : 0.4.19 resserre sa borne `http` de `^1` à `^1.1`, ce qui est exactement ce qui invalide un lock épinglé à la main — satisfait ici (`http 1.4.0`), plus checksum authentifié par `sha256sum` du `.crate`. **Audit vert sur `main`** prouvé par `workflow_dispatch` (run 393, `Job succeeded`, 666 crates, 22 warnings autorisés, 0 vulnérabilité), pas déduit du run de PR. Glob `src-tauri/**` **enfin prouvé** — le suivi ouvert depuis #232 peut être fermé. Nouveau suivi **#345**. Candidate **v0.15.0**.)
>
> Précédente MAJ : 2026-08-14 (**Chantier import CSV livré — milestone `planned-2026-08-12-import-csv-format` 10/10, pile de 10 PRs #333-#342 mergée ff-only** (`main` `37b832e`). Le symptôme rapporté par Max — « l'app oublie le format, y compris les colonnes montant positif/négatif » — n'était pas une faiblesse d'heuristique mais un **trou de persistance** : `import_sources` ne portait ni `amount_mode` ni `sign_convention` (ils n'existaient que sur `import_config_templates`), donc `useImportWizard.ts:323` réécrivait `signConvention: "negative_expense"` **en dur** à chaque restauration et une source réglée en montants positifs inversait tous ses montants au 2e import, sans erreur. **Migration v17** (4 colonnes + `CHECK`, backfill `LIKE '%debitAmount%'` reproduisant la règle runtime → aucune source ne change de comportement) ; codec unique `formatToRow`/`formatFromRow` à test de complétude ; `credit − debit` sur magnitudes ; `parseFrenchAmount` **ancré** ; détection par libellé d'en-tête ; score de confiance (seuil 90 %) ; **aperçu obligatoire à récap signé** ; signatures de banques + dérive de format ; sources et modèles enfin sauvegardés dans l'export SREF. **871 → 1181 vitest**, **106 → 111 Rust**. 20 tables / 24 index **inchangés** (v17 est un ALTER pur). ADR 0019. Candidate **v0.15.0**. Rappels infra : `main` est **protégée** (whitelist push `maximus`) + shadowing d'identité dans `~/.git-credentials` → remote ancré `https://maximus@…`)
> Précédente MAJ : 2026-08-15 (**PRs #321 et #322 reviewées, corrigées et mergées ff-only** (`main` `97d376b`) — les deux dormaient depuis 18 jours, chacune avec une revue du 2026-08-14 non traitée. **#321 APPROVE** : `plist 1.10.0` remonte `quick-xml` à `0.41.0`, la version corrigée, dans la borne existante de `tauri` → RUSTSEC-2026-0194/-0195 **résolues, plus acceptées**, `.cargo/audit.toml` réduit à `rsa` seul, `deep-link` dé-yanké. Point **inversé** par la revue de re-vérification : les 7 réaiguillages `windows-sys` **remontent** à `0.61.2`, soit l'état exact du tag `v0.14.0` (vérifié crate par crate) — c'était `main` qui portait la configuration jamais compilée sous Windows, la PR la supprime au lieu de l'ajouter. **#322 REQUEST_CHANGES puis corrigée avant merge** : la checklist QA inversait la séquence d'états Linux — `downloadAndInstall` télécharge **et** installe dans le même appel (`updater.rs:723-729`, `Finished` no-op en `useUpdater.ts:119-121`), donc l'invite pkexec s'ouvre pendant « Téléchargement en cours… » et `readyToInstall` n'arrive qu'**après** `dpkg -i` ; l'ordre des cases **neutralisait le prérequis polkit** que la page existe pour imposer (sans agent l'app se fige en `downloading`, le testeur rapporte « le téléchargement bloque »). #312/#313/#315 fermées. **#314 est caduque** : `audit.yml` tire tous les jours à 06:00 UTC, **19 runs `schedule` verts** depuis le 2026-07-28 — l'issue a été écrite le 07-27 au soir, avant que le premier tir programmé ait pu exister. CI runs 370/371 vertes, garde-fou d'audit tracé (`Suppression guard: 2 checks, exit 0`), **111 Rust**, 0 vulnérabilité. Candidate **v0.15.0**. Rappels infra : `main` est **protégée** (whitelist push `maximus`) + remote ancré `https://maximus@…`)
## Position actuelle
@ -18,6 +18,7 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
## Decisions recentes
- 2026-09-04 : **#343 ouverte, #344 mergée — l'audit quotidien rouge depuis 17 jours, corrigé (`main` `5154c52`).** Découvert au `/warmup` : `audit.yml` échouait tous les matins depuis le **2026-08-19** (runs 375→391), dernier vert le 08-18, **aucun push sur `main` dans l'intervalle**. Une seule vulnérabilité : **RUSTSEC-2026-0258** / GHSA-q83h-524g-xf6h, `h2` 0.4.13, datée du 08-17 — DoS de sévérité faible, les frames HTTP/2 `DATA` vides sont acceptées et mises en file **sans aucune limite**, donc sur un flux non drainé la mémoire croît sans borne et la longueur peut déborder en panique. Le délai 08-17→08-19 est la propagation dans l'advisory-db. Ces 17 jours **sont** le mode d'échec que l'ADR 0018 visait à empêcher — « alarme permanente = alarme ignorée » — cette fois par le haut : le gate criait, personne ne regardait. **Atteignabilité par triple explicite**, jamais `--target all` : `h2` est sur les **deux** cibles livrées par deux chemins, `hyper`←`hyper-rustls`←`reqwest 0.13.2`←`tauri-plugin-updater` (le chemin de la mise à jour automatique) et `hyper-tls`←`reqwest 0.12.28` déclaré directement en `Cargo.toml:43` (licence, auth, cours, rétroaction — 4 modules). Correctif publié + crate atteignable = **règle 1** de `.cargo/audit.toml`, donc corrigé et non suppressé ; ni le fichier de suppressions ni le garde-fou de `check-rust.yml` ne bougent. **Le piège, et la raison d'un diff de 2 lignes** : `cargo update -p h2` donne bien 0.4.19 mais réaiguille au passage 7 pointeurs `windows-sys` (`colored`, `dirs-sys`, `errno`, `rustix`, `rustls-platform-verifier`, `tempfile`, `winapi-util`) de `0.61.2` vers 0.48/0.52/0.59 — **exactement ce que #321 avait défait délibérément le 08-15**. Laisser passer aurait régressé cette décision en silence, 3 semaines après. Le lock a donc été restauré et **seule la strophe `h2`** appliquée (version + checksum), après avoir **retracé depuis la source** — `git show v0.14.0:src-tauri/Cargo.lock`, les 7 crates comparés un à un — que `0.61.2` est bien l'état du tag, donc la seule configuration `windows-sys` dont on ait la preuve qu'elle compile sous Windows ; relayer la note de #321 n'aurait pas suffi. `cargo metadata --locked` accepte le résultat **sans le réécrire**. **`/pr-review` APPROVE et a trouvé ce que j'avais manqué** : une strophe écrite à la main ne garantit plus ni checksum ni liste de dépendances, et `h2` 0.4.19 **resserre sa borne `http` de `^1` à `^1.1`** — le genre de changement qui invalide un lock épinglé ; satisfait ici (`http 1.4.0`), checksum authentifié par `sha256sum` du `.crate`, et nuance utile qu'il pose : `cargo metadata --locked` prouve le graphe, **pas** le checksum — c'est le `Downloaded`→`Compiling` du run 392 qui le prouve. **Preuves plutôt que déductions** : contrôle sur l'ancien lock (exit 1 sur RUSTSEC-2026-0258) montrant que l'outil local reproduit le rouge de la CI ; log de la CI lu ligne à ligne parce que l'étape `cargo audit` de `check-rust` est `continue-on-error: true` et qu'un job vert n'y prouve rien ; garde-fou tracé (`Suppression guard: 2 checks, exit 0`) ; **111 Rust** ; et `workflow_dispatch` d'`audit.yml` **sur `main` après merge** (run 393, `Job succeeded`) plutôt que d'inférer le vert du run de PR. **Deux acquis au passage** : (1) le **glob `src-tauri/**` est enfin prouvé** — cette PR est le cas test isolé attendu depuis #232 (lock + 2 `.md` seulement) : `check-rust` a tourné, `check-frontend` correctement sauté par sa denylist ; le suivi peut être fermé ; (2) **#345 ouverte** — la contrainte `windows-sys 0.61.2` en est à sa 3e rencontre (#310 l'introduit, #321 la défait, #344 l'évite à la main) et ne vit dans **aucun** ADR, aucun commentaire de `Cargo.toml`, aucun garde CI : seulement 3 lignes de prose de STATE.md et un message de commit, donc le prochain `cargo update` la régressera en silence. Merge via l'API Forgejo (rebase ff, SHA inchangé) plutôt qu'en local, ce qui évite la réconciliation manuelle des PRs. Aucune migration DB (v1→v17). (ref #343, #345, PR #344)
- 2026-08-15 : **#321 + #322 mergées ff-only (`main` `97d376b`) — deux PRs de 18 jours, deux revues du 08-14 jamais traitées, et une revue qui se corrige elle-même.** Passes de re-vérification lancées depuis le main loop (jamais en sous-agent, [[feedback-pr-review-subagent-forks]]) et séquencées plutôt que parallélisées — deux revues concurrentes partagent le même chemin de scratch et s'écrasent entre le Write et le POST (gotcha du chantier import). **Les deux passes ont retracé les claims depuis la source au lieu de relayer, et les deux ont trouvé une erreur dans la revue qu'elles re-vérifiaient.** (1) Sur #321, la revue du 08-14 lisait les 7 réaiguillages `windows-sys` comme une dérive de re-résolution et demandait un build Windows avant de tagger : **le sens est l'inverse** — #310 les avait fait *descendre* de `0.61.2` vers 0.48/0.52/0.59, cette PR les *remonte* à `0.61.2`, l'état exact du tag `v0.14.0` (`git show v0.14.0:src-tauri/Cargo.lock`, crate par crate). v0.14.0 taggée le 07-18, #310 mergée le 07-27 → le build NSIS de v0.14.0 a réellement compilé contre `0.61.2`. **La configuration non prouvée sous Windows était celle de `main`** ; la PR réduit le risque. (2) Sur #322, la revue du 08-14 annonçait « trois » déclencheurs et mappait `UpdateCard.tsx:201` à la page d'erreur — faux dans les deux sens : `:201` est le retry de l'état `error` de la carte, et `ErrorPage.tsx:82` est un **4e** site distinct qui **détecte seulement** (`handleCheckUpdate` s'arrête à `setUpdateStatus("available")`). Appliquer « écrire trois » aurait échangé une inexactitude contre une autre. **Le blocage de #322 était réel et non traité** : la checklist demandait de cocher `readyToInstall` **avant** l'invite d'authentification, alors que `download_and_install` fait `download(...).await?` puis `install(bytes)` dans le même appel (`updater.rs:723-729`), que `Finished` est un no-op côté UI (`useUpdater.ts:119-121`) et que `READY_TO_INSTALL` n'est dispatché qu'après le retour de `dpkg -i` — l'invite pkexec s'ouvre donc **par-dessus « Téléchargement en cours… »**. Conséquence exacte de l'ordre des cases : sans agent polkit l'app se fige en `downloading`, le testeur guette un blocage après `readyToInstall`, et rapporte « le téléchargement bloque » au lieu de « pas d'agent polkit » — **la page produisait la fausse trace qu'elle existe pour éliminer**. Corrigé en `97d376b` avec 3 mineurs (les 4 déclencheurs, les préfixes `/api/v1/packages/` (DELETE) vs `/api/packages/` (PUT) que `release.yml:217-219` commente déjà, ordre du changelog `SKILL.md`) + titre de PR (« step 9 » → « as step 10 »). Merge : rebase des 2 branches (15 commits de retard) en pile linéaire, **preuve que le rebase préserve `.cargo/`/`.forgejo/`/`Cargo.lock` au byte près** vs le commit validé par la CI run 338, puis CI re-jouée quand même (runs 370/371 verts, garde-fou **exécuté et tracé**, `cargo audit` 0 vulnérabilité / 22 warnings autorisés) et `cargo test` **111 passed** — le « 106 » de la description de #321 était périmé, le chantier import ayant ajouté 5 tests. Issues #312/#313/#315 auto-fermées par `Resolves`, PRs fermées à la main (Forgejo ne détecte pas un merge local comme *merged*), branches supprimées. **#314 trouvée caduque au passage** : `audit.yml` tire bien tous les jours à 06:00 UTC — 19 runs `schedule` verts du 07-28 au 08-15 — et son `workflow_dispatch` de référence datait du 07-27 à 23:56Z, soit ~6 h avant le premier tir programmé possible. L'issue a été écrite avant que le scheduler ait pu s'exprimer, pas après l'avoir vu échouer. (ref #312, #313, #314, #315, PRs #321/#322)
- 2026-08-14 : **Import CSV — chantier complet livré (10 issues, migration v17)**. Cycle intégral en une session : revue d'implémentation → `/spec` → `/review-spec` → `/plan-run` → `/autopilot` → `/pr-review` ×10 → merge ff-only. **Le diagnostic a tenu, mais trois choses ont été trouvées en cours de route que personne n'avait vues.** (1) Au re-ancrage Phase 3b : `dataExportService` ne sérialise ni `import_sources` ni `import_config_templates` et fait `DELETE FROM import_sources` à la restauration — **restaurer une sauvegarde détruisait toutes les configurations d'import**, 3e voie de perte du format, indépendante du bug racine (→ #331, + `withTransaction` que le service n'avait nulle part). (2) Un worker a **mesuré** que l'exemple « Solde 2024 » de l'issue n'est pas défectueux (`parseFloat` s'arrête sur le `S`) et a ajouté la fixture qui l'est vraiment. (3) Un autre a mesuré que chez RBC `CAD$` et un `Cheque Number` presque vide **sont** sparse-complementary : le générique les apparie et une ligne de chèque s'importe à −234,05 au lieu de −6,95 — d'où l'override par signature. **Correction de la revue initiale** : `parseFrenchAmount("50,00-")` ne rend pas 50 mais **5000** — `"100,00 CAD"` → 10000, `"1 234,56 CR"` → 123456 : erreur de **facteur 100** qui passait `isNaN` et comptait donc la ligne comme VALIDE. Le ×100 atteignait aussi l'import de titres #245. Durcissement global assumé : un relevé à suffixe de devise produit désormais des **lignes en erreur** plutôt que des valeurs fausses. **`/pr-review` a bloqué 3 des 10 PRs, tous fondés** : (a) #336 — la règle débit/crédit testait `isNaN(debit) && isNaN(credit)`, donc une cellule illisible à côté du remplissage `0,00` calculait 0 − 0 = 0 et importait en silence, **le bug même que la PR fermait, une cellule plus loin** (rejoué sur sa propre fixture : 6 transactions à 0,00 $, 0 erreur) ; (b) #337 — `detectDescriptionColumn` retournait la colonne préférée **sans veto par les données**, et le mot-clé `transaction` capturait la colonne DEBIT/CREDIT de Tangerine, tuant la catégorisation ; (c) #340 — **régression réelle** : la signature Desjardins est 4 libellés génériques appariés par sous-ensemble, et une signature court-circuitait le scan sparse-complementary → un fichier `Date;Description;Débit;Crédit;Montant;Solde` lu correctement AVANT devenait `positive_expense` APRÈS, chaque dépôt en dépense. Corrigés en 3 commits sur la tête de pile (`484c4be`, `ef7de3c`, `37b832e`), **chacun vérifié par mutation** — et cette vérification a révélé qu'un de mes propres tests **passait la mutation** (le fix sur les signatures génériques empêchait la branche gardée d'être atteinte) : il a fallu un fichier reconnu par une signature *légitime* pour l'exercer. Leçon : un garde qui ne peut pas échouer ne garde rien, y compris quand c'est soi qui l'écrit. Incident run : le worker #331 tué par une limite de session pendant sa validation — travail récupéré depuis son worktree et revalidé de zéro (le rationale de sa PR est une reconstruction, signalé comme tel). Gotcha parallélisation : deux agents de revue partageaient `scratchpad/review.md`, l'un a écrasé l'autre entre Write et POST → **donner un chemin de scratch unique par agent**. Suivis ouverts : refus du format à indicateur borné à `amountCol ± 1` ; dérives de comptes préexistantes dans `CLAUDE.md`/`architecture.md` (mesurées et documentées, non corrigées). (ref #323-#332, PRs #333-#342)
- 2026-07-27 : **#310 + #311 mergées — `cargo audit` 9→0, `npm audit` 3→2 acceptées** (`main` `a14258b`, ff-only ; PRs #316 → #318 stackées, CI verte des deux côtés). Parties d'un `/analyse-vulnerabilite` : rapport Défenseur **écarté comme source** (VPS injoignable — SSH Tailscale en attente d'auth navigateur, aucun lien imprimé ; rapport local du 2026-05-06, 82 jours) → vérité live = `cargo audit 0.22.2` + `npm audit` sur `main`, advisory-db `0bfde9d6`. **Le triage d'origine de #310 révisé sur un point** : `quick-xml` (2× 7.5 high) n'est **compilé sur aucune des deux cibles livrées** — `cargo tree -i` vide sur `x86_64-pc-windows-msvc` **et** `x86_64-unknown-linux-gnu`, présent seulement sur `x86_64-apple-darwin` via `plist`←`tauri` (dép. conditionnelle Apple). Il n'y avait donc **rien à attendre de Tauri**, contrairement au « probablement bloqué en amont » du corps. Méthode retenue : tracer **par triple explicite**, jamais `--target all` (qui répond « présent » pour des deps Apple jamais compilées ici) — mémoire [[reference-cargo-audit-reachability-par-cible]]. 6 advisories atteignables corrigées par `cargo update -p rustls-webpki -p tar` (0.103.9→**0.103.13** couvrant les 4, 0.4.44→**0.4.46**) sans toucher `Cargo.toml` ; dérive de lock expliquée et bornée (7 pointeurs `windows-sys` repointés vers des versions **déjà présentes**, 666 paquets avant/après, deps Windows-only → no-op sur Linux ; `--precise` donne le même résultat, c'est la re-résolution de cargo 1.94.1). Les 3 restantes → `.cargo/audit.toml` versionné, lu par les deux workflows sans qu'aucun ne le sache. **Le plan checker a levé un MAJOR fondé** : une liste de suppressions sans déclencheur de retrait **inverse** le problème de l'alarme (l'audit resterait vert si `quick-xml` redevenait atteignable) — d'où 3 règles en **ADR 0018** : admission sur preuve par cible livrée ou absence de correctif ; clé **par ID d'advisory jamais par crate** (une nouvelle advisory sur le même crate repasse au rouge) ; **garde-fou bloquant** dans `check-rust.yml`, placé après `cargo check` (index chaud) avec `--locked`, testant le **code de sortie séparément de la sortie** (un crate absent et un `cargo tree` en panne impriment tous deux du vide) + **canari** `tar`. Le pendant — suppression devenue *inutile* — est #312. **Leçon du 1er run CI** : le garde était **muet en cas de succès**, donc indiscernable dans les logs d'une étape non exécutée — exactement le mode d'échec silencieux que la PR combat (même famille que le « glob `src-tauri/**` non prouvé » de #232) → il logge maintenant chaque vérification (`Suppression guard: 4 checks, exit 0`, visible run 336). Preuve que `.cargo/audit.toml` est bien lu en conteneur : le `workflow_dispatch` de `audit.yml` sur la branche est **vert**, alors que le bump seul n'aurait éliminé que 6 des 9. **npm** (#311) : `postcss` 8.5.13→**8.5.23 sans override** — `vite` déclare `^8.5.3`, seul le lock était périmé (≠ cas #241 où le parent pinnait) ; `nanoid` suit dans sa borne ; **CSS émis identique octet pour octet** (postcss *est* le pipeline CSS, un build vert n'aurait prouvé que la compilation) ; `react-router` **accepté** — advisory mode **RSC**, or `App.tsx:109` monte un `BrowserRouter` client-only, et `react-router-dom` est **figé à 7.18.1** (v8 a fusionné le paquet dans `react-router`) donc le « fix » npm est un **downgrade** en 7.11.0 → sortir de la plage = migration, pas bump (#317, titre portant `react-router` pour la dédup par sous-chaîne de `/analyse-vulnerabilite`). Asymétrie à connaître : **aucun `npm audit` en CI**, donc rien ne rougit côté front — écrit dans `architecture.md` + `CLAUDE.md` pour que les 2 high permanents ne se lisent pas comme une régression. Gotchas Forgejo relevés : logs de job **uniquement via l'URL web** (l'API v1 répond 404 partout) et `head_branch` = `#<PR>` sur les runs `pull_request` → [[reference-forgejo-ci-logs-et-head-branch]]. 106 Rust + 871 vitest + builds verts. Aucune migration DB (v1→v16). (ref #310, #311, PRs #316/#318, #312-#315, #317)
@ -27,12 +28,11 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
- 2026-07-18 : **Epic #260 « rapports uniformes » fermée + #259 livrée en PR #296 (review)**. #260 fermée après `/analyze` : **4/5 rapports pleinement conformes** (income-statement + filtres partagés + collapse par-profil) ; les 3 écarts restants vs le texte de l'epic sont des **décisions de conception assumées**, entérinés par Max — (1) lignes vides budget non masquées (spec décision 6 : grille = surface d'édition) ; (2) collapse budget replié « comme partout » (#289/ADR 0016 inverse le « sauf budget » de l'epic) ; (3) dashboard convergé sur le modèle Cartes plutôt qu'une table income-statement hiérarchique (décision 5). **Correction STATE** : les entrées 07-08/07-11 « reste de #260 = #259 » sont fausses — #259 est une migration de taxonomie sans lien avec l'epic (recadrée 07-12), et les vraies déviations #260 n'y étaient pas tracées. **#259** (fusion des catégories custom) livrée en PR #296 (`issue-259-merge-custom-categories`) : bloc préservé de `StepSimulate` rendu en `MappingRow`, reducer `RESOLVE_ROW` résout rows+preserved (`unresolved` compté sur seed only → ne bloque pas « Suivant »), writer via helper `isResolvedTarget` (fourre-tout créé seulement s'il reste une custom non fusionnée ; customs fusionnées désactivées au lieu d'être re-parentées). Plan-check pass (6 MINOR, 3 intégrés), **836 vitest** + build tsc/vite propres, régression parent/enfant custom couverte, aucune migration DB. `/pr-review` **APPROVE**, mergée (rebase) le 2026-07-19 → `main` `2314a64`, #259 fermée, branche supprimée. (ref #260, #259 PR #296)
- 2026-07-18 : **Milestone collapse multi-niveaux (#288-291) livrée → v0.14.0**. Cycle complet en une session : `/plan-run` (spec 2 fichiers, 8 décisions drainées) → `/review-spec` (3 experts, **verdict 🔴**) → refonte v2 → `/autopilot` (4 workers) → `/pr-review` ×4 → ff-merge → release. **Le point clé** : la revue a tué l'algo v1. Il suivait un curseur de profondeur supposant un **ordre DFS** ; or la grille Budget (`useBudget.ts:361`) trie par **niveau** — les 3 experts l'ont trouvé indépendamment. Refonte v2 : **visibilité par remontée de `parent_id`** (une ligne visible ssi tous ses ancêtres dépliés), order-independant → résout d'un coup l'ordre budget, le tri par type qui sépare parent/enfant, la feuille « (direct) » qui partage la clé du parent, et la contrainte `visible()`-avant-`reorderRows`. **Pile linéaire forcée** (B/C/D dépendent tous du nouveau hook de A → pas de wave parallèle) : PRs #292-#295, chacune basée sur la précédente. `/pr-review` **APPROVE ×3 + 1 REQUEST_CHANGES** (#295 : ligne `- Spec:` de l'ADR 0016 pointant des specs gitignorées → 404 ; corrigé par **force-add des specs à la racine**, précédent repo `spec-refonte-rapports.md` ; au passage la revue s'est trompée en disant que 0015 n'avait pas de ligne Spec — elle en a une, cassée pareil, bug pré-existant signalé). **ff-merge** de la pile (4 issues auto-fermées via `Resolves #N`, milestone 4/4, 4 branches supprimées) → **v0.14.0** taggée. **Persistance migrée `localStorage` → `user_preferences`** (base du profil) : `deleteProfile` ne purge aucun `localStorage` → une clé par-profil y serait un résidu survivant à la suppression, révélant les catégories explorées d'un profil PIN-protégé (exigence privacy-first, **ADR 0016**, frontière tracée : état UI par-profil → DB profil, état UI machine → localStorage). Le hook gagne `defaultExpanded` + `storageKey` nullable, qui **unifie aussi les 2 arbres de catégories** (#290 : `CategoryTree` déplié-par-défaut, guide replié ; corrige le bug `allExpanded = size>0` du guide ; worker a trouvé un **4e consommateur** `StepDiscover` non listé au plan, migré). 828 vitest, aucune migration DB. Écarts protocole tracés au [rapport](reports/DAILY-REPORT-2026-07-15.md) : workers auto-validés **sans forker `/pr-review`** (évite le double-post [[feedback-pr-review-subagent-forks]]) ; #294 sans test (refactor de rendu, non testable sans jsdom) → vérif runtime déléguée à la revue. Reste `spec-ci-build-optimization` (2/4) + `spec-paiements` (#270/#271) + #259. (ref #288-291, PRs #292-#295)
- 2026-07-12 : **#259 recadrée — l'issue décrivait un flux qui n'existe pas** (rectifie les entrées des 2026-07-05 / 07-11 qui la classaient « mapping manuel compte sans similaire auto » et « reliquat de l'epic #260 » : les deux sont faux). Le corps d'origine, rédigé à chaud le 2026-07-05 pendant le run v0.12.0, avait **transposé par analogie** le signalement de Max vers le module Bilan sans ouvrir le fichier : il décrivait une auto-association des comptes standards aux comptes existants du profil dans `StarterAccountsModal`, avec « case désactivée quand aucun similaire n'est auto-identifié ». **Aucune notion de mapping n'existe dans ce flux** — la case est un simple « créer ce compte : oui/non », désactivée **quand une collision EST détectée** (`StarterAccountsModal.tsx:160`), soit la polarité inverse ; le commentaire `l.8` cité (« the matching checkbox ») désignait « la case **correspondante** », pas « la case de matching ». **Le vrai sujet** (confirmé par Max) est la **migration des catégories** : `computeMigrationPlan` range les catégories en 2 seaux et un seul est éditable — `plan.rows` (seed) passe par le moteur d'appariement + type-ahead par ligne (#246/#252), tandis que `plan.preserved` (catégories **custom**) est poussé avec `v1TargetId: null` **sans même être soumis au moteur d'appariement**, rendu en `<li>` texte brut (`StepSimulate.tsx:202-216`, aucun picker) et déversé d'office par le writer sous le fourre-tout « Catégories personnalisées (migration) ». Sémantique tranchée avec Max : **fusion** (choisir une feuille standard réassigne tx/budgets/mots-clés/fournisseurs et fait disparaître la custom ; ne rien choisir = comportement actuel, et ne doit **pas** bloquer le bouton « Suivant »). Re-parentage écarté. Travail = câblage sur 3 couches (UI `StepSimulate` → `MappingRow` réutilisable tel quel ; reducer `RESOLVE_ROW` qui ne voit que `plan.rows` ; 4 retouches du writer) — la machinerie de fusion est **déjà générique** (`buildMappingFromRows` filtre les cibles nulles, étapes 3-7 bouclent sur la Map). Complexité Medium. Piège à couvrir : fusionner une custom **parente ayant des enfants custom** (liste `preserved` plate → l'enfant non résolu retombe au fourre-tout, pas d'orphelin, mais aucun test ne le garantit). Leçon process : une issue rédigée par analogie en fin de run, sans lecture du code, peut inverser la prémisse **et** se tromper de module — le `/analyze` l'a rattrapée 6 jours plus tard. (ref #259)
- 2026-07-11 : **M2 « rapports-parite » (#277-#279) shippée** — dernière grosse tranche de l'epic #260 « rapports uniformes ». Run `/autopilot` (3 workers en worktree → PRs #285/#286/#287), reviewée via **3 `/pr-review` adversariales parallèles** (read-only, `git show` sans checkout) : **APPROVE #285** (grille budget income-first, #278) + **APPROVE #286** (BVA réel-vs-budget income-first, #277) + **REQUEST_CHANGES #287** (dashboard Cartes, #279). Blocage #287 = **le point I7 prédit au plan** confirmé réel : `getCartesSnapshot` propageait `accountIds` aux sous-rapports top-movers/budget mais **pas** aux séries `fetchMonthlyFlows` (KPIs/sparklines/overlay 12 mois) ni `fetchSeasonality` → sur le dashboard (1re page à exposer le filtre par-dessus ces séries) les KPI montraient des totaux non filtrés à côté de barres/tendance filtrées. **Fix** (`9ee5ad3`) : threader `accountIds?` dans les 2 fetchers (`source_id IN (...)` paramétré via `inPlaceholders`, index `$3`/`$4`) + câblage `getCartesSnapshot` ; le CHANGELOG #279 promettait déjà le filtrage → **code aligné sur la promesse, texte inchangé** ; +2 assertions cartes. **Merge local de la pile** (3 merges `--no-ff`, conflit additif CHANGELOG ×2 résolu par union #277/#278/#279 — i18n **non conflictuel** car #277/#278 réutilisent des clés existantes, seul #279 touche les locales ; STATE non conflictuel car branches ne l'ont pas commité) → tip cumulé validé (tsc + vite build + **811 vitest** + cargo check + **98 Rust**) → push `main` `a982f9e`. Réconciliation Forgejo : 3 issues auto-fermées via `Resolves #N`, 3 PRs fermées manuellement (merges locaux non détectés *merged*), milestone `overnight-2026-07-08-rapports-parite` **3/3 fermée**, branches supprimées, worktrees leftover nettoyés (gotcha recursion). Aucune migration DB (v1→v16), non taggé ([Unreleased]). Wrinkle process : le fork du skill `/pr-review` avait posté un APPROVE prématuré sur #287 (I7 gradé non bloquant) → superséé + commentaire stale supprimé par la passe adversariale indépendante (trace code + « 1re page à exposer le filtre » + CHANGELOG à honorer) ; valeur de la double-vérif indépendante. Suivi non bloquant : `getDashboardSummary` = code mort à retirer. Reste de #260 : #259 (mapping manuel compte sans similaire auto). (ref #260, #277-#279, PRs #285-#287)
## Blockers actifs
- Aucun blocker externe dur. `spec-monetisation` **fermée 12/12** (2026-07-08) — #50/#52/#53/#135/#136 livrées, ne sont plus des blockers.
- Backlog ouvert (`status:ready`, non bloqué) : `spec-paiements` (#270 activation /v1 + product explicite + URL achat localisée — c'est elle qui activera le CTA « Obtenir » des UpsellGate, aujourd'hui désactivé « bientôt disponible » ; #271 absorbé par #301, livré) ; `spec-ci-build-optimization` **3/4** (#232 livrée le 2026-07-27 ; reste **#234** connectivité du serveur de cache runner, qui débloquerait `Swatinem/rust-cache` — accès hôte VPS requis).
- Suivis des advisories : **#312, #313 et #315 fermées le 2026-08-15** (PRs #321/#322 mergées). **#314 fermée le 2026-08-15, prémisse infirmée** — elle affirmait « zéro run `schedule` », or `audit.yml` tire tous les jours à 06:00 UTC (**19 runs `schedule` verts** du 07-28 au 08-15, sans trou) ; elle a été écrite le 07-27 vers 23:56Z, ~6 h avant le premier tir programmé possible, et décrivait donc une fenêtre trop courte, pas un scheduler en panne. Ces 19 runs confirment au passage que `.cargo/audit.toml` est bien lu en conteneur **sur `main`** — l'ADR 0018 ne l'avait vérifié que par un `workflow_dispatch` sur une branche. Restent **#317** (re-évaluer `react-router` à une migration v8) et **#319** (déclencheur de retrait pour `rsa`, désormais **seule** entrée de `.cargo/audit.toml` — le garde-fou est passé à 2 vérifications).
- Backlog ouvert (`status:ready`, non bloqué) : `spec-paiements` (#270 activation /v1 + product explicite + URL achat localisée — c'est elle qui activera le CTA « Obtenir » des UpsellGate, aujourd'hui désactivé « bientôt disponible » ; #271 absorbé par #301, livré) ; `spec-ci-build-optimization` **3/4** (#232 livrée le 2026-07-27 ; reste **#234** connectivité du serveur de cache runner, qui débloquerait `Swatinem/rust-cache` — accès hôte VPS requis). Le suivi « glob `src-tauri/**` non prouvé » ouvert avec #232 est **levé** : la PR #344 est le cas test isolé attendu (`Cargo.lock` + 2 `.md` seulement) — `check-rust` a bien tourné, `check-frontend` a bien été sauté par sa denylist.
- Suivis des advisories : **#312, #313 et #315 fermées le 2026-08-15** (PRs #321/#322 mergées). **#314 fermée le 2026-08-15, prémisse infirmée** — elle affirmait « zéro run `schedule` », or `audit.yml` tire tous les jours à 06:00 UTC (**19 runs `schedule` verts** du 07-28 au 08-15, sans trou) ; elle a été écrite le 07-27 vers 23:56Z, ~6 h avant le premier tir programmé possible, et décrivait donc une fenêtre trop courte, pas un scheduler en panne. Ces 19 runs confirment au passage que `.cargo/audit.toml` est bien lu en conteneur **sur `main`** — l'ADR 0018 ne l'avait vérifié que par un `workflow_dispatch` sur une branche. **#343 fermée le 2026-09-04** (PR #344) — RUSTSEC-2026-0258 sur `h2`, la seule vulnérabilité qui rougissait l'audit depuis le 08-19 ; l'audit est de nouveau vert sur `main`, prouvé par `workflow_dispatch` (run 393) et non déduit du run de PR. Restent **#317** (re-évaluer `react-router` à une migration v8), **#319** (déclencheur de retrait pour `rsa`, désormais **seule** entrée de `.cargo/audit.toml` — le garde-fou est passé à 2 vérifications) et **#345** (domicilier la contrainte `windows-sys 0.61.2`, qui ne vit dans aucun ADR, aucun commentaire ni aucun garde CI et sera régressée au prochain `cargo update` — 3e rencontre après #310 et #321).
- Nouveau bug ouvert : **#320** — les installations `.rpm` ne reçoivent aucune mise à jour (`latest.json` ne porte qu'une entrée `linux-x86_64` construite depuis le `.deb`, `release.yml:104-122`), alors que les `.rpm` continuent d'être publiés en assets. Documenté en §3 de `docs/qa-update-cycle.md` (« ne pas tester : cassé, pas seulement non vérifié »).
- Suivis ouverts du chantier import (milestone fermée) : le refus du format « montant absolu + indicateur D/C » ne scanne que `amountCol ± 1`, donc une colonne D/C placée ailleurs passe encore ; dérives de comptes préexistantes dans `CLAUDE.md` et `docs/architecture.md` (composants, pages, services, `Version actuelle : 0.6.3` vs tag v0.14.0) mesurées et documentées en revue, non corrigées.
- Release à considérer : le `[Unreleased]` porte le feature-gating, les deux lots d'advisories **et** le chantier import (migration v17) → candidate `v0.15.0` via `/release`. **L'étape 10 s'appliquera** : ces lots touchent `tauri-plugin-updater` et ses dépendances (`rustls-webpki`, `tar`, `plist`/`quick-xml`, `deep-link`), donc `docs/qa-update-cycle.md` est à dérouler pour de vrai — deux machines de bureau, une clé Base+ sur chacune, partir de la version précédente. Et #320 reste ouverte : les installations `.rpm` ne se mettront pas à jour, quelle que soit la release.