# 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@…`) ## Position actuelle v0.9.1 shippée (2026-05-10). Milestones `spec-refonte-rapports`, `spec-refonte-seed-categories-ipc`, `spec-price-fetching`, `overnight-2026-04-26-bilan` et `overnight-2026-04-27-prices` complétées et fermées. Maximus-api 0.3.0 LIVE en prod (`api.lacompagniemaximus.com`) — pubkey Ed25519 alignée, smoke test Phase B (#161) validé. Backlog actif concentré sur `spec-monetisation` (7/12) : activation en ligne (#53), pipeline Stripe desktop (#50, #52) et maximus-api Stripe webhooks (#135, #136). 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). **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.12.0 shippée** (2026-07-05, tag `v0.12.0` → CI release #311) — recadrage de #243 (montants de transfert inclus dans le comparable) par Max en « analyse de résultat » : deux rapports comparables refondus en états de résultat (income statement). Le compare réel-vs-réel (#253) et le tableau de tendance par catégorie (#256) affichent le **revenu**, un **Résultat avant transferts** (revenus − dépenses) et un **Résultat net** (après transferts), sections ordonnées revenu→dépense→transfert ; les rapports comparables ouvrent par défaut sur le **mois complet précédent** (#253). Modules purs extraits et testés (`compareResults.ts`, `overTimeResults.ts`) ; 2 blocages `/pr-review` corrigés (fuite du revenu dans les top-movers Cartes ; garde StrictMode sur le sync du mois de référence). Livré en 2 issues/PRs stackées #255→#257 (merge local + réconciliation Forgejo, branches supprimées), 701 vitest verts, aucune migration DB (v1→v16 inchangées), 20 tables / 24 index. Backlog ouvert au passage : #258 (db-locked `proposeStarterAccounts`) → fermé le 2026-07-07 (déjà corrigé v0.10.1, pas de nesting) ; #254 (collapse/expand) → livré le 2026-07-07 (PR #261) ; #259 (mapping manuel d'un compte standard sans similaire auto) et #260 (epic « rapports uniformes » — tous les rapports sur le modèle income-statement) ouverts. ## Decisions recentes - 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-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-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 `