Simpl-Resultat/STATE.md
le king fu e5c188e2d8 state: close #314 — audit.yml scheduler was never broken
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 12:42:38 -04:00

41 KiB
Raw Blame History

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@…)

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@…)

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). 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.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-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éescargo tree -i vide sur x86_64-pc-windows-msvc et x86_64-unknown-linux-gnu, présent seulement sur x86_64-apple-darwin via plisttauri (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 overridevite 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_requestreference-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 plisttauri (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). /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 localStorageuser_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 : 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 StepSimulateMappingRow 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).
  • 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.