6.5 KiB
Spec Decisions — Gating des fonctionnalités par édition de licence
Date: 2026-07-19 Projet: simpl-resultat Statut: Draft Slug: feature-gating
Contexte
Simpl'Résultat n'applique aujourd'hui aucun gating de fonctionnalités : un utilisateur Free a accès à tout, sauf la récupération des cours (seul gate dur, vérifié côté serveur via maximus-api). L'infrastructure de licence est pourtant déjà là — 3 éditions signées Ed25519 (free/base/premium), table FEATURE_TIERS, champ features[] signé — mais seuls 3 call-sites de gate existent (auto-update, laissé ouvert ; cours, Premium). Max veut définir un vrai périmètre d'abonnement en verrouillant des modules selon le tier.
Objectif
Appliquer une matrice d'entitlements Free/Base/Premium à travers l'app : chaque module hors du tier de l'utilisateur reste visible mais verrouillé, avec un écran d'upsell. Le gating est piloté par un hook central côté React, adossé à l'édition de licence déjà vérifiée, avec un override par-licence optionnel. Aucune donnée n'est supprimée — le durcissement bloque l'accès, il ne détruit rien.
Scope
IN
- Matrice d'entitlements Free/Base/Premium (ci-dessous).
- Hook central
useEntitlement(feature)+ garde de navigation (Sidebar) et de route. - Écran/zone d'upsell « verrouillé » (cadenas, CTA upgrade), i18n FR/EN.
- Enrichir
FEATURE_TIERS(Rust) + câbler l'override par-licence (features[]consulté paris_feature_allowed). - Gating par onglet des rapports (
/reports/*) : Free = Tendance uniquement. - Absorption du re-gate auto-update (#271) : l'auto-update devient Base+.
- Durcissement sec non destructif : blocage d'accès immédiat, données conservées, récupérables par upgrade.
OUT (explicitement exclu)
- Le champ
productexplicite (#270) — reste dans spec-paiements. - Le go-live Stripe / le flux d'achat en ligne (l'upsell pointe vers l'achat, dont l'URL localisée relève de #270).
- Un vrai système de codes d'invitation (l'émission de clés reste manuelle via l'endpoint admin
generate). - Tout gate « dur » server-enforced au-delà de l'existant (cours) — le gating local est un soft paywall assumé (GPL).
- Un 4e tier « admin » : l'accès total de Max = une licence Premium auto-émise.
- Mode lecture-seule des features verrouillées (écarté : durcissement sec, pas de distinction voir/éditer).
Matrice d'entitlements
| Module | Free | Base | Premium |
|---|---|---|---|
| Dashboard | ✅ | ✅ | ✅ |
| Import CSV | ✅ | ✅ | ✅ |
| Transactions | ✅ | ✅ | ✅ |
| Catégories (+ guide, migration) | ✅ | ✅ | ✅ |
| Rapport Tendance | ✅ | ✅ | ✅ |
| Autres rapports (Highlights, Comparaison, Catégorie, Cartes) | ❌ | ✅ | ✅ |
| Budget | ❌ | ✅ | ✅ |
| Ajustements (écritures manuelles + split de transactions) | ❌ | ✅ | ✅ |
| Multi-profils | ❌ | ✅ | ✅ |
| Auto-update | ❌ | ✅ | ✅ |
| Bilan complet (patrimoine + cours) | ❌ | ❌ | ✅ |
| Export/Import chiffré | ✅ | ✅ | ✅ |
| Changelog / Docs | ✅ | ✅ | ✅ |
Admin (Max) = licence Premium auto-émise (superset).
Décisions prises
| Question | Décision | Raison |
|---|---|---|
| UX du gate (feature hors tier) | Verrouillé + upsell : entrée visible avec cadenas ; clic → écran « Passez à Base/Premium » | Meilleure conversion — l'utilisateur voit ce qu'il rate |
| Données existantes lors du durcissement | Durcissement sec non destructif : blocage d'accès (vue + édition) immédiat, mais données conservées, jamais supprimées, récupérables par upgrade | Simple à coder (pas de mode lecture-seule) ; ne détruit aucune donnée financière (clarifié par Max) |
| Modèle d'entitlements | Statique + override licence : map tier → features en dur, PLUS license.features[] consulté en additif (allowed = feature ∈ tier OU feature ∈ license.features) |
Matrice centralisée et simple, + débloque le champ features[] déjà signé pour des licences spéciales sans créer de tier |
| Coordination spec-paiements | Absorber #271 (re-gate auto-update) ici ; #270 (product) reste séparé |
L'auto-update est dans la matrice → gaté ici, #271 redondant ; product/URL d'achat = go-live, hors scope |
| Enforcement | UI-only (gardes React : nav + route). current_edition() (Rust) reste la source de vérité lue ; les commandes Tauri ne refusent pas (sauf l'existant : cours server-enforced) |
Cohérent avec le soft-paywall GPL assumé — un gate Rust serait aussi retirable par un fork, sans gain de sécurité |
| Écran d'upsell | Composant générique paramétré (feature + tier requis), i18n FR/EN ; CTA « Obtenir Base/Premium » (lien d'achat, dépend de #270) + « J'ai déjà une clé » → Réglages → Licence | Un seul composant réutilisable, pas un écran par feature |
| Admin (accès total de Max) | Licence Premium auto-émise (endpoint admin generate), pas de 4e édition ni mode dev |
Premium = superset ; réutilise l'infra existante, zéro code neuf |
| Free multi-profils existants | Profil actif conservé ; profils supplémentaires verrouillés → upsell (données conservées) | Cohérent avec durcissement sec + conservation des données |
/adjustments (module Ajustements) |
Base | Outil de correction avancé (écritures + split multi-catégories) → différenciateur payant (tranché post-review 2026-07-19) |
Contraintes
- GPL-3.0 + privacy-first : le gating local est un soft paywall assumé (contournable par fork). Seules les features adossées à maximus-api (les cours, aujourd'hui) sont dures. Aucune donnée financière ne quitte l'appareil.
- Séquencement : le gating est livrable et testable indépendamment de Stripe (pas encore en live) via des clés admin-émises. Corollaire : sans ce chantier, distribuer des clés n'a aucun effet visible (tout est déjà ouvert).
Références
| Source | Pertinence |
|---|---|
spec-monetisation.md |
Modèle des 3 tiers, contraintes GPL / soft-paywall, format de licence |
src-tauri/src/commands/entitlements.rs |
FEATURE_TIERS + is_feature_allowed — table à enrichir, override features[] à câbler |
src-tauri/src/commands/license_commands.rs |
current_edition() (source de vérité de l'édition) + LicenseClaims.features (champ signé à consulter) |
src/services/licenseService.ts, src/hooks/useLicense.ts, src/hooks/useIsPremium.ts |
Base du futur hook useEntitlement |
src/App.tsx (routes /reports/*), src/components/layout/Sidebar |
Points de garde nav + route |