state: feature-gating milestone re-homed to planned-2026-07-19 (ready for autopilot)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
le king fu 2026-07-19 20:44:34 -04:00
parent 4f39fa3434
commit 0b408a8014

View file

@ -16,7 +16,7 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
## Decisions recentes
- 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. (ref spec-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)
- 2026-07-18 : **Epic #260 « rapports uniformes » fermée + #259 livrée en PR #296 (review)**. #260 fermée après `/analyze` : **4/5 rapports pleinement conformes** (income-statement + filtres partagés + collapse par-profil) ; les 3 écarts restants vs le texte de l'epic sont des **décisions de conception assumées**, entérinés par Max — (1) lignes vides budget non masquées (spec décision 6 : grille = surface d'édition) ; (2) collapse budget replié « comme partout » (#289/ADR 0016 inverse le « sauf budget » de l'epic) ; (3) dashboard convergé sur le modèle Cartes plutôt qu'une table income-statement hiérarchique (décision 5). **Correction STATE** : les entrées 07-08/07-11 « reste de #260 = #259 » sont fausses — #259 est une migration de taxonomie sans lien avec l'epic (recadrée 07-12), et les vraies déviations #260 n'y étaient pas tracées. **#259** (fusion des catégories custom) livrée en PR #296 (`issue-259-merge-custom-categories`) : bloc préservé de `StepSimulate` rendu en `MappingRow`, reducer `RESOLVE_ROW` résout rows+preserved (`unresolved` compté sur seed only → ne bloque pas « Suivant »), writer via helper `isResolvedTarget` (fourre-tout créé seulement s'il reste une custom non fusionnée ; customs fusionnées désactivées au lieu d'être re-parentées). Plan-check pass (6 MINOR, 3 intégrés), **836 vitest** + build tsc/vite propres, régression parent/enfant custom couverte, aucune migration DB. `/pr-review` **APPROVE**, mergée (rebase) le 2026-07-19 → `main` `2314a64`, #259 fermée, branche supprimée. (ref #260, #259 PR #296)
- 2026-07-18 : **Milestone collapse multi-niveaux (#288-291) livrée → v0.14.0**. Cycle complet en une session : `/plan-run` (spec 2 fichiers, 8 décisions drainées) → `/review-spec` (3 experts, **verdict 🔴**) → refonte v2 → `/autopilot` (4 workers) → `/pr-review` ×4 → ff-merge → release. **Le point clé** : la revue a tué l'algo v1. Il suivait un curseur de profondeur supposant un **ordre DFS** ; or la grille Budget (`useBudget.ts:361`) trie par **niveau** — les 3 experts l'ont trouvé indépendamment. Refonte v2 : **visibilité par remontée de `parent_id`** (une ligne visible ssi tous ses ancêtres dépliés), order-independant → résout d'un coup l'ordre budget, le tri par type qui sépare parent/enfant, la feuille « (direct) » qui partage la clé du parent, et la contrainte `visible()`-avant-`reorderRows`. **Pile linéaire forcée** (B/C/D dépendent tous du nouveau hook de A → pas de wave parallèle) : PRs #292-#295, chacune basée sur la précédente. `/pr-review` **APPROVE ×3 + 1 REQUEST_CHANGES** (#295 : ligne `- Spec:` de l'ADR 0016 pointant des specs gitignorées → 404 ; corrigé par **force-add des specs à la racine**, précédent repo `spec-refonte-rapports.md` ; au passage la revue s'est trompée en disant que 0015 n'avait pas de ligne Spec — elle en a une, cassée pareil, bug pré-existant signalé). **ff-merge** de la pile (4 issues auto-fermées via `Resolves #N`, milestone 4/4, 4 branches supprimées) → **v0.14.0** taggée. **Persistance migrée `localStorage` → `user_preferences`** (base du profil) : `deleteProfile` ne purge aucun `localStorage` → une clé par-profil y serait un résidu survivant à la suppression, révélant les catégories explorées d'un profil PIN-protégé (exigence privacy-first, **ADR 0016**, frontière tracée : état UI par-profil → DB profil, état UI machine → localStorage). Le hook gagne `defaultExpanded` + `storageKey` nullable, qui **unifie aussi les 2 arbres de catégories** (#290 : `CategoryTree` déplié-par-défaut, guide replié ; corrige le bug `allExpanded = size>0` du guide ; worker a trouvé un **4e consommateur** `StepDiscover` non listé au plan, migré). 828 vitest, aucune migration DB. Écarts protocole tracés au [rapport](reports/DAILY-REPORT-2026-07-15.md) : workers auto-validés **sans forker `/pr-review`** (évite le double-post [[feedback-pr-review-subagent-forks]]) ; #294 sans test (refactor de rendu, non testable sans jsdom) → vérif runtime déléguée à la revue. Reste `spec-ci-build-optimization` (2/4) + `spec-paiements` (#270/#271) + #259. (ref #288-291, PRs #292-#295)
- 2026-07-12 : **#259 recadrée — l'issue décrivait un flux qui n'existe pas** (rectifie les entrées des 2026-07-05 / 07-11 qui la classaient « mapping manuel compte sans similaire auto » et « reliquat de l'epic #260 » : les deux sont faux). Le corps d'origine, rédigé à chaud le 2026-07-05 pendant le run v0.12.0, avait **transposé par analogie** le signalement de Max vers le module Bilan sans ouvrir le fichier : il décrivait une auto-association des comptes standards aux comptes existants du profil dans `StarterAccountsModal`, avec « case désactivée quand aucun similaire n'est auto-identifié ». **Aucune notion de mapping n'existe dans ce flux** — la case est un simple « créer ce compte : oui/non », désactivée **quand une collision EST détectée** (`StarterAccountsModal.tsx:160`), soit la polarité inverse ; le commentaire `l.8` cité (« the matching checkbox ») désignait « la case **correspondante** », pas « la case de matching ». **Le vrai sujet** (confirmé par Max) est la **migration des catégories** : `computeMigrationPlan` range les catégories en 2 seaux et un seul est éditable — `plan.rows` (seed) passe par le moteur d'appariement + type-ahead par ligne (#246/#252), tandis que `plan.preserved` (catégories **custom**) est poussé avec `v1TargetId: null` **sans même être soumis au moteur d'appariement**, rendu en `<li>` texte brut (`StepSimulate.tsx:202-216`, aucun picker) et déversé d'office par le writer sous le fourre-tout « Catégories personnalisées (migration) ». Sémantique tranchée avec Max : **fusion** (choisir une feuille standard réassigne tx/budgets/mots-clés/fournisseurs et fait disparaître la custom ; ne rien choisir = comportement actuel, et ne doit **pas** bloquer le bouton « Suivant »). Re-parentage écarté. Travail = câblage sur 3 couches (UI `StepSimulate``MappingRow` réutilisable tel quel ; reducer `RESOLVE_ROW` qui ne voit que `plan.rows` ; 4 retouches du writer) — la machinerie de fusion est **déjà générique** (`buildMappingFromRows` filtre les cibles nulles, étapes 3-7 bouclent sur la Map). Complexité Medium. Piège à couvrir : fusionner une custom **parente ayant des enfants custom** (liste `preserved` plate → l'enfant non résolu retombe au fourre-tout, pas d'orphelin, mais aucun test ne le garantit). Leçon process : une issue rédigée par analogie en fin de run, sans lecture du code, peut inverser la prémisse **et** se tromper de module — le `/analyze` l'a rattrapée 6 jours plus tard. (ref #259)
@ -31,4 +31,4 @@ Audit critique de la page Bilan livré (`docs/audit-bilan-2026-05.md`, revue CPA
## 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-feature-gating` (#297-#302, gating par tier Free/Base/Premium — planifié 2026-07-19) ; `spec-paiements` (#270 activation /v1 + product explicite + URL achat localisée ; #271 absorbé par #301) ; `spec-ci-build-optimization` 2/4 (#234 connectivité cache runner, #232 split workflows).
- Backlog ouvert (`status:ready`, non bloqué) : `planned-2026-07-19-feature-gating` (#297-#302, gating par tier — **prête `/autopilot`**, bodies auto-suffisants) ; `spec-paiements` (#270 activation /v1 + product explicite + URL achat localisée ; #271 absorbé par #301) ; `spec-ci-build-optimization` 2/4 (#234 connectivité cache runner, #232 split workflows).