state: sync after #310 + #311 merge (deps advisories cleared)

This commit is contained in:
le king fu 2026-07-27 20:41:01 -04:00
parent a14258b147
commit 81804bb94c

View file

@ -1,6 +1,6 @@
# STATE — Simpl'Résultat # STATE — Simpl'Résultat
> Derniere MAJ : 2026-07-27 (**CI optimisée — #232 mergée** (`main` `7779f7d`, milestone `spec-ci-build-optimization` **3/4**) : split `check.yml``check-rust.yml` + `check-frontend.yml` + `audit.yml`, caches Actions morts retirés (#234 non résolu), `cargo-audit` en binaire pré-buildé. Mesuré : **rust 21m44 → 8m55**, **PR frontend-only 24m12 → 1m43** (93 %). Trou de couverture fermé : les PR stackées ne recevaient aucune CI (#305-#308 en avaient zéro). Retrait du `|| true` a découvert **9 advisories RUSTSEC**#310 ouverte ; `audit.yml` sera rouge tant qu'elle n'est pas traitée. Précédente MAJ 2026-07-21 : **Feature-gating par tier shippé sur `main`** — milestone `planned-2026-07-19-feature-gating` **6/6 fermée** : socle LicenseProvider/`useEntitlement` (#297, PR #303 mergée après fix review), garde UI RequireFeature+UpsellGate (#298), routes+Sidebar gatées (#299), multi-profils Base+ non destructif (#300), Rust auto-update Base+ + `features[]` fail-closed + Cargo feature `dev-override` (#301), docs ADR 0017 (#302). Pile #304-#308 `/pr-review` **APPROVE ×5**, ff-merge `main` `6de9617` après validation du tip cumulé (**871 vitest** + **106 Rust**, build propre). [Unreleased], non taggé — v0.14.0 reste le dernier tag. Aucune migration DB (v1→v16). Découverte infra : `main` est **protégée** (whitelist push `maximus`, règle du 2026-03-07) + shadowing d'identité dans `~/.git-credentials` (`defenseur-auto-bot` en 1re position) → remote du repo ancré `https://maximus@…`) > Derniere MAJ : 2026-07-27 (**Advisories de dépendances traitées — #310 + #311 mergées** (`main` `a14258b`, ff-only, PRs #316/#318 stackées) : **`cargo audit` 9 → 0** et **`npm audit` 3 → 2 acceptées**. Les 6 advisories Rust atteignables tombent par `cargo update -p rustls-webpki -p tar` (0.103.13 / 0.4.46, bornes existantes, `Cargo.toml` intact) ; les 3 restantes sont **hors surface produit**`quick-xml` ×2 n'est compilé sur **aucune cible livrée** (Apple-only via `plist`, ce qui révise le triage d'origine « bloqué en amont ») et `rsa` n'a aucun correctif publié ni aucun arbre — et passent en liste acceptée `.cargo/audit.toml`, **par ID d'advisory jamais par crate**, avec **garde-fou bloquant** dans `check-rust.yml` rejouant la preuve à chaque PR Rust (exit status testé séparément de la sortie + canari + log de chaque vérification). ADR 0018. Côté npm : `postcss` 8.5.23 **sans override** (la borne de vite l'autorisait déjà), CSS émis identique octet pour octet ; `react-router` accepté (mode RSC inatteignable, `react-router-dom` figé à 7.18.1). 5 suivis : #312-#315, #317. [Unreleased], non taggé — v0.14.0 reste le dernier tag ; candidate **v0.15.0**. Aucune migration DB (v1→v16). Précédente MAJ 2026-07-27 : **CI optimisée — #232 mergée** (`7779f7d`, `spec-ci-build-optimization` 3/4) — split `check.yml``check-rust.yml` + `check-frontend.yml` + `audit.yml`, caches Actions morts retirés (#234 non résolu), `cargo-audit` pré-buildé ; **rust 21m44 → 8m55**, **PR frontend-only 24m12 → 1m43**. Rappels infra : `main` est **protégée** (whitelist push `maximus`) + shadowing d'identité dans `~/.git-credentials` → remote ancré `https://maximus@…`)
## Position actuelle ## Position actuelle
@ -8,7 +8,7 @@ v0.9.1 shippée (2026-05-10). Milestones `spec-refonte-rapports`, `spec-refonte-
Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA + UX). Étape 0 — quick wins terminologie « catégorie »→« type », symbole optionnel, date de snapshot déplaçable — mergée (#201, issues #198/#199/#200 fermées). Chantier structurel = Étapes 1-2 de l'audit (séparation véhicule fiscal × classe d'actif via `vehicle_type`, puis détail par titre `balance_securities`+holdings+`book_cost`, bascule agrégé→détaillé, réouverture ADR 0012). **Étape 1 livrée** (sprint 2026-06-01 : PRs #206-#209 mergées, milestone `overnight-2026-06-01-bilan-axe-vehicule` fermée 4/4) — `vehicle_type` nullable = enveloppe fiscale portée par le compte, catégorie = pure classe d'actif (5 classes), migrations additives v12/v13 (v1-v11 intactes), renommage via `custom_label` (fix bug I), axe graphique classe/enveloppe + rendements repliables persistés ; ADR 0014 Accepted, ADR 0012 Rejected. CHANGELOG sous [Unreleased] (pas encore taggé). **Étape 2 livrée** (merge manuel 2026-06-09 : pile de 9 PRs stackées #219-#227 mergées bottom-up, milestone `overnight-2026-06-05-bilan-detail-titres` à 9/10, issues #210-#218 fermées) — détail par titre via `balance_securities` + `balance_snapshot_holdings`, `balance_accounts.kind` ('simple'|'detailed') + `detailed_since`, migrations additives v14/v15/v16 (v1-v13 intactes), conversion des comptes cotés existants en détaillés 1-position (v16), service securities transactionnel, reducer holdings + dispatch `account.kind`, UI multi-titres (SecurityPicker), assistant détailler-un-compte (date pivot), drill-down par titre + gain latent, tests intégration/régression ; ADR 0015 Accepted. **#228 mergé** (PR #229, fix-forward : garde d'abort v16 scopée aux comptes convertibles via `JOIN balance_categories` + `c.asset_type IS NOT NULL` dans les 3 copies — Migration v16 / V16_SQL / V16_CORRUPT — + test régression ; CI vert car #229 ciblait `main`) → milestone `overnight-2026-06-05-bilan-detail-titres` complète **10/10 et fermée**. **v0.10.0 shippée** (2026-06-29 : Étapes 1+2 du bilan, migrations v12→v16) **puis hotfix v0.10.1** (2026-06-30, PR #230, déployé) — corrige un « database is locked » introduit en 0.10.0 (apparaissait après l'abandon d'un snapshot en cours) : tout l'accès DB est désormais sérialisé via `withTransaction` dans `db.ts` (tauri-plugin-sql = pool sqlx multi-connexions sans primitive de transaction JS → `BEGIN`/`COMMIT` en `db.execute` séparés pouvaient strander une transaction d'écriture = verrou zombie), appliqué aux 5 sites transactionnels ; + console de log live-update + API `logInfo/logWarn/logError`. 20 tables / 24 index, 16 migrations (v1→v16). Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA + UX). Étape 0 — quick wins terminologie « catégorie »→« type », symbole optionnel, date de snapshot déplaçable — mergée (#201, issues #198/#199/#200 fermées). Chantier structurel = Étapes 1-2 de l'audit (séparation véhicule fiscal × classe d'actif via `vehicle_type`, puis détail par titre `balance_securities`+holdings+`book_cost`, bascule agrégé→détaillé, réouverture ADR 0012). **Étape 1 livrée** (sprint 2026-06-01 : PRs #206-#209 mergées, milestone `overnight-2026-06-01-bilan-axe-vehicule` fermée 4/4) — `vehicle_type` nullable = enveloppe fiscale portée par le compte, catégorie = pure classe d'actif (5 classes), migrations additives v12/v13 (v1-v11 intactes), renommage via `custom_label` (fix bug I), axe graphique classe/enveloppe + rendements repliables persistés ; ADR 0014 Accepted, ADR 0012 Rejected. CHANGELOG sous [Unreleased] (pas encore taggé). **Étape 2 livrée** (merge manuel 2026-06-09 : pile de 9 PRs stackées #219-#227 mergées bottom-up, milestone `overnight-2026-06-05-bilan-detail-titres` à 9/10, issues #210-#218 fermées) — détail par titre via `balance_securities` + `balance_snapshot_holdings`, `balance_accounts.kind` ('simple'|'detailed') + `detailed_since`, migrations additives v14/v15/v16 (v1-v13 intactes), conversion des comptes cotés existants en détaillés 1-position (v16), service securities transactionnel, reducer holdings + dispatch `account.kind`, UI multi-titres (SecurityPicker), assistant détailler-un-compte (date pivot), drill-down par titre + gain latent, tests intégration/régression ; ADR 0015 Accepted. **#228 mergé** (PR #229, fix-forward : garde d'abort v16 scopée aux comptes convertibles via `JOIN balance_categories` + `c.asset_type IS NOT NULL` dans les 3 copies — Migration v16 / V16_SQL / V16_CORRUPT — + test régression ; CI vert car #229 ciblait `main`) → milestone `overnight-2026-06-05-bilan-detail-titres` complète **10/10 et fermée**. **v0.10.0 shippée** (2026-06-29 : Étapes 1+2 du bilan, migrations v12→v16) **puis hotfix v0.10.1** (2026-06-30, PR #230, déployé) — corrige un « database is locked » introduit en 0.10.0 (apparaissait après l'abandon d'un snapshot en cours) : tout l'accès DB est désormais sérialisé via `withTransaction` dans `db.ts` (tauri-plugin-sql = pool sqlx multi-connexions sans primitive de transaction JS → `BEGIN`/`COMMIT` en `db.execute` séparés pouvaient strander une transaction d'écriture = verrou zombie), appliqué aux 5 sites transactionnels ; + console de log live-update + API `logInfo/logWarn/logError`. 20 tables / 24 index, 16 migrations (v1→v16).
**Milestone `deps-security-2026-07` livrée** (2026-07-01, 4/4 fermée) — 4 vulns de dépendances Defenseur remédiées en 2 PRs (#239 `react-router-dom` 7.18.1 ; #240 `vite` 6.4.3 + `vitest` 4.1.9) ; `npm audit` 5→**0** — le dernier `@babel/core` low remédié le 2026-07-04 (override scopé `@vitejs/plugin-react`→`@babel/core@^7.29.7`, issue #241 / PR #242 mergée). Nouveau milestone `spec-ci-build-optimization` (2/4) ouvert (baseline #231 : cause #2 = `reserveCache timeout` du runner ; #234 connectivité cache VPS, #232 split workflows). **Milestone `deps-security-2026-07` livrée** (2026-07-01, 4/4 fermée) — 4 vulns de dépendances Defenseur remédiées en 2 PRs (#239 `react-router-dom` 7.18.1 ; #240 `vite` 6.4.3 + `vitest` 4.1.9) ; `npm audit` 5→**0** — le dernier `@babel/core` low remédié le 2026-07-04 (override scopé `@vitejs/plugin-react`→`@babel/core@^7.29.7`, issue #241 / PR #242 mergée). **Ce « 0 » n'est plus l'état courant** : des advisories publiées depuis l'ont ramené à 3, traitées en #311 le 2026-07-27 → **2 restantes, acceptées** (`react-router`, cf. entrée du jour). Nouveau milestone `spec-ci-build-optimization` (2/4) ouvert (baseline #231 : cause #2 = `reserveCache timeout` du runner ; #234 connectivité cache VPS, #232 split workflows).
**v0.11.0 shippée** (2026-07-05, tag `v0.11.0` → CI release Windows/Linux + JSON updater) — milestone `overnight-2026-07-05-bilan-rapports-ux` complétée **5/5 et fermée** via un run `/autopilot` (5 workers séquentiels, PRs #248-#252 toutes `/pr-review` APPROVE, puis merge local de la pile + réconciliation Forgejo). Contenu : rapport comparable réel-vs-réel **hiérarchique** avec sous-totaux (#247) ; **netting des transferts** ciblé (catégories type `transfer` → SUM signé ~0, autres types inchangés au byte) dans le compare (#243) ; landing Bilan en **tuiles** `HubReportNavCard` + guard empty-state découplé + gestion comptes accessible partout (#244) ; **import CSV de titres** dans un snapshot détaillé (front pur, `autoDetectHoldingColumns`, prix flexible) (#245) ; cible de **migration catégories éditable** sur chaque ligne + type-ahead `CategoryCombobox` feuilles-seulement (#246). Aucune migration DB (v1→v16 inchangées), 20 tables / 24 index, 16 migrations. Point de suivi non bloquant : nets de transfert signés traversent du code UI compare pensé pour des magnitudes positives (mord seulement les transferts déséquilibrés à une jambe). **v0.11.0 shippée** (2026-07-05, tag `v0.11.0` → CI release Windows/Linux + JSON updater) — milestone `overnight-2026-07-05-bilan-rapports-ux` complétée **5/5 et fermée** via un run `/autopilot` (5 workers séquentiels, PRs #248-#252 toutes `/pr-review` APPROVE, puis merge local de la pile + réconciliation Forgejo). Contenu : rapport comparable réel-vs-réel **hiérarchique** avec sous-totaux (#247) ; **netting des transferts** ciblé (catégories type `transfer` → SUM signé ~0, autres types inchangés au byte) dans le compare (#243) ; landing Bilan en **tuiles** `HubReportNavCard` + guard empty-state découplé + gestion comptes accessible partout (#244) ; **import CSV de titres** dans un snapshot détaillé (front pur, `autoDetectHoldingColumns`, prix flexible) (#245) ; cible de **migration catégories éditable** sur chaque ligne + type-ahead `CategoryCombobox` feuilles-seulement (#246). Aucune migration DB (v1→v16 inchangées), 20 tables / 24 index, 16 migrations. Point de suivi non bloquant : nets de transfert signés traversent du code UI compare pensé pour des magnitudes positives (mord seulement les transferts déséquilibrés à une jambe).
@ -16,6 +16,7 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
## Decisions recentes ## Decisions recentes
- 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)
- 2026-07-27 : **#232 mergée — CI split, caches morts retirés, `cargo-audit` pré-buildé** (`main` `7779f7d`, PR #309 rebase, milestone `spec-ci-build-optimization` **3/4**). `/analyze` a confirmé la prémisse mais **révisé le cadrage sur 3 points mesurés** : (1) les 2 jobs ne tournent **pas en parallèle** — runner à capacité 1, `frontend` démarre à la seconde où `rust` finit sur tous les runs de l'historique → chaque PR payait rust+frontend ≈ 24,5 min ; (2) **3 des 40 derniers commits first-parent** touchent `src-tauri/`, dont 2 `chore: release` (push sur `main`, ne déclenche pas la CI) → le path-filter est le **gain dominant**, pas un effet de bord ; (3) le risque « required check skippé » est **nul** (`enable_status_check: false` sur la protection de `main`, vérifié API). Chrono re-mesuré du run 326 : **12m15s de gaspillage sur 21m44** — save `target/` 6m11 + save registry 43s (`reserveCache failed`, cause #234) + `cargo install cargo-audit` 4m41 + restores ~40s ; **nouveau vs baseline #231** : le restore **timeout aussi** désormais (`getCacheEntry failed`), le 30 juin n'avait qu'un MISS propre. Résultat : **rust 21m44 → 8m55** (59 %), **frontend 2m28 → 1m43**, **PR frontend-only 24m12 → 1m43** (93 %). **Trou de couverture fermé** : `branches: [main]` ne matchait pas une PR stackée → **#305-#308 (pile feature-gating) n'ont eu aucune CI**, seules #303/#304 (base `main`) ont tourné ; plus aucun filtre `branches:`. 2 écarts assumés vs le corps d'origine, tranchés sur mesure : cache npm retiré aussi (23s/run) et **denylist** pour le frontend (job à 2,5 min → le faire tourner pour rien est bon marché, ne PAS le faire tourner silencieusement ne l'est pas ; le job cher garde un allowlist strict). `/pr-review` **APPROVE** — a récupéré les logs CI plutôt que juger le diff, et levé que l'override `PATH` job-level aurait pu masquer le binaire d'`install-action` (vérifié : audit bien exécuté) ; 2 des 6 suggestions appliquées (`.claude/**` au denylist, ref `check.yml` périmée dans le skill `release`). **Skip prouvé empiriquement** : le 2e push (ni `src-tauri/**` ni `check-rust.yml`) n'a **pas** déclenché `check-rust`. **Reste non prouvé — le glob `src-tauri/**` lui-même** (seule l'entrée chemin-exact a matché) : mode d'échec **silencieux** sur la PR Rust (1/40, ni `cargo check` ni `cargo test`) → #310 (`cargo update` touchant `Cargo.lock` seul) sera le test isolé, à surveiller. Le retrait du `|| true` a **découvert 9 advisories RUSTSEC réelles** (préexistantes, seulement masquées) → **#310** ouverte : `rustls-webpki` ×4 + `tar` ×2 atteignables via `tauri-plugin-updater`/`reqwest` (correctifs **patch**), `quick-xml` ×2 bloqué en amont par `plist`←`tauri` (bump majeur), `rsa` sans correctif publié mais seul parent `sqlx-mysql` — absent de tout arbre de compilation (`cargo tree -i rsa --target all` vide, projet SQLite) donc vraisemblablement **inatteignable**. Conséquence : `audit.yml` (quotidien, bloquant par choix) sera **rouge dès son 1er run** tant que #310 n'est pas traitée — alarme permanente = alarme ignorée, donc à lander tôt. Aucune migration DB (v1→v16). (ref #232, PR #309, #310) - 2026-07-27 : **#232 mergée — CI split, caches morts retirés, `cargo-audit` pré-buildé** (`main` `7779f7d`, PR #309 rebase, milestone `spec-ci-build-optimization` **3/4**). `/analyze` a confirmé la prémisse mais **révisé le cadrage sur 3 points mesurés** : (1) les 2 jobs ne tournent **pas en parallèle** — runner à capacité 1, `frontend` démarre à la seconde où `rust` finit sur tous les runs de l'historique → chaque PR payait rust+frontend ≈ 24,5 min ; (2) **3 des 40 derniers commits first-parent** touchent `src-tauri/`, dont 2 `chore: release` (push sur `main`, ne déclenche pas la CI) → le path-filter est le **gain dominant**, pas un effet de bord ; (3) le risque « required check skippé » est **nul** (`enable_status_check: false` sur la protection de `main`, vérifié API). Chrono re-mesuré du run 326 : **12m15s de gaspillage sur 21m44** — save `target/` 6m11 + save registry 43s (`reserveCache failed`, cause #234) + `cargo install cargo-audit` 4m41 + restores ~40s ; **nouveau vs baseline #231** : le restore **timeout aussi** désormais (`getCacheEntry failed`), le 30 juin n'avait qu'un MISS propre. Résultat : **rust 21m44 → 8m55** (59 %), **frontend 2m28 → 1m43**, **PR frontend-only 24m12 → 1m43** (93 %). **Trou de couverture fermé** : `branches: [main]` ne matchait pas une PR stackée → **#305-#308 (pile feature-gating) n'ont eu aucune CI**, seules #303/#304 (base `main`) ont tourné ; plus aucun filtre `branches:`. 2 écarts assumés vs le corps d'origine, tranchés sur mesure : cache npm retiré aussi (23s/run) et **denylist** pour le frontend (job à 2,5 min → le faire tourner pour rien est bon marché, ne PAS le faire tourner silencieusement ne l'est pas ; le job cher garde un allowlist strict). `/pr-review` **APPROVE** — a récupéré les logs CI plutôt que juger le diff, et levé que l'override `PATH` job-level aurait pu masquer le binaire d'`install-action` (vérifié : audit bien exécuté) ; 2 des 6 suggestions appliquées (`.claude/**` au denylist, ref `check.yml` périmée dans le skill `release`). **Skip prouvé empiriquement** : le 2e push (ni `src-tauri/**` ni `check-rust.yml`) n'a **pas** déclenché `check-rust`. **Reste non prouvé — le glob `src-tauri/**` lui-même** (seule l'entrée chemin-exact a matché) : mode d'échec **silencieux** sur la PR Rust (1/40, ni `cargo check` ni `cargo test`) → #310 (`cargo update` touchant `Cargo.lock` seul) sera le test isolé, à surveiller. Le retrait du `|| true` a **découvert 9 advisories RUSTSEC réelles** (préexistantes, seulement masquées) → **#310** ouverte : `rustls-webpki` ×4 + `tar` ×2 atteignables via `tauri-plugin-updater`/`reqwest` (correctifs **patch**), `quick-xml` ×2 bloqué en amont par `plist`←`tauri` (bump majeur), `rsa` sans correctif publié mais seul parent `sqlx-mysql` — absent de tout arbre de compilation (`cargo tree -i rsa --target all` vide, projet SQLite) donc vraisemblablement **inatteignable**. Conséquence : `audit.yml` (quotidien, bloquant par choix) sera **rouge dès son 1er run** tant que #310 n'est pas traitée — alarme permanente = alarme ignorée, donc à lander tôt. Aucune migration DB (v1→v16). (ref #232, PR #309, #310)
- 2026-07-21 : **Milestone `planned-2026-07-19-feature-gating` livrée 6/6 et fermée — le gating par tier est sur `main`** ([Unreleased], candidate v0.15.0). Reprise du run `/autopilot` interrompu le 19 au soir (mort après la PR #303 ; worker #298 tué sans commit — 2 worktrees + branche vide nettoyés). Séquence : `/pr-review` #303 **REQUEST_CHANGES** (le retry backoff CWE-703 s'armait aussi sur un échec de `submitKey` → l'auto-refresh effaçait le message « clé invalide » ~1 s après ; et `status: "error"` persistant aurait laissé `ready=false` à jamais pour les futurs RequireFeature) → fix `b9e13b5` : validation de clé **orthogonale** au lifecycle de chargement (`validationError` dédié, `status` intouché, reducer exporté + 6 tests) → **APPROVE** → merge API. Relance `/autopilot` en session : **5 workers séquentiels en pile linéaire** (#302 dépend de tout ; CHANGELOG centralisé dans #302 → zéro conflit inter-maillons) → PRs #304-#308, 0 needs-clarification, 0 blocked ([rapport](reports/DAILY-REPORT-2026-07-20.md)). `/pr-review` **×5 parallèles → APPROVE ×5** ; seule retouche user-facing : coquille FR `docs.editions` (« tout la » → « tout de la », `6de9617`). Merge : tip cumulé validé (871 vitest + build + cargo 106) puis **ff-only** → push `main` **refusé : branche protégée** (whitelist `maximus`, règle du 2026-03-07, jamais rencontrée avant) — cause réelle : `~/.git-credentials` porte 3 identités Forgejo et `defenseur-auto-bot` en 1re position **shadow** `maximus` (git prend la 1re entrée du host ; les pushes de branches passaient, seule `main` whitelistée refusait). Fix scopé : `git remote set-url origin https://maximus@…` (ne PAS réordonner le fichier partagé — les agents defenseurs s'authentifient par lui). Push OK → 5 issues auto-fermées (`Resolves #N`), 5 PRs fermées + commentées, milestone 6/6 fermée, branches supprimées (+ purge de 7 branches `worktree-agent-*` du harness ; le « résidu `origin/issue-259` » signalé au warmup était un tracking ref périmé faute de `fetch --prune` — la branche remote avait bien été supprimée le 19). Décisions workers notables : tuiles `ProfileSelectionPage` **non** verrouillées (page atteinte seulement sans profil actif résoluble → un verrou heuristique risquerait un soft-lock hors de tous les profils) ; dev-override en **tête** de résolution (sinon un compte Premium actif masquerait `SR_DEV_EDITION`) ; `#[serde(default)]` préexistant sur `features` = les licences Base déjà émises sans le champ ne régressent pas. Gotcha process : un fork `/pr-review` lancé pendant que la CI est `pending` se termine « en attente du monitor CI » **sans jamais poster** (rien ne survit au fork) → monitorer la CI depuis le main loop, relancer le skill après le vert. Post-merge non bloquant : extraction `LockBadge`/`UpsellPanel` si une 3e surface apparaît ; label PIN « (annuler) » (bug cosmétique préexistant) ; job CI ponctuel `--features dev-override`. ADR 0017 Accepted. Aucune migration DB (v1→v16). (ref planned-2026-07-19-feature-gating, #297-#302, PRs #303-#308) - 2026-07-21 : **Milestone `planned-2026-07-19-feature-gating` livrée 6/6 et fermée — le gating par tier est sur `main`** ([Unreleased], candidate v0.15.0). Reprise du run `/autopilot` interrompu le 19 au soir (mort après la PR #303 ; worker #298 tué sans commit — 2 worktrees + branche vide nettoyés). Séquence : `/pr-review` #303 **REQUEST_CHANGES** (le retry backoff CWE-703 s'armait aussi sur un échec de `submitKey` → l'auto-refresh effaçait le message « clé invalide » ~1 s après ; et `status: "error"` persistant aurait laissé `ready=false` à jamais pour les futurs RequireFeature) → fix `b9e13b5` : validation de clé **orthogonale** au lifecycle de chargement (`validationError` dédié, `status` intouché, reducer exporté + 6 tests) → **APPROVE** → merge API. Relance `/autopilot` en session : **5 workers séquentiels en pile linéaire** (#302 dépend de tout ; CHANGELOG centralisé dans #302 → zéro conflit inter-maillons) → PRs #304-#308, 0 needs-clarification, 0 blocked ([rapport](reports/DAILY-REPORT-2026-07-20.md)). `/pr-review` **×5 parallèles → APPROVE ×5** ; seule retouche user-facing : coquille FR `docs.editions` (« tout la » → « tout de la », `6de9617`). Merge : tip cumulé validé (871 vitest + build + cargo 106) puis **ff-only** → push `main` **refusé : branche protégée** (whitelist `maximus`, règle du 2026-03-07, jamais rencontrée avant) — cause réelle : `~/.git-credentials` porte 3 identités Forgejo et `defenseur-auto-bot` en 1re position **shadow** `maximus` (git prend la 1re entrée du host ; les pushes de branches passaient, seule `main` whitelistée refusait). Fix scopé : `git remote set-url origin https://maximus@…` (ne PAS réordonner le fichier partagé — les agents defenseurs s'authentifient par lui). Push OK → 5 issues auto-fermées (`Resolves #N`), 5 PRs fermées + commentées, milestone 6/6 fermée, branches supprimées (+ purge de 7 branches `worktree-agent-*` du harness ; le « résidu `origin/issue-259` » signalé au warmup était un tracking ref périmé faute de `fetch --prune` — la branche remote avait bien été supprimée le 19). Décisions workers notables : tuiles `ProfileSelectionPage` **non** verrouillées (page atteinte seulement sans profil actif résoluble → un verrou heuristique risquerait un soft-lock hors de tous les profils) ; dev-override en **tête** de résolution (sinon un compte Premium actif masquerait `SR_DEV_EDITION`) ; `#[serde(default)]` préexistant sur `features` = les licences Base déjà émises sans le champ ne régressent pas. Gotcha process : un fork `/pr-review` lancé pendant que la CI est `pending` se termine « en attente du monitor CI » **sans jamais poster** (rien ne survit au fork) → monitorer la CI depuis le main loop, relancer le skill après le vert. Post-merge non bloquant : extraction `LockBadge`/`UpsellPanel` si une 3e surface apparaît ; label PIN « (annuler) » (bug cosmétique préexistant) ; job CI ponctuel `--features dev-override`. ADR 0017 Accepted. Aucune migration DB (v1→v16). (ref planned-2026-07-19-feature-gating, #297-#302, PRs #303-#308)
- 2026-07-19 : **Chantier gating par tier planifié via `/spec` → milestone `spec-feature-gating` (#297-#302)**. Origine : réflexion monétisation de Max (« changer le périmètre des abonnements »). Matrice tranchée : **Free** = Dashboard/Import/Transactions/Catégories/**rapport Tendance**/Export chiffré/Changelog (mono-profil) ; **Base** = + les 4 autres rapports/Budget/multi-profils/auto-update ; **Premium** = + Bilan complet (patrimoine + cours). Admin (Max) = licence Premium auto-émise (pas de 4e édition). Décisions clés : upsell **verrouillé** (pas masqué) ; durcissement **sec non destructif** (blocage d'accès, données conservées, récupérables par upgrade) ; entitlements **statique + override `features[]`** ; **enforcement UI-only** (soft-paywall GPL assumé, seul le gate cours reste dur/server-enforced) ; **#271 absorbé** (auto-update Base+, fermé superseded). Re-ancrage Phase 3b : socle déjà là (3 éditions Ed25519, `current_edition`, `check_entitlement`) mais `is_feature_allowed` ignore `features[]` + `useLicense` per-appel → **LicenseProvider** requis pour un `useEntitlement` sync ; rapports = routes distinctes (gate par route) ; #271 = 1 ligne. 6 issues (socle→garde UI→routes/sidebar→multi-profils→#271 Rust→docs), specs force-add (gitignorées, précédent #295), aucune migration DB. **`/review-spec` 3 experts → verdict 🟡, tout corrigé dans le plan** : 2 🔴 (nav `reports` gaté à tort alors que hub Free ; `advanced-reports` Rust mort contredit `reports-advanced`) + 6 🟡 (override `features[]` **fail-closed en Free** CWE-863 : une clé copiée downgrade free mais expose ses features signées ; `SR_DEV_EDITION` derrière une Cargo feature `dev-override` pas `debug_assertions` CWE-489 ; LicenseProvider récup. d'erreur CWE-703 ; `useEntitlement``{allowed,ready}` anti-flash ; `useIsPremium.test` à migrer ; gate création profil dans `ProfileFormModal`, point unique). Ajustements tranché **Base** par Max. **Re-homée `planned-2026-07-19-feature-gating`** (`/plan-run` Step 0 : bodies rendus auto-suffisants ; résiduel drainé — CTA « Obtenir » désactivé + « bientôt », dev-override gardé). Prête `/autopilot`. (ref planned-2026-07-19-feature-gating, #297-#302) - 2026-07-19 : **Chantier gating par tier planifié via `/spec` → milestone `spec-feature-gating` (#297-#302)**. Origine : réflexion monétisation de Max (« changer le périmètre des abonnements »). Matrice tranchée : **Free** = Dashboard/Import/Transactions/Catégories/**rapport Tendance**/Export chiffré/Changelog (mono-profil) ; **Base** = + les 4 autres rapports/Budget/multi-profils/auto-update ; **Premium** = + Bilan complet (patrimoine + cours). Admin (Max) = licence Premium auto-émise (pas de 4e édition). Décisions clés : upsell **verrouillé** (pas masqué) ; durcissement **sec non destructif** (blocage d'accès, données conservées, récupérables par upgrade) ; entitlements **statique + override `features[]`** ; **enforcement UI-only** (soft-paywall GPL assumé, seul le gate cours reste dur/server-enforced) ; **#271 absorbé** (auto-update Base+, fermé superseded). Re-ancrage Phase 3b : socle déjà là (3 éditions Ed25519, `current_edition`, `check_entitlement`) mais `is_feature_allowed` ignore `features[]` + `useLicense` per-appel → **LicenseProvider** requis pour un `useEntitlement` sync ; rapports = routes distinctes (gate par route) ; #271 = 1 ligne. 6 issues (socle→garde UI→routes/sidebar→multi-profils→#271 Rust→docs), specs force-add (gitignorées, précédent #295), aucune migration DB. **`/review-spec` 3 experts → verdict 🟡, tout corrigé dans le plan** : 2 🔴 (nav `reports` gaté à tort alors que hub Free ; `advanced-reports` Rust mort contredit `reports-advanced`) + 6 🟡 (override `features[]` **fail-closed en Free** CWE-863 : une clé copiée downgrade free mais expose ses features signées ; `SR_DEV_EDITION` derrière une Cargo feature `dev-override` pas `debug_assertions` CWE-489 ; LicenseProvider récup. d'erreur CWE-703 ; `useEntitlement``{allowed,ready}` anti-flash ; `useIsPremium.test` à migrer ; gate création profil dans `ProfileFormModal`, point unique). Ajustements tranché **Base** par Max. **Re-homée `planned-2026-07-19-feature-gating`** (`/plan-run` Step 0 : bodies rendus auto-suffisants ; résiduel drainé — CTA « Obtenir » désactivé + « bientôt », dev-override gardé). Prête `/autopilot`. (ref planned-2026-07-19-feature-gating, #297-#302)
@ -25,8 +26,9 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
- 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) - 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)
- 2026-07-08 : **Suite #260 — M1 « filtres-fondation » mergée** (`main` `fe9ae01`), M2 prête. Planifiée via `/plan-overnight`+`/review-spec` (filtre **multi-comptes** `accountIds[]`/`IN(...)` tranché après analyse, révise le « sourceId singulier » initial). **M1 livrée** via `/autopilot` (5 workers séquentiels en worktree → PRs #280-#284 en **pile linéaire**) + `/pr-review` **APPROVE ×5** + **merge local fast-forward** : hook `useReportsPeriod` additif (+`accountIds` URL `sources` + type `ReportFilters`, #272), 7 services en `accountIds[]` via helper paramétré `sqlFilters.inPlaceholders` (#273), `<FilterPanel>` slot temporel + multi-select « sources d'import » (libellé distinct du Bilan, #274), adoption Tendances (#275) + Compare/Budget (#276 — incl. `CompareBudgetView` ; câblage budget via `getActualTotalsForYear`, le body citait à tort `getBudgetVsActualData`). Aucune migration DB. Build + **791 vitest** verts (728 + 63). NB : le « 4676 / 277 fichiers » vu en pré-merge était une **pollution de worktrees**`vitest` récursait dans les 5 worktrees leftover sous `.claude/worktrees/` (791 × ~6) ; nettoyés depuis, compteur réel = 791. Gotcha : nettoyer les worktrees (ou exclure `.claude/worktrees/`) avant de valider un tip. Réconciliation Forgejo complète (5 issues auto-fermées via `Resolves #N`, 5 PRs fermées, milestone fermée, branches supprimées ; worktrees nettoyés). **M2 `overnight-2026-07-08-rapports-parite` (#277-#279) prête**`/autopilot` (BVA + grille budget income-first + dashboard Cartes). Notes review pour M2 : séries `getCartesSnapshot` (`fetchMonthlyFlows`/`fetchSeasonality`) non filtrées (I7/#279) ; grille budget câblée via `getActualTotalsForYear`. (ref #260, #272-#276, PRs #280-#284) - 2026-07-08 : **Suite #260 — M1 « filtres-fondation » mergée** (`main` `fe9ae01`), M2 prête. Planifiée via `/plan-overnight`+`/review-spec` (filtre **multi-comptes** `accountIds[]`/`IN(...)` tranché après analyse, révise le « sourceId singulier » initial). **M1 livrée** via `/autopilot` (5 workers séquentiels en worktree → PRs #280-#284 en **pile linéaire**) + `/pr-review` **APPROVE ×5** + **merge local fast-forward** : hook `useReportsPeriod` additif (+`accountIds` URL `sources` + type `ReportFilters`, #272), 7 services en `accountIds[]` via helper paramétré `sqlFilters.inPlaceholders` (#273), `<FilterPanel>` slot temporel + multi-select « sources d'import » (libellé distinct du Bilan, #274), adoption Tendances (#275) + Compare/Budget (#276 — incl. `CompareBudgetView` ; câblage budget via `getActualTotalsForYear`, le body citait à tort `getBudgetVsActualData`). Aucune migration DB. Build + **791 vitest** verts (728 + 63). NB : le « 4676 / 277 fichiers » vu en pré-merge était une **pollution de worktrees**`vitest` récursait dans les 5 worktrees leftover sous `.claude/worktrees/` (791 × ~6) ; nettoyés depuis, compteur réel = 791. Gotcha : nettoyer les worktrees (ou exclure `.claude/worktrees/`) avant de valider un tip. Réconciliation Forgejo complète (5 issues auto-fermées via `Resolves #N`, 5 PRs fermées, milestone fermée, branches supprimées ; worktrees nettoyés). **M2 `overnight-2026-07-08-rapports-parite` (#277-#279) prête**`/autopilot` (BVA + grille budget income-first + dashboard Cartes). Notes review pour M2 : séries `getCartesSnapshot` (`fetchMonthlyFlows`/`fetchSeasonality`) non filtrées (I7/#279) ; grille budget câblée via `getActualTotalsForYear`. (ref #260, #272-#276, PRs #280-#284)
- 2026-07-07 : **Tendances hiérarchiques livrées** (1re slice de l'epic #260 « rapports uniformes »). Préparée via `/plan-overnight` (13 décisions drainées ; `/review-spec` 3 experts → 3 critiques résolus par l'approche **sidecar** : garder le pivot par mois pour chart **+ dashboard** (`DashboardPage:142` = consommateur partagé oublié), ajouter un arbre **clé-par-id** pour le tableau — pivot clé-par-nom aurait faussé le Résultat net sur homonymes une fois le `LIMIT 50` retiré), puis exécutée **le même jour** via `/autopilot` (session interactive, ciblée par nom de milestone **hors fenêtre horaire** — cf. mémoire [[feedback-plan-execute-temporal-decoupling]]). 4 workers en worktree → issues **#262-#265**, `/pr-review` **APPROVE ×4**, merge local de la pile (#266 standalone + chaîne #267→#268→#269 ; CHANGELOG **auto-résolu par section**, vérifié) ; tip cumulé validé build + **728 vitest** → push `main` `fe87313`, réconciliation Forgejo (4 PRs fermées, 4 issues auto-fermées via `Resolves #N`, milestone `overnight-2026-07-08-tendances-hierarchiques` fermée, branches supprimées). Contenu : tendance par catégorie → tableau income-statement **hiérarchique + collapse replié-par-défaut** (défaut byCategory+table = fix « paie invisible »), `getCategoryOverTime` en **arbre id-keyed sidecar** via `buildLeafDrivenTree<T>` générique extrait de `buildCompareTree` (behavior-preserving), Résultat avant transferts interleavé. Aucune migration DB, non taggé ([Unreleased]). 2 polish non bloquants : perf dashboard (arbre construit non lu, #264), DRY `typeOf` dupliqué (#265). Reste de #260 (BVA-au-standard, budget, dashboard, filtres partagés) différé. (ref #260, #262-#265, PRs #266-#269) - 2026-07-07 : **Tendances hiérarchiques livrées** (1re slice de l'epic #260 « rapports uniformes »). Préparée via `/plan-overnight` (13 décisions drainées ; `/review-spec` 3 experts → 3 critiques résolus par l'approche **sidecar** : garder le pivot par mois pour chart **+ dashboard** (`DashboardPage:142` = consommateur partagé oublié), ajouter un arbre **clé-par-id** pour le tableau — pivot clé-par-nom aurait faussé le Résultat net sur homonymes une fois le `LIMIT 50` retiré), puis exécutée **le même jour** via `/autopilot` (session interactive, ciblée par nom de milestone **hors fenêtre horaire** — cf. mémoire [[feedback-plan-execute-temporal-decoupling]]). 4 workers en worktree → issues **#262-#265**, `/pr-review` **APPROVE ×4**, merge local de la pile (#266 standalone + chaîne #267→#268→#269 ; CHANGELOG **auto-résolu par section**, vérifié) ; tip cumulé validé build + **728 vitest** → push `main` `fe87313`, réconciliation Forgejo (4 PRs fermées, 4 issues auto-fermées via `Resolves #N`, milestone `overnight-2026-07-08-tendances-hierarchiques` fermée, branches supprimées). Contenu : tendance par catégorie → tableau income-statement **hiérarchique + collapse replié-par-défaut** (défaut byCategory+table = fix « paie invisible »), `getCategoryOverTime` en **arbre id-keyed sidecar** via `buildLeafDrivenTree<T>` générique extrait de `buildCompareTree` (behavior-preserving), Résultat avant transferts interleavé. Aucune migration DB, non taggé ([Unreleased]). 2 polish non bloquants : perf dashboard (arbre construit non lu, #264), DRY `typeOf` dupliqué (#265). Reste de #260 (BVA-au-standard, budget, dashboard, filtres partagés) différé. (ref #260, #262-#265, PRs #266-#269)
- 2026-07-07 : #254 (collapse/expand des sous-catégories) livré + #258 (db-locked) fermé. **#254** : chevron par catégorie parente + « tout replier/déplier » sur les 2 rapports comparables hiérarchiques (`ComparePeriodTable` réel-vs-réel, `BudgetVsActualTable` réel-vs-budget) ; **repli par défaut** (contrainte remontée de #260), état persisté par rapport en localStorage. Repli purement visuel — sous-totaux/totaux/résultats calculés sur les lignes brutes, jamais les visibles (tracé en /pr-review). Module `collapsibleRows.ts` (pur) + hook `useCollapsibleGroups.ts` + 12 tests → **713 vitest**, aucune migration DB. `CategoryOverTimeTable` exclu (liste plate top-N, rien à replier) → relève de l'epic #260. Livré via sous-agent en worktree isolé + /pr-review APPROVE + merge API (single PR, pas de conflit i18n/CHANGELOG) → PR #261, #254 auto-fermée. **#258** fermé comme déjà corrigé en v0.10.1 (`8cb7e53`, présent v0.11.0/v0.12.0) : la piste « BEGIN imbriqué » du body était une mauvaise lecture — `withTransaction` (`db.ts`) n'émet aucun BEGIN, `proposeStarterAccountsInTransaction:720` est le seul (même pattern que `saveSnapshotAtomic`/`upsertSnapshotLines` qui ne deadlockent jamais) ; smell latent noté (garde `inTransaction` = booléen global, pas un compteur → requête simple concurrente pendant une transaction bypasse le verrou). Prochaine étape : /plan-overnight de l'epic #260. (ref #254/#258, PR #261)
## Blockers actifs ## 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. - 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) ; **#310** (`status:triage`, 9 advisories RUSTSEC dans `Cargo.lock` — 6 correctifs patch trivials, à lander tôt sinon `audit.yml` reste rouge tous les jours). Release à considérer : le `[Unreleased]` porte tout le feature-gating → candidate `v0.15.0` via `/release`. - 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 ouverts des advisories (#310/#311 fermées) : **#314** — `audit.yml` **n'a jamais été déclenché** (zéro run `schedule` sur 420 tâches Actions) ; le `workflow_dispatch` fonctionne, donc le problème est isolé au **scheduler**, et tant qu'il n'est pas réglé la couverture quotidienne n'existe pas, quel que soit l'état vert du workflow. Puis **#312** (retirer les suppressions `quick-xml` quand `plist`/`tauri` passera à `>= 0.41.0` — le garde-fou attrape la suppression devenue injustifiée, #312 celle devenue inutile), **#313** (`tauri-plugin-deep-link 2.4.8` *yanked*, dépendance directe), **#315** (`/release` ne teste aucun cycle téléchargement+extraction réel, alors que `tar`/`rustls-webpki` ne sont ici que compilés), **#317** (re-évaluer `react-router` à une migration v8).
- Release à considérer : le `[Unreleased]` porte le feature-gating **et** les deux lots d'advisories → candidate `v0.15.0` via `/release`.